Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

LayoutInflater

Однажды я пытался раскрыть вам тайное значение названия метода inflate() - слово inflate происходит от словосочетания in flat - в квартиру. Существует старинная традиция запускать в квартиру первым кота, который исследует все закоулки дома и заявляет о своём согласии жить в нём. Однако, мы сделаем вид, что поверили официальной версии, что по-английски inflate переводится как надувать, т.е. мы как бы надуваем данными из XML-файла объекты View.

Мы рассмотрим надувание компонентов применимо к LayoutInflater. Существует также класс MenuInflater, который использует тот же принцип.

Фрагменты тоже часто используют механизм надувания. Обратите внимание на сигнатуру метода onCreateView():


onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Сам механизм надувания прост и не требует особых объяснений, изучения достоин один аспект.

Вероятно, вам не раз приходилось писать/копировать такой код:


inflater.inflate(R.layout.my_layout, null);

Даже у меня на сайте есть такие примеры. Недавно я заметил, что среда разработки ругается на последний параметр null.

Оказывается, это неправильно. Давайте изучать этот код.

Существует две версии метода inflate() для стандартных приложений.


inflate(int resource, ViewGroup root)
inflate(int resource, ViewGroup root, boolean attachToRoot)

В первом параметре указывается идентификатор ресурса разметки, который мы собираемся надуть. Во втором параметре указывается корневой компонент, к которому нужно присоединить надутые объекты. В третьем параметре (если он используется) указывается, нужно ли присоединять надутые объекты к корневому элементу.

LayoutInflater автоматически пытается присоединить надутые компоненты к корневому элементу. Иногда сама среда разработки может создать заготовку с использованием null, чтобы избежать возможного краха приложения. И разработчики используют предложенный вариант, хотя можно было использовать второй вариант метода с тремя параметрами.

Но давайте узнаем, чего мы лишаемся, когда используем null для корневого элемента.

Надувание компонентов часто применяется в адаптерах, когда разметка отдельного элемента списка задаётся в XML-файле, а затем динамически конвертируется в набор компонентов в методе getView():


// Отрывок из одного примера
@Override
public View getView(int position, View convertView, ViewGroup parent) {
	Cat cat = getItem(position);

	if (convertView == null) {
		convertView = LayoutInflater.from(getContext())
				.inflate(android.R.layout.simple_list_item_2, 
						null);
	}
    
    ...

	return convertView;
}

LayoutInflater

Среда разработки заругалась. Исправим null на parent, который по названию вроде подходит под корневой элемент. Однако, запустив проект, мы получим ошибку. Йожкин кот, что мы наделали?!

Вспоминаем, что есть другая версия метода и исправляем на следующий вариант.


convertView = LayoutInflater.from(getContext())
		.inflate(android.R.layout.simple_list_item_2, 
		parent, false);

Пример работает, предупреждение не выводится. Но осадочек остался.

При использовании null среда разработки как-бы пытается сказать: "Я не знаю, какой элемент является родительским, поэтому прости, между нами всё кончено", я буду использовать null.

Тогда зачем нам этот родительский элемент? На самом деле он может сыграть важную роль в различных ситуациях.

Проблемы могут возникнуть при использовании атрибутов android:layout_xxx в родительском элементе. В результате, не зная ничего о родителе, мы не можем использовать его LayoutParams и система будет игнорировать вашу разметку. А вы будете думать, что это баг.

Без LayoutParams родительский ViewGroup надует компоненты с настройками по умолчанию. И в большинстве случае это вполне работает.

Рассмотрим конкретный пример. Создадим разметку для отдельного элемента списка layout/list_item.xml.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingRight="15dp" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

</LinearLayout>

Мы хотим использовать фиксированную высоту для каждого элемента и используем атрибут android:layout_height у родительского элемента.

Попробуем неправильный вариант.


convertView = LayoutInflater.from(getContext()).inflate(
		R.layout.list_item, null);

Результат.

LayoutInflater

В этом варианте игнорируется родитель и соответственно его настройки.

Перепишем пример.


convertView = LayoutInflater.from(getContext()).inflate(
		R.layout.list_item, parent, false);

Результат на экране.

LayoutInflater

Теперь вам должна быть ясна разница между разными вариантами и правильный код для применения.

Но бывают случаи, когда использование null оправданно.

Одним из таких примеров является использование разметки в AlertDialog. Допустим, у нас есть код.


AlertDialog.Builder builder = new AlertDialog.Builder(context);
View content = LayoutInflater.from(context).inflate(R.layout.item_row, null);
 
builder.setTitle("My Dialog");
builder.setView(content);
builder.setPositiveButton("OK", null);
builder.show();

AlertDialog.Builder не нуждается в родительском элементе для построения диалогового окна.

Рассмотрим случаи, когда нужен true. Допустим, у нас есть отдельная разметка кнопки.


<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/button">
</Button>

Мы можем программно присоединить кнопку к LinearLayout в активности или фрагменте.


inflater.inflate(R.layout.custom_button, mLinearLayout, true);

Мы указали, что хотим надуть кнопку из его собственной разметки и присоединить полученную кнопку к LinearLayout (второй параметр). Это решение эквивалентно вызову метода с двумя параметрами.


inflater.inflate(R.layout.custom_button, mLinearLayout);

Как вариант, можно не присоединять при надувании, а добавить программно через addView().


Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
mLinearLayout.addView(button);

Другой случай использования true встречается при создании собственного компонента, когда применяется <merge>.


public class MyCustomView extends LinearLayout {
    ...
    private void init() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(R.layout.view_with_merge_tag, this);
    }
}

У файла разметки нет корневого ViewGroup и мы указываем свой собственный компонент на основе LinearLayout в качестве корневого. Если бы мы использовали в своей разметке вместо merge любой компонент, например, FrameLayout, то тогда был бы другой случай.Теперь вы знаете, в каких ситуациях нужно использовать разные методы inflate(). Это как с котом - надутого кота можно обратить в null, когда вы не нуждаетесь в двойниках.

Реклама