Que signifie le paramètre LayoutInflater attachToRoot?

201

La LayoutInflater.inflatedocumentation n'est pas exactement claire pour moi sur l'objectif du attachToRootparamètre.

attachToRoot : si la hiérarchie gonflée doit être attachée au paramètre racine? Si faux, root est uniquement utilisé pour créer la sous-classe correcte de LayoutParams pour la vue racine dans le XML.

Quelqu'un pourrait-il expliquer plus en détail, en particulier la vue racine, et peut-être montrer un exemple de changement de comportement entre les valeurs trueet false?

Jeff Axelrod
la source
1
Connexe: Donner du sens à LayoutInflater
blahdiblah

Réponses:

157

MAINTENANT OU PAS MAINTENANT

La principale différence entre le "troisième" paramètre attachToRoot étant vrai ou faux est la suivante.

Lorsque vous mettez attachToRoot

true: ajoutez la vue enfant au parent RIGHT NOW
false: ajoutez la vue enfant au parent NOT NOW .
Ajoutez-le plus tard. "

Quand est-ce plus tard ?

C'est plus tard lorsque vous utilisez par exemple parent.addView(childView)

Une idée fausse courante est que si le paramètre attachToRoot est faux, la vue enfant ne sera pas ajoutée au parent. FAUX
Dans les deux cas, la vue enfant sera ajoutée à parentView. Ce n'est qu'une question de temps .

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

est équivalent à

inflater.inflate(child,parent,true);

A BIG NO-NO
Vous ne devez jamais transmettre attachToRoot comme true lorsque vous n'êtes pas responsable de l'ajout de la vue enfant au parent.
Par exemple, lors de l'ajout d'un fragment

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

si vous passez le troisième paramètre comme vrai, vous obtiendrez IllegalStateException à cause de ce type.

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

Puisque vous avez déjà ajouté le fragment enfant dans onCreateView () par erreur. L'appel à add vous indiquera que la vue enfant est déjà ajoutée au parent, d'où IllegalStateException .
Ici, vous n'êtes pas responsable de l'ajout de childView, FragmentManager est responsable. Donc, passez toujours faux dans ce cas.

REMARQUE: j'ai également lu que parentView n'obtiendra pas childView touchEvents si attachToRoot est faux. Mais je ne l'ai pas testé cependant.

Rohit Singh
la source
6
Très utile, surtout la partie concernant le FragmentManager, merci!
CybeX
94

Si la valeur est true, lorsque votre mise en page est gonflée, elle sera automatiquement ajoutée à la hiérarchie de vues du ViewGroup spécifié dans le 2e paramètre en tant qu'enfant. Par exemple, si le paramètre racine était unLinearLayout votre vue gonflée sera automatiquement ajoutée en tant qu'enfant de cette vue.

S'il est défini sur false, votre mise en page sera gonflée mais ne sera attachée à aucune autre mise en page (elle ne sera donc pas dessinée, ne recevra pas d'événements tactiles, etc.).

Joseph Earl
la source
17
Je suis confus. Je recevais un « enfant spécifié a déjà une erreur de parent » jusqu'à ce que je lis cette réponse , qui me dirigeai à utiliser falsepour attachToRootpendant mon de Fragment onCreateView. Cela a résolu le problème et pourtant la mise en page du fragment est visible et active, en dépit de votre réponse. Qu'est - ce qui se passe ici?
Jeff Axelrod
67
Parce qu'un fragment attache automatiquement la mise en page renvoyée par onCreateView. Donc, si vous le joignez manuellement dans onCreateView, votre vue est attachée à 2 parents (ce qui produit l'erreur que vous mentionnez).
Joseph Earl
11
Je suis un peu confus ici, @JosephEarl vous avez dit que si défini sur true, la vue est attachée au 2ème paramètre qui est le container, mais vous dites ensuite que le fragment est automatiquement attaché à partir onCreateView(), donc à ma connaissance, le troisième paramètre est inutile et devrait être défini falsetoujours?
unmultimedio
5
Vous retournez la vue dans oncreateview, celle-ci est alors automatiquement jointe. Si vous définissez attach sur true, une erreur est levée. Toutefois, lorsque vous gonflez la vue dans une situation autonome, vous pouvez choisir d'attacher automatiquement la vue à son conteneur en définissant la valeur sur true. Je ne me suis presque jamais mis à vrai, car j'ajoute toujours la vue moi-même.
frostymarvelous
7
@unmultimedio c'est seulement inutile pour la vue racine retournée par onCreateView. Si vous gonflez d'autres mises en page dans cette vue racine, ou si vous gonflez dans un contexte différent (par exemple dans une activité), cela est utile.
Joseph Earl
36

