Android inflate the layout

LayoutInflater

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

У файла разметки нет корневого ViewGroup и мы указываем свой собственный компонент на основе LinearLayout в качестве корневого. Если бы мы использовали в своей разметке вместо merge любой компонент, например, FrameLayout, то тогда был бы другой случай.

Теперь вы знаете, в каких ситуациях нужно использовать разные методы inflate(). Это как с котом — надутого кота можно обратить в null, когда вы не нуждаетесь в двойниках.

Источник

Полный список

— разбираем как можно использовать LayoutInflater

После изучения SQLite самое время приступить к изучению списков – List. Но перед этим полезно будет узнать про LayoutInflater. Это знание пригодится нам в создании расширенных списков. Также перед этим уроком рекомендую снова прочесть урок про LayoutParams, освежить знания.

LayoutInflater – это класс, который умеет из содержимого layout-файла создать View-элемент. Метод который это делает называется inflate. Есть несколько реализаций этого метода с различными параметрами. Но все они используют друг друга и результат их выполнения один – View.

Читайте также:  Госуслуги apk 4pda android

Как видим, на вход метод принимает три параметра:

resource — ID layout-файла, который будет использован для создания View. Например — R.layout.main
root – родительский ViewGroup-элемент для создаваемого View. LayoutParams от этого ViewGroup присваиваются создаваемому View.
attachToRoot – присоединять ли создаваемый View к root. Если true, то root становится родителем создаваемого View. Т.е. это равносильно команде root.addView(View). Если false – то создаваемый View просто получает LayoutParams от root, но его дочерним элементом не становится.

Посмотрим на практике.

Project name: P0401_LayoutInflater
Build Target: Android 2.3.3
Application name: LayoutInflater
Package name: ru.startandroid.develop.p0401layoutinflater
Create Activity: MainActivity

Открываем main.xml и рисуем такой экран:

На экране две ViewGroup — linLayout и relLayout. В них по TextView с соответствующим текстом.

Создадим еще один layout-файл text.xml:

Тут просто TextView без всяких ViewGroup. На нем мы и будем испытывать LayoutInflater.

Открываем MainActivity.java и пишем код:

Мы получаем LayoutInflater методом getLayoutInflater, используем его для получения View-элемента из layout-файла text.xml и считываем LayoutParams у свежесозданного view.

Обратите внимание на параметры, которые мы использовали для метода inflate. Мы указали ID layout-ресурса, передали null в качестве родительского элемента и, соответственно, привязка к родителю — false.

Все сохраним и запустим.

На экране ничего не изменилось. Т.к. мы конвертнули layout в view, но никуда его не поместили. Он просто висит в памяти.

Class of view: class android.widget.TextView
LayoutParams of view is null: true
Text of view: Layout with TextView

Мы видим класс созданного элемента — TextView. Все верно — этот элемент и был в файле text.xml. Далее видим null вместо LayoutParams. Это произошло потому, что родителя в методе inflate мы указали null. А именно от родителя view и должен был получить LayoutParams. Третья строка лога показывает текст TextView. Он тот же, что и в layout-файле text.xml – все верно.

Давайте немного изменим программу. Будем добавлять наш созданный элемент в linLayout из main.xml. Делается это просто – командой addView.

(добавляете только выделенные строки)

Мы нашли linLayout с экрана и добавили в него созданный с помощью LayoutInflater элемент.

Сохраняем, запускаем. Видим, что элемент добавился на экран в linLayout.

Теперь давайте попробуем указать родителя (root) при вызове метода inflate. Перепишем метод onCreate:

Мы находим элементы linLayout и relLayout с экрана и с помощью LayoutInflater создаем два View-элемента из layout-файла text.xml. Для первого указываем root – linLayout, для второгоrelLayout. Но третий параметр attachToRoot оставляем false. Это значит, что созданный View-элемент получит LayoutParams от root-элемента, но не добавится к нему.

Все сохраним, запустим. На экране ничего не поменялось. Т.к. мы ни к чему новые элементы не добавляли и attachToRoot = false.

Class of view1: class android.widget.TextView
Class of layoutParams of view1: class android.widget.LinearLayout$LayoutParams
Text of view1: Layout with TextView
Class of view2: class android.widget.TextView
Class of layoutParams of view2: class android.widget.RelativeLayout$LayoutParams
Text of view2: Layout with TextView

По логам видно, что класс созданных элементов – TextView. А класс LayoutParams различается. В первом случае – это LinearLayout$LayoutParams, т.к. в качестве root элемента в методе inflate мы указали linLayout, а это объект класса LinearLayout. Во втором случае класс LayoutParams у созданного элемента — RelativeLayout$LayoutParams. Потому, что в качестве root указали relLayout (класс RelativeLayout).

