Что означает параметр attachToRoot для LayoutInflater?

201

LayoutInflater.inflateДокументация не совсем ясно мне о цели attachToRootпараметра.

attachToRoot : должна ли завышенная иерархия быть привязана к корневому параметру? Если false, root используется только для создания правильного подкласса LayoutParams для корневого представления в XML.

Может кто - то пожалуйста , объясните более подробно, а именно то , что вид корня, и , возможно , показать пример изменения в поведении между trueи falseценностей?

Джефф Аксельрод
источник
1
Связанный: Понимание LayoutInflater
blahdiblah
Дубликат: stackoverflow.com/questions/22326314/…
mikebabcock

Ответы:

157

СЕЙЧАС ИЛИ НЕ СЕЙЧАС

Основное различие между «третьим» параметром attachToRoot, равным true или false, заключается в следующем.

Когда вы ставите attachToRoot

true: добавить дочернее представление к родителю ПРЯМО СЕЙЧАС
false: добавить дочернее представление к родителю НЕ СЕЙЧАС .
Добавьте это позже. `

Когда это позже ?

Это позже, когда вы используете, например, для parent.addView(childView)

Распространенным заблуждением является то, что если параметр attachToRoot имеет значение false, то дочернее представление не будет добавлено к родительскому. НЕПРАВИЛЬНО
В обоих случаях дочерний вид будет добавлен в parentView. Это просто вопрос времени .

inflater.inflate(child,parent,false);
parent.addView(child);   

эквивалентно

inflater.inflate(child,parent,true);

БОЛЬШОЕ НЕТ-НЕТ
Вы никогда не должны передавать attachToRoot как true, если вы не несете ответственности за добавление дочернего представления к родительскому.
Например, при добавлении фрагмента

public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle bundle)
  {
        super.onCreateView(inflater,parent,bundle);
        View view = inflater.inflate(R.layout.image_fragment,parent,false);
        .....
        return view;
  }

если вы передадите третий параметр как true, вы получите IllegalStateException из-за этого парня.

getSupportFragmentManager()
      .beginTransaction()
      .add(parent, childFragment)
      .commit();

Поскольку вы уже добавили дочерний фрагмент в onCreateView () по ошибке. Вызов add скажет вам, что дочернее представление уже добавлено к родителю. Отсюда IllegalStateException .
Здесь вы не несете ответственности за добавление childView, за это отвечает FragmentManager. Так что всегда передавайте false в этом случае.

ПРИМЕЧАНИЕ: я также читал, что parentView не получит childView touchEvents, если attachToRoot имеет значение false. Но я не проверял это все же.

Рохит Сингх
источник
6
Очень полезно, особенно часть, касающаяся FragmentManager, спасибо!
CybeX
94

Если задано значение true, то при накачке макета он будет автоматически добавлен в иерархию представлений группы ViewGroup, указанной во втором параметре, как дочерний элемент. Например, если корневой параметр былLinearLayout то ваше завышенное представление будет автоматически добавлено как потомок этого представления.

Если установлено значение false, то ваш макет будет раздут, но не будет привязан ни к какому другому макету (поэтому он не будет отображаться, получать сенсорные события и т. Д.).

Джозеф Эрл
источник
17
Я запутался. Я получаю «Указанный ребенок уже имеет родительскую ошибку» , пока я не прочитал этот ответ , который направил мой использовать falseдля attachToRootво время моего фрагмента onCreateView. Это решило проблему , и все же макет фрагмента виден и активен, несмотря на ваш ответ. Что происходит здесь?
Джефф Аксельрод
67
Потому что фрагмент автоматически присоединяет макет, возвращенный из onCreateView. Так что, если вы присоедините его вручную в onCreateView, то ваше представление будет привязано к 2 родителям (что приводит к указанной вами ошибке).
Джозеф Эрл
11
Я немного запутался здесь, @JosephEarl, вы сказали, что если установлено значение true, представление присоединяется ко 2-му параметру, который является container, но затем вы говорите, что фрагмент автоматически присоединяется из onCreateView(), так что, насколько я понимаю, третий параметр бесполезен и должен быть установлен falseвсегда?
unmultimedio
5
Вы возвращаете представление в oncreateview, затем оно автоматически присоединяется. Если для параметра attach установлено значение true, выдается ошибка. Однако, когда вы раздуваете представление в автономной ситуации, вы можете выбрать автоматическое присоединение вида к его контейнеру, установив значение true. Хотя я вряд ли когда-либо установлю истину, так как всегда добавляю точку зрения сама.
морозный
7
@unmultimedio бесполезно только для корневого представления, возвращаемого onCreateView. Если вы надуваете дополнительные макеты в этом корневом представлении или надуваете в другом контексте (например, в действии), тогда это полезно.
Джозеф Эрл
36

Кажется, что в ответах много текста, но нет кода, поэтому я решил восстановить этот старый вопрос на примере кода, в нескольких ответах, которые упоминали люди:

Если задано значение true, то при накачке макета он будет автоматически добавлен в иерархию представлений группы ViewGroup, указанной во втором параметре, как дочерний элемент.

Что это на самом деле означает в коде (что понимают большинство программистов):

public class MyCustomLayout extends LinearLayout {
    public MyCustomLayout(Context context) {
        super(context);
        // Inflate the view from the layout resource and pass it as child of mine (Notice I'm a LinearLayout class).

        LayoutInflater.from(context).inflate(R.layout.child_view, this, true);
    }
}

Обратите внимание, что предыдущий код добавляет макет R.layout.child_viewкак дочерний элемент MyCustomLayoutиз-за attachToRootparam trueи назначает параметры макета родительского элемента точно таким же образом, как если бы я использовал addViewпрограммно, или как если бы я делал это в xml:

<LinearLayout>
   <View.../>
   ...
</LinearLayout>

Следующий код объясняет сценарий при передаче attachRootкак false:

LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setLayoutParams(new LayoutParams(
    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
linearLayout.setOrientation(LinearLayout.VERTICAL);
    // Create a stand-alone view
View myView = LayoutInflater.from(context)
    .inflate(R.layout.ownRootView, null, false);
linearLayout.addView(myView);

В предыдущем коде вы указали, что хотите myViewбыть его собственным корневым объектом и не привязывать его к какому-либо родительскому объекту, позже мы добавили его как часть, LinearLayoutно на мгновение это было автономное (не родительское) представление.

То же самое происходит с фрагментами, вы можете добавить их в уже существующую группу и стать ее частью, или просто передать параметры:

inflater.inflate (R.layout.fragment, null, false);

Чтобы указать, что это будет его собственный корень.

Мартин Касарес
источник
1
Из всех, это было самым полезным.
Вахиб Ул Хак
26

Документации и двух предыдущих ответов должно быть достаточно, только некоторые мысли от меня.

inflateМетод используется для накачивания файлов макета. С этими раздутыми макетами у вас есть возможность прикрепить их непосредственно к родителюViewGroup или просто накачать иерархию представления из этого файла макета и работать с ним вне обычной иерархии представления.

В первом случае attachToRootпараметр должен быть установлен в true(или намного проще использовать inflateметод, который принимает файл макета и родительский корень ViewGroup(не null)). В этом случае Viewвозвращается просто то, ViewGroupчто было передано в методе,ViewGroup к которому будет добавлена ​​раздутая иерархия представления.

Для второго варианта возвращается Viewкорень ViewGroupиз файла макета. Если вы помните наше последнее обсуждение вопроса о include-mergeпаре, это одна из причин mergeограничения (когда файл макета с mergeименем root завышен, необходимо указать родительский элемент и attachedToRootустановить его значение true). Если у вас был файл макета с корневым mergeтегом и он attachedToRootбыл установлен, falseто inflateметод не будет ничего возвращать, поскольку mergeне имеет эквивалента. Также, как сказано в документации, inflateверсия с attachToRootустановленным на от родительского. Это важно в некоторых случаях, наиболее заметно с потомками , подкласса , для которогоfalse важна значением, поскольку вы можете создать иерархию представлений с правильнымиLayoutParamsAdapterViewViewGroupaddView()набор методов не поддерживается. Я уверен, что вы помните, используя эту строку в getView()методе:

convertView = inflater.inflate(R.layout.row_layout, parent, false);

Эта строка гарантирует, что накачанный R.layout.row_layoutфайл имеет правильное значение LayoutParamsиз AdapterViewподкласса, установленного в его корне ViewGroup. Если вы этого не сделаете, у вас могут возникнуть проблемы с файлом макета, если корнем был a RelativeLayout. TableLayout/TableRowТакже есть специальное и важно , LayoutParamsи вы должны убедиться , что мнения в них правильно LayoutParams.

Luksprog
источник
18

Я сам был также путать о том, что была реальная цель attachToRootв inflateметоде. После небольшого изучения пользовательского интерфейса я наконец получил ответ:

родитель:

в данном случае это виджет / макет, который окружает объекты представления, которые вы хотите накачать с помощью findViewById ().

attachToRoot:

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

Надеюсь, что это устраняет путаницу

Умер Фарук
источник
Ваш
11

Я написал этот ответ, потому что даже пройдя несколько страниц StackOverflow, я не смог четко понять, что означает attachToRoot. Ниже приведен метод inflate () в классе LayoutInflater.

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

Посмотрите на activity_main.xml файл, button.xml расположение и MainActivity.java файла , который я создал.

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

button.xml

<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    LayoutInflater inflater = getLayoutInflater();
    LinearLayout root = (LinearLayout) findViewById(R.id.root);
    View view = inflater.inflate(R.layout.button, root, false);
}

Когда мы запустим код, мы не увидим кнопку в макете. Это потому, что наш макет кнопки не добавляется в основной макет действия, так как attachToRoot имеет значение false.

LinearLayout имеет метод addView (View view), который можно использовать для добавления Views в LinearLayout. Это добавит макет кнопки в основной макет действия и сделает кнопку видимой при запуске кода.

root.addView(view);

Давайте удалим предыдущую строку и посмотрим, что произойдет, если мы установим attachToRoot в значение true.

View view = inflater.inflate(R.layout.button, root, true);

Снова мы видим, что расположение кнопок видно. Это связано с тем, что attachToRoot напрямую прикрепляет раздутый макет к указанному родителю. Который в данном случае является корневым LinearLayout. Здесь нам не нужно добавлять представления вручную, как в предыдущем случае с помощью метода addView (View view).

Почему люди получают IllegalStateException, если для attachToRoot установлено значение true для фрагмента.

Это связано с тем, что для фрагмента вы уже указали, где разместить макет фрагмента в файле активности.

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .add(R.id.root, fragment)
    .commit();

Добавить (интермедиат родитель, фрагмент фрагмент) добавляет фрагмент , который имеет его расположение к родительскому макету. Если мы установим attachToRoot как true, вы получите IllegalStateException: указанный дочерний элемент уже имеет родителя. Поскольку макет фрагмента уже добавлен в родительский макет в методе add ().

Вы всегда должны передавать false для attachToRoot, когда вы раздуваете фрагменты. FragmentManager является задачей добавления, удаления и замены фрагментов.

Вернемся к моему примеру. Что делать, если мы делаем оба.

View view = inflater.inflate(R.layout.button, root, true);
root.addView(view);

В первой строке LayoutInflater присоединяет макет кнопки к корневому макету и возвращает объект View, который содержит тот же макет кнопки. Во второй строке мы добавляем тот же объект View в родительский корневой макет. Это приводит к тому же исключению IllegalStateException, которое мы видели с фрагментами (у указанного дочернего элемента уже есть родительский элемент).

Имейте в виду, что существует другой перегруженный метод inflate (), который по умолчанию устанавливает attachToRoot в значение true.

View inflate (int resource, ViewGroup root)
capt.swag
источник
Простое и понятное объяснение, именно то, что я искал!
flyingAssistant
10

Существует много путаницы в этой теме из-за документации для метода inflate ().

В общем, если для attachToRoot установлено значение true, то файл макета, указанный в первом параметре, раздувается и присоединяется к ViewGroup, указанной во втором параметре, в тот момент времени. Когда attachToRoot имеет значение false, файл макета из первого параметра раздувается и возвращается как вид, и любое вложение вида происходит в другое время.

Это, вероятно, мало что значит, если вы не видите много примеров. При вызове LayoutInflater.inflate () внутри метода onCreateView фрагмента вы захотите передать false для attachToRoot, потому что действие, связанное с этим фрагментом, фактически отвечает за добавление представления этого фрагмента. Если вы надуваете вручную и добавляете представление в другое представление в более поздний момент времени, например, с помощью метода addView (), вы захотите передать false для attachToRoot, потому что вложение приходит позднее.

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

https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/

seanjfarrell
источник
4

attachToRoot значение true означает inflatedView объект будет добавлен в иерархию родительского представления. Таким образом, пользователи могут «видеть» и воспринимать события касания (или любые другие операции пользовательского интерфейса). В противном случае, он только что был создан, не добавлен в какую-либо иерархию представления и, следовательно, не может быть виден или обрабатывать события касания.

Для разработчиков iOS, впервые знакомых с Android, attachToRootзначение true означает, что вы вызываете этот метод:

[parent addSubview:inflatedView];

Если вы пойдете дальше, вы можете спросить: зачем мне передавать родительский вид, если я настроен attachToRootна false? Это связано с тем, что корневому элементу в вашем XML-дереве требуется родительское представление для вычисления некоторых параметров LayoutParams (например, match parent).

Элстон
источник
0

Когда вы определяете родителя, attachToRoot определяет, хотите ли вы, чтобы инфлятор действительно прикрепил его к родителю или нет. В некоторых случаях это вызывает проблемы, как в ListAdapter, это вызовет исключение, потому что список пытается добавить представление в список, но он говорит, что он уже присоединен. В других случаях, когда вы просто надуваете представление для добавления в Activity, это может быть удобно и сэкономит вам строку кода.

CaseyB
источник
1
не дает четкой картины, которую должен дать хороший ответ.
Prakhar1001
0

Например, у нас есть ImageView, а LinearLayoutи а RelativeLayout. LinearLayout является дочерним элементом RelativeLayout. Иерархия Представлений будет.

RelativeLayout
           ------->LinearLayout

и у нас есть отдельный файл макета для ImageView

image_view_layout.xml

Присоединить к корню:

//here container is the LinearLayout

    View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
  1. Здесь v содержит ссылку на макет контейнера, т.е. LinearLayout.and, если вы хотите установить такие параметры, как setImageResource(R.drawable.np);ImageView, вам нужно будет найти его по ссылке родителя, т.е.view.findById()
  2. Родителем v будет FrameLayout.
  3. LayoutParams будет иметь FrameLayout.

Не привязывать к корню:

//here container is the LinearLayout
    View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
  1. Здесь v содержит макет контейнера без ссылки, а прямую ссылку на ImageView, которая является раздутой, так что вы можете установить его параметры как view.setImageResource(R.drawable.np);без ссылки подобно findViewById. Но контейнер указан так, что ImageView получает LayoutParams контейнера, так что вы можете сказать, что ссылка на контейнер только для LayoutParams и ничего больше.
  2. поэтому в конкретном случае Parent будет нулевым.
  3. LayoutParams будет иметь LinearLayout.
Фейсал Насир
источник
0

attachToRoot Установите в true:

Если для attachToRoot установлено значение true, то файл макета, указанный в первом параметре, раздувается и присоединяется к ViewGroup, указанной во втором параметре.

Представьте, что мы указали кнопку в файле макета XML с шириной макета и высотой макета, равными match_parent.

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

Теперь мы хотим программно добавить эту кнопку в LinearLayout внутри фрагмента или действия. Если наш LinearLayout уже является переменной-членом mLinearLayout, мы можем просто добавить кнопку со следующим:

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

Мы указали, что хотим накачать кнопку из файла ресурсов макета; Затем мы сообщаем LayoutInflater, что хотим присоединить его к mLinearLayout. Наши параметры макета соблюдаются, потому что мы знаем, что Button добавляется в LinearLayout. Тип параметров макета кнопки должен быть LinearLayout.LayoutParams.

attachToRoot Установите в false (не обязательно использовать false)

Если для attachToRoot установлено значение false, то файл макета, указанный в первом параметре, раздувается и не присоединяется к ViewGroup, указанной во втором параметре, но это раздутое представление получает родительский объект LayoutParams, который позволяет этому представлению правильно вписаться в родительский объект .


Давайте посмотрим, когда вы захотите установить для attachToRoot значение false. В этом случае представление, указанное в первом параметре inflate (), не прикреплено к ViewGroup во втором параметре в данный момент времени.

Вспомните наш пример Button из предыдущего, где мы хотим прикрепить пользовательскую кнопку Button из файла макета к mLinearLayout. Мы по-прежнему можем присоединить нашу кнопку к mLinearLayout, передав false для attachToRoot - мы просто вручную добавим его самостоятельно.

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

Эти две строки кода эквивалентны тому, что мы написали ранее в одной строке кода, когда мы передали true для attachToRoot. Передавая false, мы говорим, что пока не хотим присоединять наш View к корневой ViewGroup. Мы говорим, что это произойдет в другой момент времени. В этом примере другим моментом времени является просто метод addView (), который используется сразу после инфляции.

Ложный пример attachToRoot требует немного больше работы, когда мы вручную добавляем View в ViewGroup.

attachToRoot Устанавливается в false (false является обязательным).

Когда надувать и возвращать представление фрагмента в onCreateView (), обязательно передайте false для attachToRoot. Если вы передадите значение true, вы получите исключение IllegalStateException, поскольку указанный дочерний элемент уже имеет родителя. Вы должны были указать, где ваш фрагмент будет возвращен в вашу активность. FragmentManager является задачей добавления, удаления и замены фрагментов.

FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment =  fragmentManager.findFragmentById(R.id.root_viewGroup);

if (fragment == null) {
fragment = new MainFragment();
fragmentManager.beginTransaction()
    .add(R.id.root_viewGroup, fragment)
    .commit();
}

Контейнер root_viewGroup, в котором будет храниться ваш фрагмент в вашей активности, - это параметр ViewGroup, данный вам в onCreateView () вашего фрагмента. Это также ViewGroup, которую вы передаете в LayoutInflater.inflate (). Однако FragmentManager будет обрабатывать присоединение вашего фрагмента к этой ViewGroup. Вы не хотите прикрепить его дважды. Установите attachToRoot в false.

public View onCreateView(LayoutInflater inflater, ViewGroup  parentViewGroup, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout,     parentViewGroup, false);

return view;
}

Почему нам в первую очередь предоставляется родительская ViewGroup нашего фрагмента, если мы не хотим присоединять ее в onCreateView ()? Почему метод inflate () запрашивает корневую ViewGroup?

Оказывается, что даже когда мы не сразу добавляем наш недавно надутый View к его родительской ViewGroup, мы все равно должны использовать LayoutParams родителя, чтобы новый View определял его размер и положение всякий раз, когда он в конечном итоге присоединяется.

Ссылка: https://youtu.be/1Y0LlmTCOkM?t=409

Utshaw
источник
0

Просто поделюсь некоторыми моментами, с которыми я столкнулся, работая над этой темой,

В дополнение к принятому ответу я хочу несколько моментов, которые могут быть полезны.

Таким образом, когда я использовал attachToRoot как true, возвращаемое представление имело тип ViewGroup, т.е. корневую ViewGroup родителя, которая была передана в качестве параметра для метода inflate (layoutResource, ViewGroup, attachToRoot) , а не типа макета, который был передан, но для attachToRoot. как false мы получаем тип, возвращаемый функцией корневого ViewGroup этого layoutResource .

Позвольте мне объяснить на примере:

Если у нас есть LinearLayout в качестве корневого макета, а затем мы хотим добавить TextView в него через функцию inflate .

затем с помощью attachToRoot в качестве истинной функции раздувать возвращает вид типа LinearLayout

в то время как на использование attachToRoot в качестве ложной функции раздувать возвращает вид типа TextView

Надеюсь, что это открытие поможет ...

ХАРИС УМАЙД
источник