On dirait beaucoup de texte dans les réponses mais pas de code, c'est pourquoi j'ai décidé de faire revivre cette vieille question avec un exemple de code, dans plusieurs réponses les gens ont mentionné:

Si la valeur est true, lorsque votre mise en page est gonflée, elle sera automatiquement ajoutée à la hiérarchie de vues du ViewGroup spécifié dans le 2e paramètre en tant qu'enfant.

Ce que cela signifie réellement dans le code (ce que la plupart des programmeurs comprennent) est:

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

Notez que le code précédent ajoute la mise R.layout.child_viewen page en tant qu'enfant à MyCustomLayoutcause de attachToRootparam is trueet affecte les paramètres de mise en page du parent exactement de la même manière que si j'utilisais par addViewprogramme, ou comme si je faisais cela en xml:

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

Le code suivant explique le scénario lors du passage en attachRoottant que 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);

Dans le code précédent, vous spécifiez que vous vouliez myViewêtre son propre objet racine et ne l'attachez à aucun parent, nous l'avons ajouté plus tard dans le cadre duLinearLayout mais pour un moment, il s'agissait d'une vue autonome (pas de parent).

La même chose se produit avec les fragments, vous pouvez les ajouter à un groupe déjà existant et en faire partie, ou simplement passer les paramètres:

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

Pour spécifier que ce sera sa propre racine.

Martin Cazares
la source
1
De tous, ce fut le plus utile.
Wahib Ul Haq
26

La documentation et les deux réponses précédentes devraient suffire, juste quelques réflexions de ma part.

La inflateméthode est utilisée pour gonfler les fichiers de mise en page. Avec ces dispositions gonflées, vous devez avoir la possibilité de les attacher directement à un parent ViewGroupou simplement de gonfler la hiérarchie de vues à partir de ce fichier de disposition et de travailler avec elle en dehors de la hiérarchie de vues normale.

Dans le premier cas, le attachToRootparamètre devra être défini sur true(ou beaucoup plus simple, utilisez la inflateméthode qui prend un fichier de mise en page et une racine parent ViewGroup(non null)). Dans ce cas, le Viewretour est simplement celui ViewGroupqui a été passé dans la méthode, leViewGroup à laquelle la hiérarchie de vue gonflée sera ajoutée.

Pour la deuxième option, le retour Viewest la racine ViewGroupdu fichier de mise en page. Si vous vous souvenez de notre dernière discussion de la include-mergequestion des paires, c'est l'une des raisons de la mergelimitation de (quand un fichier de mise en page avec mergecomme racine est gonflé, vous devez fournir un parent et attachedToRootêtre défini sur true). Si vous aviez un fichier de mise en page avec la racine une mergebalise et que vous l'aviez attachedToRootdéfini, falsela inflateméthode n'aura rien à retourner car elle mergen'a pas d'équivalent. De plus, comme le dit la documentation, la inflateversion avec attachToRootset to falseest importante car vous pouvez créer la hiérarchie des vues avec la bonneLayoutParamsdu parent. Ceci est important dans certains cas, surtout avec les enfants de AdapterView, une sous-classe de ViewGroup, pour laquelle lesaddView()l'ensemble de méthodes n'est pas pris en charge. Je suis sûr que vous vous souvenez d'avoir utilisé cette ligne dans la getView()méthode:

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

Cette ligne garantit que le R.layout.row_layoutfichier gonflé est correct à LayoutParamspartir de la AdapterViewsous - classe définie à sa racine ViewGroup. Si vous ne le faites pas, vous pourriez avoir des problèmes avec le fichier de mise en page si la racine était a RelativeLayout. Ils TableLayout/TableRowont également certains spéciaux et importants LayoutParamset vous devez vous assurer que les vues en eux sont correctes LayoutParams.

Luksprog
la source
18