Теперь у нас два варианта, как добавить созданные view1 и view2 на экран.

1) Снова использовать методы addView

2) Передавать true в качестве третьего параметра метода inflate. Тогда созданный View-элемент будет добавлен к root.

Выберем второй вариант и внесем изменения в код:

Передаем true в качестве третьего параметра в методе inflate и убираем строки выведения в лог текстов из TextView. Сейчас будет понятно почему.

Все сохраним и запустим приложение.

Как видим, созданные TextView появились в своих родителях, которых мы указали в методе inflate. В RelativeLayout элементы наложились друг на друга, т.к. мы не настроили расположение. В данный момент это не существенно.

Class of view1: class android.widget.LinearLayout
Class of layoutParams of view1: class android.widget.LinearLayout$LayoutParams
Class of view2: class android.widget.RelativeLayout
Class of layoutParams of view2: class android.widget.LinearLayout$LayoutParams

Обратите внимание на класс элементов. В первом случае — это LinearLayout, а во втором — RelativeLayout. Т.е. метод inflate вернул нам не созданные из layout-файла View-элементы, а те, что мы указывали как root. А созданные из layout-файла View элементы он добавил в root как дочерние аналогично команде addView. Это произошло потому, что мы указали true в третьем параметре (attachToRoot) метода inflate.

Соответственно LayoutParams для view1 и view2 будет LinearLayout$LayoutParams, т.к. linLayout и relLayout имеют родителя LinearLayout. И LayoutParams берут от него.

Для закрепления темы на следующем уроке сделаем пример поинтереснее.

На следующем уроке:

— делаем свой вариант списка

getLayoutInflater

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

Читайте также:  Отображение процессора для андроид

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Understanding Android’s LayoutInflater.inflate()

Understanding Android’s LayoutInflater.inflate()

It’s easy to get comfortable with boilerplate setup code, so much so that we gloss over the finer details. I’ve experienced this with LayoutInflater (which coverts an XML layout file into corresponding ViewGroups and Widgets) and the way it inflates Views inside Fragment’s onCreateView() method. Upon looking for clarification in Google documentation and discussion on the rest of the web, I noticed that many others were not only unsure of the specifics of LayoutInflater’s inflate() method, but were completely misusing it.

Much of the confusion may come from Google’s vague documentation in regards to attachToRoot , the optional third parameter of the inflate() method.

Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view of the XML.

Maybe the confusion comes from a statement that ends in a question mark?

The general gist is this: If attachToRoot is set to true , then the layout file specified in the first parameter is inflated and attached to the ViewGroup specified in the second parameter.

Then the method returns this combined View, with the ViewGroup as the root. When attachToRoot is false , the layout file from the first parameter is inflated and returned as a View. The root of the returned View would simply be the root specified in the layout file. In either case, the ViewGroup’s LayoutParams are needed to correctly size and position the View created from the layout file.

Passing in true for attachToRoot results in a layout file’s inflated View being added to the ViewGroup right on the spot. Passing in false for attachToRoot means that the View created from the layout file will get added to the ViewGroup in some other way.

Let’s break down both scenarios with plenty of examples so we can better understand.

attachToRoot Set to True

Imagine we specified a button in an XML layout file with its layout width and layout height set to match_parent .

We now want to programmatically add this Button to a LinearLayout inside of a Fragment or Activity. If our LinearLayout is already a member variable, mLinearLayout , we can simply add the button with the following:

We specified that we want to inflate the Button from its layout resource file; we then tell the LayoutInflater that we want to attach it to mLinearLayout . Our layout parameters are honored because we know the Button gets added to a LinearLayout. The Button’s layout params type should be LinearLayout.LayoutParams.

The following would also be equivalent. LayoutInflater’s two parameter inflate() method automatically sets attachToRoot to true for us.

Another appropriate use of passing true for attachToRoot is a custom View. Let’s look at an example where a layout file uses a tag for its root. Using a tag signifies that the layout file allows for flexibility in terms of the type of root ViewGroup it may have.

This is a perfect use for a true attachToRoot parameter. The layout file does not have a root ViewGroup in this example, so we specify our custom LinearLayout to be its root. If our layout file had a FrameLayout as its root instead of , the FrameLayout and its children would inflate as normal. Then the FrameLayout and children would get added to the LinearLayout, leaving the LinearLayout as the root ViewGroup containing the FrameLayout and children.

attachToRoot Set to False

Let’s take a look at when you would want to set attachToRoot to false . In this scenario, the View specified in the first parameter of inflate() is not attached to the ViewGroup in the second parameter at this point in time.

