Що означає параметр LayoutInflater attachToRoot?


201

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

attachToRoot : чи слід приєднати завищену ієрархію до кореневого параметра? Якщо значення false, root використовується лише для створення правильного підкласу LayoutParams для кореневого перегляду в XML.

Невже хтось може пояснити більш детально, зокрема, що таке кореневий погляд, а може, показати приклад зміни в поведінці між значеннями trueта falseзначеннями?



Відповіді:


157

ЗАРАЗ АБО НЕ ЗАРАЗ

Основна відмінність параметра "третій" attachToRoot від істинного або хибного полягає в цьому.

Коли ви кладете attachToRoot

вірно: додайте дочірнє представлення до батьківського права ЗАРАЗ СЛІД
помилкове: додайте подання дитини до батьківського НЕ ЗАРАЗ .
Додайте його пізніше. `

Коли це пізніше ?

Це пізніше, коли ви використовуєте для напр parent.addView(childView)

Поширене помилкове уявлення : якщо параметр attachToRoot хибний, дочірній вигляд не буде додано до батьківського. WRONG
В обох випадках дочірнє подання буде додано до parentView. Це лише питання часу .

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

еквівалентно

inflater.inflate(child,parent,true);

ВЕЛИКИЙ НІ-НІ
Ніколи не слід передавати attachToRoot як істинне, коли ви не несете відповідальності за додавання дочірнього виду до батьків.
Наприклад, додаючи фрагмент

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;
  }

якщо ви передасте третій параметр як істинний, ви отримаєте IllegalStateException через цього хлопця.

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

Оскільки ви вже додали дочірній фрагмент в onCreateView () помилково. Виклик додати скаже вам, що дочірній вигляд уже додано до батьківського Hence IllegalStateException .
Тут ви не несете відповідальності за додавання childView, відповідає FragmentManager. Тому завжди передайте помилкове в цьому випадку.

ПРИМІТКА. Я також читав, що parentView не отримає childView touchEvents, якщо attachToRoot помилкове. Але я цього не перевіряв.


6
Дуже корисно, особливо частина щодо FragmentManager, дякую!
CybeX

94

Якщо встановлено значення true, тоді, коли ваш макет буде завищений, він буде автоматично доданий до ієрархії перегляду ViewGroup, визначеної у 2-му параметрі як дочірня. Наприклад, якщо кореневим параметром був LinearLayoutтоді ваш завищений вигляд буде автоматично додано як дочірнє зображення цього подання.

Якщо встановлено значення false, ваш макет буде завищений, але він не буде приєднаний до будь-якого іншого макета (тому його не буде намальовано, отримуйте сенсорні події тощо).


17
Я збентежений. Я отримую «Зазначений дитина вже має батьківську помилку» , поки я не прочитав цю відповідь , який направив мій використовувати falseдля attachToRootпід час мого фрагмента onCreateView. Це вирішило проблему , і все ж макет фрагмента видно і активний, не дивлячись на вашу відповідь. Що відбувається тут?
Джефф Аксельрод

67
Оскільки фрагмент автоматично приєднує макет, повернутий з onCreateView. Тож якщо ви додаєте його вручну в onCreateView, ваш погляд приєднується до двох батьків (що призводить до вказаної вами помилки).
Джозеф Граф

11
Я трохи розгублений тут, @JosephEarl ви сказали, якщо встановлено true, подання додається до другого параметра, який є container, але тоді ви говорите, що фрагмент автоматично додається з onCreateView(), тому , наскільки я розумію, третій параметр марний і його слід встановити falseзавжди?
unmultimedio

5
Ви повертаєте вигляд у режимі oncreateview, який потім додається автоматично. Якщо встановити вкладку true, помилка буде видана. Однак, коли ви надуваєте подання в окремій ситуації, ви можете автоматично приєднати подання до його контейнера, встановивши значення true. Я навряд чи коли-небудь ставлюсь істинним, хоча, як завжди додаю погляд, сам.
frostymarvelous

7
@unmultimedio це марно лише для кореневого виду, поверненого onCreateView. Якщо ви надуваєте подальші макети в цьому кореневому вікні, або ви надуваєте в іншому контексті (наприклад, у діяльності), це корисно.
Джозеф Граф

36

Здається, що у відповідях багато тексту, але немає коду, тому я вирішив відновити це старе питання на прикладі коду, у кількох відповідях люди згадували:

Якщо встановлено значення true, тоді, коли ваш макет завищений, він буде автоматично доданий до ієрархії перегляду ViewGroup, зазначеної у 2-му параметрі як дочірня.

Що насправді означає в коді (що розуміють більшість програмістів):

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через attachToRootпарам є 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коренем надутий, ви повинні надати батьківський і attachedToRootповинен бути встановлений на true). Якщо у вас був файл макета з кореневим mergeтегом і attachedToRootбуло встановлено значення, falseто inflateметоду повернути mergeнічого не буде, оскільки не має еквівалента. Також, як йдеться в документації, важлива inflateверсія з attachToRootвстановленим falseзначенням, оскільки ви можете створити ієрархію перегляду з правильноюLayoutParamsвід батьківського. Це важливо в деяких випадках, найбільш помітне для дітей AdapterViewпідкласу ViewGroup, для якого встановлені addView()методи не підтримуються. Я впевнений, що ви пам'ятаєте використання цього рядка в getView()методі:

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

Ця лінія гарантує , що роздутий R.layout.row_layoutфайл має правильний LayoutParamsз AdapterViewбезлічі підкласів на корені ViewGroup. Якщо ви цього не робите, у вас може виникнути проблеми з файлом компонування, якщо корінь був RelativeLayout. TableLayout/TableRowТакож є спеціальне і важливо , LayoutParamsі ви повинні переконатися , що думки в них правильно LayoutParams.


18

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

батьків:

у цьому випадку - віджет / макет, що оточує об’єкти подання, які ви хочете надути за допомогою findViewById ().

attachToRoot:

приєднує погляди до свого батьківського (включає їх у батьківську ієрархію), тому будь-яке торкання події, яке отримує представлення, також буде перенесено на батьківський вигляд. Тепер це до батьків, чи хоче він розважати цими подіями чи ігнорувати їх. якщо встановлено значення false, вони не додаються як безпосередні діти батьків і батьків не отримує жодних дотикових подій з подань.

Сподіваюся, це очистить плутанину


Ти відповідь вже представлений тут: stackoverflow.com/questions/22326314 / ...
неоновий Warge

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 встановлена ​​на хибну.

У LinearLayout є метод addView (Перегляд подання), який можна використовувати для додавання переглядів у LinearLayout. Це додасть макет кнопки до макета основної діяльності та зробить кнопку видимою під час запуску коду.

root.addView(view);

Видалимо попередній рядок і подивимося, що станеться, коли ми встановимо attachToRoot як істинне.

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

Знову ми бачимо, що видно макет кнопки. Це пов’язано з тим, що attachToRoot безпосередньо приєднує завищений макет до вказаного батька. Який у цьому випадку є кореневим LinearLayout. Тут нам не потрібно додавати представлення вручну, як це було в попередньому випадку методом addView (View view).

Чому люди отримують IllegalStateException під час встановлення attachToRoot як істинного для фрагмента.

Це тому, що для фрагменту ви вже вказали, де розмістити макет фрагмента у вашому файлі активності.

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

Додати (интермедиат батько, фрагмент фрагмент) додає фрагмент , який має його розташування до батьківського макету. Якщо ми встановимо attachToRoot як істинне, ви отримаєте IllegalStateException: У вказаної дитини вже є батько. Оскільки макет фрагмента вже доданий до батьківського макета методом add ().

Ви завжди повинні передавати помилку для attachToRoot, коли ви надуваєте фрагменти. Завдання FragmentManager - додавати, видаляти та замінювати фрагменти.

Назад до мого прикладу. Що робити, якщо ми робимо обидва.

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

У першому рядку LayoutInflater приєднує макет кнопки до кореневого макета та повертає об’єкт View із таким же макетом кнопки. У другому рядку ми додаємо той самий об’єкт View у батьківський макет кореня. Це призводить до того ж IllegalStateException, який ми бачили з фрагментами (у вказаної дитини вже є батько).

Майте на увазі, що існує ще один перевантажений метод надуття (), який встановлює attachToRoot як істинний за замовчуванням.

View inflate (int resource, ViewGroup root)

Просте і зрозуміле пояснення, саме те, що я шукав!
політ Помічник

10

На цю тему існує велика плутанина через документацію щодо методу inflate ().

Загалом, якщо приєднати до параметра trueToRoot значення true, то файл макета, зазначений у першому параметрі, надувається та приєднується до ViewGroup, зазначеного у другому параметрі на той момент. Коли falseToRoot помилковий, файл макета з першого параметра надувається і повертається у вигляді View, а будь-яке вкладення View відбувається в інший час.

Це, мабуть, не означає багато, якщо ви не бачите багато прикладів. Викликаючи LayoutInflater.inflate () всередині методу onCreateView фрагмента, ви захочете передати помилкове значення для attachToRoot, оскільки діяльність, пов'язана з цим фрагментом, насправді відповідає за додавання виду цього фрагмента. Якщо ви вручну надуваєте та додаєте Погляд до іншого перегляду в якийсь пізній момент часу, наприклад, за допомогою методу addView (), вам потрібно буде передати помилку на falseToRoot, оскільки вкладення надходить у більш пізній час.

Про декілька інших унікальних прикладів, що стосуються діалогів та користувацьких переглядів, ви можете прочитати в публікації блогу, яку я писав саме на цю тему.

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


4

attachToRootвстановити значення true означає, що inflatedViewзаповіт буде доданий до ієрархії батьківського подання. Таким чином, користувачі можуть "бачити" та відчувати сенсорні події (або будь-які інші операції з користувальницьким інтерфейсом) користувачами. В іншому випадку він просто створений, не доданий до жодної ієрархії перегляду, і тому його не можна побачити або обробляти подій дотику.

Для розробників iOS, які не користуються Android, attachToRootвстановлене значення true означає, що ви називаєте цей метод:

[parent addSubview:inflatedView];

Якщо ви йдете далі, ви можете запитати: Навіщо мені передати батьківський погляд, якщо я встановив attachToRootце false? Це тому, що кореневому елементу у вашому XML-дереві потрібен батьківський вигляд, щоб обчислити деякі схеми LayoutParams (наприклад, батьківський збіг).


0

Коли ви визначаєте батько, attachToRoot визначає, чи хочете ви, щоб інфлятор насправді приєднав його до батьків чи ні. У деяких випадках це спричиняє проблеми, наприклад, у ListAdapter це спричинить виняток, оскільки список намагається додати перегляд до списку, але він говорить про те, що він уже додається. В іншому випадку, коли ви просто надуваєте подання, щоб додати його до діяльності, це може бути зручно і заощадити рядок коду.


1
не дає чіткої картини, яку має дати хороша відповідь.
Prakhar1001

0

Наприклад, у нас є a ImageView, a LinearLayoutі a RelativeLayout. LinearLayout - це дитина відносної схеми. ієрархія перегляду буде.

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.і якщо ви хочете встановити параметри типу 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. тому, зокрема, Батько буде недійсним.
  3. Параметри LayoutParams матимуть LinearLayout.

0

attachToRoot Встановити значення true:

Якщо функція attachToRoot встановлена ​​в істинне значення, то файл макета, зазначений у першому параметрі, надувається і додається до 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. Наші параметри компонування шануються, оскільки ми знаємо, що кнопка додається до LinearLayout. Тип парамерів макета кнопки повинен бути LinearLayout.LayoutParams.

attachToRoot Встановити значення false (не потрібно використовувати false)

Якщо функція attachToRoot встановлена ​​на false, то файл макета, зазначений у першому параметрі, надутий і не приєднаний до ViewGroup, зазначеної у другому параметрі, але цей завищений вигляд набуває батьківського LayoutParams, що дозволяє цьому представленню правильно відповідати батьківському.


Давайте подивимось, коли ви хочете встановити attachToRoot на false. У цьому сценарії Перегляд, зазначений у першому параметрі inflate (), не приєднаний до ViewGroup у другому параметрі в цей момент часу.

Згадаймо наш приклад Button з попереднього, де ми хочемо приєднати спеціальну кнопку з файлу макета до mLinearLayout. Ми все ще можемо приєднати нашу кнопку до mLinearLayout, передавши помилку false для attachToRoot - ми її просто вручну додаємо потім.

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

Ці два рядки коду еквівалентні тому, що ми писали раніше в одному рядку коду, коли ми передавали true для attachToRoot. Передаючи помилку, ми говоримо, що поки що не хочемо прикріплювати наш погляд до кореневої ViewGroup. Ми говоримо, що це станеться в якийсь інший момент часу. У цьому прикладі іншим моментом є просто метод addView (), який використовується безпосередньо під інфляцією.

Помилковий приклад attachToRoot вимагає трохи більше роботи, коли ми вручну додаємо View в ViewGroup.

attachToRoot Встановити значення false (обов'язкове хибне значення)

При надуванні та поверненні перегляду фрагмента в onCreateView (), обов'язково переведіть помилку на falseToRoot. Якщо ви перейдете в 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 обробляє додавання перегляду вашого фрагмента до цієї групи перегляду. Ви не хочете прикріплювати її двічі. Встановіть falseToRoot на значення 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?

Виявляється, навіть коли ми не додаємо одразу завищений перегляд до його батьківського ViewGroup, ми все одно повинні використовувати батьківський LayoutParams для того, щоб новий Перегляд визначав його розмір і положення, коли він врешті-решт додається.

Посилання: https://youtu.be/1Y0LlmTCOkM?t=409


0

Просто поділюсь деякими моментами, з якими я стикався під час роботи над цією темою,

На додаток до прийнятої відповіді, я хочу до деяких моментів, які можуть допомогти.

Отже, коли я використовував attachToRoot як істинний, вигляд, який повернувся, був типу ViewGroup, тобто кореня ViewGroup батька, переданого як параметр методу надуття (layoutResource, ViewGroup, attachToRoot) , а не типу макета, який було передано, а на attachToRoot як помилковий, ми отримуємо тип повернення функції кореня ViewGroup цього layoutResource .

Поясню на прикладі:

Якщо у нас є LinearLayout як кореневої макет , а потім ми хочемо додати TextView в нього через роздувати функції.

потім за допомогою функції attachToRoot як істинної функції надуття повертається вид типу LinearLayout

при використанні attachToRoot як помилкової функції надуття повертає вид типу TextView

Сподіваюся, що ця знахідка допоможе ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.