Je me suis aussi confus au sujet de ce qui était le but réel de attachToRootdans la inflateméthode. Après un peu d'étude de l'interface utilisateur, j'ai finalement obtenu la réponse:

parent:

dans ce cas, c'est le widget / la présentation qui entoure les objets de vue que vous souhaitez gonfler à l'aide de findViewById ().

attachToRoot:

attache les vues à leur parent (les inclut dans la hiérarchie parent), de sorte que tout événement tactile que les vues reçoivent sera également transféré vers la vue parent. Maintenant, c'est au parent de décider s'il veut divertir ces événements ou les ignorer. s'ils sont définis sur false, ils ne sont pas ajoutés en tant qu'enfants directs du parent et le parent ne reçoit aucun événement tactile des vues.

J'espère que cela efface la confusion

Umer Farooq
la source
Votre réponse est déjà fournie ici: stackoverflow.com/questions/22326314/…
Neon Warge
11

J'ai écrit cette réponse parce que même après avoir parcouru plusieurs pages StackOverflow, je n'ai pas pu comprendre clairement ce que signifiait attachToRoot. Vous trouverez ci-dessous la méthode inflate () dans la classe LayoutInflater.

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

Jetez un oeil à activity_main.xml fichier, button.xml mise en page et le MainActivity.java fichier que j'ai créé.

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

Lorsque nous exécutons le code, nous ne voyons pas le bouton dans la présentation. Cela est dû au fait que la disposition de nos boutons n'est pas ajoutée à la disposition principale de l'activité car attachToRoot est défini sur false.

LinearLayout possède une méthode addView (View view) qui peut être utilisée pour ajouter des vues à LinearLayout. Cela ajoutera la disposition des boutons à la disposition principale de l'activité et rendra le bouton visible lorsque vous exécuterez le code.

root.addView(view);

Supprimons la ligne précédente et voyons ce qui se passe lorsque nous définissons attachToRoot sur true.

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

Encore une fois, nous voyons que la disposition des boutons est visible. En effet, attachToRoot attache directement la disposition gonflée au parent spécifié. Qui dans ce cas est root LinearLayout. Ici, nous n'avons pas à ajouter les vues manuellement comme nous l'avons fait dans le cas précédent avec la méthode addView (View view).

Pourquoi les gens obtiennent-ils IllegalStateException lorsqu'ils définissent attachToRoot comme true pour un fragment.

En effet, pour un fragment, vous avez déjà spécifié où placer la disposition de votre fragment dans votre fichier d'activité.

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

L' ajout (int parent, Fragment fragment) ajoute le fragment qui a sa disposition à la disposition parent. Si nous définissons attachToRoot sur true, vous obtiendrez IllegalStateException: l'enfant spécifié a déjà un parent. Puisque la disposition des fragments est déjà ajoutée à la disposition parent dans la méthode add ().

Vous devez toujours passer false pour attachToRoot lorsque vous gonflez des fragments. Le travail du FragmentManager consiste à ajouter, supprimer et remplacer des fragments.

Revenons à mon exemple. Et si nous faisons les deux.

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