Recall our Button example from earlier, where we want to attach a custom Button from a layout file to mLinearLayout . We can still attach our Button to mLinearLayout by passing in false for attachToRoot —we just manually add it ourselves afterward.

These two lines of code are equivalent to what we wrote earlier in one line of code when we passed in true for attachToRoot . By passing in false , we say that we do not want to attach our View to the root ViewGroup just yet. We are saying that it will happen at some other point in time. In this example, the other point in time is simply the addView() method used immediately below inflation.

The false attachToRoot example requires a bit more work when we manually add the View to a ViewGroup. Adding our Button to our LinearLayout was more convenient with one line of code when attachToRoot was true . Let’s look at some scenarios that absolutely require attachToRoot to be false .

Читайте также:  Петька 4 день независимости для андроид

A RecyclerView’s children should be inflated with attachToRoot passed in as false . The child views are inflated in onCreateViewHolder() .

RecyclerViews, not us, are responsible for determining when to inflate and present its child Views. The attachToRoot parameter should be false anytime we are not responsible for adding a View to a ViewGroup.

When inflating and returning a Fragment’s View in onCreateView() , be sure to pass in false for attachToRoot . If you pass in true , you will get an IllegalStateException because the specified child already has a parent. You should have specified where your Fragment’s view will be placed back in your Activity. It is the FragmentManager’s job to add, remove and replace Fragments.

The root_viewGroup container that will hold your Fragment in your Activity is the ViewGroup parameter given to you in onCreateView() in your Fragment. It’s also the ViewGroup you pass into LayoutInflater.inflate() . The FragmentManager will handle attaching your Fragment’s View to this ViewGroup, however. You do not want to attach it twice. Set attachToRoot to false .

Why are we given our Fragment’s parent ViewGroup in the first place if we don’t want to attach it in onCreateView() ? Why does the inflate() method request a root ViewGroup?

It turns out that even when we are not immediately adding our newly inflated View to its parent ViewGroup, we should still use the parent’s LayoutParams in order for the new View to determine its size and position whenever it is eventually attached.

You are bound to run into some poor advice about LayoutInflater on the web. Some people will advise you to pass in null for the root ViewGroup if you are going to pass in false for attachToRoot . However, if the parent is available, you should pass it in.

Lint will now warn you not to pass in null for root . Your app won’t crash in this scenario, but it can misbehave. When your child View doesn’t know the correct LayoutParams for its root ViewGroup, it will try to determine them on its own using generateDefaultLayoutParams.

These default LayoutParams might not be what you desired. The LayoutParams that you specified in XML will get ignored. We might have specified that our child View should match the width of our parent View, but ended up with our parent View wrapping its own content and ending up much smaller than we expected.

There are a few scenarios in which you will not have a root ViewGroup to pass into inflate() . When creating a custom View for an AlertDialog, you do not yet have access to its parent.

In this case, it is okay to pass in null for the root ViewGroup. It turns out that the AlertDialog would override any LayoutParams to match_parent anyway. However, the general rule of thumb is to pass in the parent if you’re able to do so.

Avoiding Crashes, Misbehaviors and Misunderstandings

Hopefully this post helps you avoid crashes, misbehaviors and misunderstandings when using LayoutInflater. Here are some key takeaways for different uses in certain circumstances:

  • If you have a parent to pass into the root ViewGroup parameter, do so.
  • Try to avoid passing in null for the root ViewGroup.
  • Pass in false for the attachToRoot parameter when we are not the ones responsible for attaching our layout file’s View to its root ViewGroup.
  • Do not pass in true for a View that has already been attached to a ViewGroup.
  • Custom Views are a good use case to pass in true for attachToRoot .

Juan Pablo Claude

During his tenure at BNR, Juan Pablo has taught bootcamps on macOS development, iOS development, Python, and Django. He has also participated in consulting projects in those areas. Juan Pablo is currently a Director of Technology focusing mainly on managing engineers and his interests include Machine Learning and Data Science.

Schedule a call today! Our team of nerds are ready to help

Splash Screens: The Final Right Way

The impending release of Android 12 brings with it a group of new APIs for Android developers to learn and play with. Being a good citizen.

Coming soon: Kotlin Programming: The Big Nerd Ranch Guide, Second Edition

Kotlin has been changing at a steady pace over the years, and more and more developers are choosing Kotlin as their language of choice.

Using StateFlow over LiveData for end-to-end operations

Observing data asynchronously is such a core skill for mobile developers that you may imagine Android has a long-established set of simple APIs in.

Источник

Оцените статью