Dans la première ligne, LayoutInflater attache la disposition des boutons à la disposition racine et renvoie un objet View qui contient la même disposition des boutons. Dans la deuxième ligne, nous ajoutons le même objet View à la disposition racine parent. Il en résulte la même exception IllegalStateException que nous avons vue avec les fragments (l'enfant spécifié a déjà un parent).

Gardez à l'esprit qu'il existe une autre méthode inflate () surchargée, qui définit attachToRoot comme true par défaut.

View inflate (int resource, ViewGroup root)
capt.swag
la source
Explication simple et claire, juste ce que je cherchais!
flyingAssistant
10

Il y a beaucoup de confusion sur ce sujet en raison de la documentation de la méthode inflate ().

En général, si attachToRoot est défini sur true, le fichier de disposition spécifié dans le premier paramètre est gonflé et attaché au ViewGroup spécifié dans le deuxième paramètre à ce moment. Lorsque attachToRoot est faux, le fichier de disposition du premier paramètre est gonflé et renvoyé en tant que vue et toute pièce jointe de vue se produit à un autre moment.

Cela ne signifie probablement pas grand-chose, sauf si vous voyez beaucoup d'exemples. Lorsque vous appelez LayoutInflater.inflate () à l'intérieur de la méthode onCreateView d'un fragment, vous voudrez passer false pour attachToRoot car l'activité associée à ce fragment est en fait responsable de l'ajout de la vue de ce fragment. Si vous gonflez et ajoutez manuellement une vue à une autre vue ultérieurement, comme avec la méthode addView (), vous souhaiterez passer false pour attachToRoot car la pièce jointe arrive à un moment ultérieur.

Vous pouvez lire plusieurs autres exemples uniques concernant les dialogues et les vues personnalisées sur un article de blog que j'ai écrit sur ce sujet.

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

seanjfarrell
la source
4

attachToRootdéfini sur true signifie que le inflatedViewsera ajouté à la hiérarchie de la vue parent. Ainsi, les utilisateurs peuvent éventuellement "voir" et détecter des événements tactiles (ou toute autre opération d'interface utilisateur). Sinon, il vient d'être créé, n'a été ajouté à aucune hiérarchie de vue et ne peut donc pas être vu ou gérer les événements tactiles.

Pour les développeurs iOS nouveaux sur Android, attachToRootla valeur true signifie que vous appelez cette méthode:

[parent addSubview:inflatedView];

Si vous allez plus loin, vous pourriez vous demander: pourquoi devrais-je passer la vue des parents si je mets attachToRootà false? C'est parce que l'élément racine de votre arborescence XML a besoin de la vue parent pour calculer certains LayoutParams (comme match parent).

Alston
la source
0

Lorsque vous définissez le parent, attachToRoot détermine si vous souhaitez que le gonfleur l'attache réellement au parent ou non. Dans certains cas, cela provoque des problèmes, comme dans un ListAdapter, il provoquera une exception car la liste essaie d'ajouter la vue à la liste mais elle dit qu'elle est déjà attachée. Dans d'autres cas où vous gonflez simplement la vue vous-même pour l'ajouter à une activité, cela pourrait être pratique et vous faire économiser une ligne de code.

CaseyB
la source
1
ne donne pas une image claire qu'une bonne réponse devrait fournir.
Prakhar1001
0

Par exemple, nous avons un ImageView, un LinearLayoutet un RelativeLayout. LinearLayout est l'enfant de RelativeLayout. la hiérarchie des vues sera.

RelativeLayout
           ------->LinearLayout

et nous avons un fichier de mise en page distinct pour ImageView

image_view_layout.xml

Attacher à la racine:

//here container is the LinearLayout

    View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
  1. Ici, v contient la référence de la disposition du conteneur, c'est-à-dire LinearLayout.et si vous voulez définir les paramètres comme setImageResource(R.drawable.np);ImageView, vous devrez le trouver par la référence du parent, c'est-à-direview.findById()
  2. Le parent de v sera le FrameLayout.
  3. LayoutParams sera de FrameLayout.

Ne pas attacher à la racine:

//here container is the LinearLayout
    View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
  1. Ici, v contient la disposition du conteneur sans référence mais une référence directe à ImageView qui est gonflé afin que vous puissiez définir ses paramètres comme view.setImageResource(R.drawable.np);sans arbitrer comme findViewById. Mais le conteneur est spécifié afin que ImageView obtienne les LayoutParams du conteneur afin que vous puissiez dire que la référence du conteneur est juste pour LayoutParams rien d'autre.
  2. donc dans un cas particulier, Parent sera nul.
  3. LayoutParams sera de LinearLayout.
Faisal Naseer
la source
0

attachToRoot Définissez sur true:

Si attachToRoot est défini sur true, le fichier de disposition spécifié dans le premier paramètre est gonflé et attaché au ViewGroup spécifié dans le deuxième paramètre.

Imaginez que nous ayons spécifié un bouton dans un fichier de mise en page XML avec sa largeur et sa hauteur de mise en page définies sur 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>

Nous voulons maintenant ajouter par programme ce bouton à un LinearLayout à l'intérieur d'un fragment ou d'une activité. Si notre LinearLayout est déjà une variable membre, mLinearLayout, nous pouvons simplement ajouter le bouton avec ce qui suit:

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

Nous avons spécifié que nous voulions gonfler le bouton à partir de son fichier de ressources de mise en page; nous indiquons ensuite au LayoutInflater que nous voulons le joindre à mLinearLayout. Nos paramètres de mise en page sont respectés car nous savons que le bouton est ajouté à un LinearLayout. Le type de paramètres de disposition du bouton doit être LinearLayout.LayoutParams.

attachToRoot Définissez sur false (non requis pour utiliser false)

Si attachToRoot est défini sur false, le fichier de disposition spécifié dans le premier paramètre est gonflé et non attaché au ViewGroup spécifié dans le deuxième paramètre, mais cette vue gonflée acquiert LayoutParams du parent, ce qui permet à cette vue de tenir correctement dans le parent.


Jetons un coup d'œil au moment où vous souhaitez définir attachToRoot sur false. Dans ce scénario, la vue spécifiée dans le premier paramètre de inflate () n'est pas attachée au ViewGroup dans le deuxième paramètre à ce stade.

Rappelons notre exemple de bouton précédent, où nous voulons attacher un bouton personnalisé d'un fichier de disposition à mLinearLayout. Nous pouvons toujours attacher notre bouton à mLinearLayout en passant false pour attachToRoot - nous l'ajoutons manuellement nous-mêmes par la suite.

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

Ces deux lignes de code sont équivalentes à ce que nous avons écrit plus tôt dans une seule ligne de code lorsque nous avons passé true pour attachToRoot. En passant false, nous disons que nous ne voulons pas encore attacher notre vue au ViewGroup racine. Nous disons que cela se produira à un autre moment. Dans cet exemple, l'autre moment est simplement la méthode addView () utilisée immédiatement en dessous de l'inflation.

L'exemple false attachToRoot nécessite un peu plus de travail lorsque nous ajoutons manuellement la vue à un ViewGroup.

attachToRoot Défini sur false (false est obligatoire)

Lorsque vous gonflez et renvoyez la vue d'un fragment dans onCreateView (), assurez-vous de passer false pour attachToRoot. Si vous transmettez true, vous obtiendrez une exception IllegalStateException car l'enfant spécifié a déjà un parent. Vous devriez avoir spécifié où la vue de votre fragment sera replacée dans votre activité. Le travail du FragmentManager consiste à ajouter, supprimer et remplacer des fragments.

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

Le conteneur root_viewGroup qui contiendra votre fragment dans votre activité est le paramètre ViewGroup qui vous est donné dans onCreateView () dans votre fragment. C'est également le ViewGroup que vous passez dans LayoutInflater.inflate (). Le FragmentManager gérera cependant la liaison de la vue de votre fragment à ce groupe de vues. Vous ne voulez pas le joindre deux fois. Définissez attachToRoot sur false.

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

return view;
}

Pourquoi nous donne-t-on en premier lieu le ViewGroup parent de notre fragment si nous ne voulons pas le joindre dans onCreateView ()? Pourquoi la méthode inflate () demande-t-elle un ViewGroup racine?

Il s'avère que même lorsque nous n'ajoutons pas immédiatement notre nouvelle vue gonflée à son ViewGroup parent, nous devons toujours utiliser les LayoutParams du parent afin que la nouvelle vue détermine sa taille et sa position chaque fois qu'elle est finalement attachée.

Lien: https://youtu.be/1Y0LlmTCOkM?t=409

Utshaw
la source
0

Je partage juste quelques points que j'ai rencontrés en travaillant sur ce sujet,

En plus de la réponse acceptée, je voudrais quelques points qui pourraient être utiles.

Ainsi, lorsque j'ai utilisé attachToRoot comme vrai, la vue qui a été renvoyée était de type ViewGroup, c'est-à-dire le ViewGroup racine du parent qui a été passé en paramètre pour la méthode gonfler (layoutResource, ViewGroup, attachToRoot) , pas de type la mise en page qui a été transmise mais sur attachToRoot comme false, nous obtenons le type de retour de fonction du ViewGroup racine de ce layoutResource .

Je m'explique avec un exemple:

Si nous avons un LinearLayout comme disposition racine et que nous voulons ensuite y ajouter TextView via la fonction gonfler .

puis en utilisant attachToRoot comme véritable fonction de gonflement retourne une vue de type LinearLayout

lors de l'utilisation de attachToRoot comme fausse fonction de gonflage renvoie une vue de type TextView

J'espère que cette découverte vous sera utile ...

HARIS UMAID
la source