Cosa significa il parametro LayoutInflater attachToRoot?


201

La LayoutInflater.inflatedocumentazione non è esattamente chiara per me in merito allo scopo del attachToRootparametro.

attachToRoot : se la gerarchia inflazionata deve essere collegata al parametro root? Se false, root viene utilizzato solo per creare la sottoclasse corretta di LayoutParams per la vista radice nell'XML.

Qualcuno potrebbe spiegare in modo più dettagliato, in particolare quale sia la vista di root, e forse mostrare un esempio di un cambiamento nel comportamento tra truee falsevalori?



Risposte:


157

ORA O NON ORA

La differenza principale tra il "terzo" parametro attachToRoot su true o false è questa.

Quando si inserisce attachToRoot

true: aggiungi la vista figlio al genitore DESTRA ORA
falso: aggiungi la vista figlio al genitore NON ORA .
Aggiungilo più tardi. `

Quand'è dopo ?

Quello dopo è quando lo usi per es parent.addView(childView)

Un malinteso comune è che, se il parametro attachToRoot è falso, la vista figlio non verrà aggiunta al genitore. SBAGLIATO
In entrambi i casi, la vista figlio verrà aggiunta a parentView. È solo questione di tempo .

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

è equivalente a

inflater.inflate(child,parent,true);

UN GRANDE NO-NO
Non dovresti mai passare attachToRoot come vero quando non sei responsabile per l'aggiunta della vista figlio al genitore.
Ad esempio quando si aggiunge un frammento

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

se passi il terzo parametro come vero otterrai IllegalStateException a causa di questo ragazzo.

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

Poiché hai già aggiunto il frammento figlio in onCreateView () per errore. La chiamata di add ti dirà che la vista figlio è già stata aggiunta all'elemento padre IllegalStateException .
Qui non sei responsabile per l'aggiunta di childView, FragmentManager è responsabile. Quindi passa sempre falso in questo caso.

NOTA: Ho anche letto che parentView non otterrà childView touchEvents se attachToRoot è falso. Ma non l'ho provato però.


6
Molto utile, specialmente la parte riguardante il FragmentManager, grazie!
CybeX il

94

Se impostato su true, quando il layout viene gonfiato verrà automaticamente aggiunto alla gerarchia di visualizzazione del ViewGroup specificato nel secondo parametro come figlio. Ad esempio, se il parametro root era a, LinearLayoutla vista gonfiata verrà automaticamente aggiunta come figlio di quella vista.

Se è impostato su falso, il layout verrà gonfiato ma non verrà attaccato a nessun altro layout (quindi non verrà disegnato, riceverà eventi touch ecc.).


17
Non ho capito bene. Mi è stato sempre un "figlio specificato è già un errore di genitore” fino a quando ho letto questa risposta , che diretto il mio da utilizzare falseper attachToRootdurante il mio frammento di onCreateView. Questo ha risolto il problema e tuttavia il layout del frammento è visibile e attiva, nonostante la vostra risposta. Che cosa sta succedendo qui?
Jeff Axelrod,

67
Perché un frammento collega automaticamente il layout restituito da onCreateView. Quindi, se lo si collega manualmente in onCreateView, la vista viene collegata a 2 genitori (che produce l'errore che si menziona).
Joseph Earl,

11
Sono un po 'confuso qui, @JosephEarl, hai detto che se impostato su true, la vista è collegata al secondo parametro che è il container, ma poi dici che il frammento è automaticamente collegato da onCreateView(), quindi per la mia comprensione, il terzo parametro è inutile e dovrebbe essere impostato falsesempre?
unmultimedio,

5
Restituisci la vista in oncreateview, questa viene quindi automaticamente allegata. Se si imposta attach su true, viene generato un errore. Tuttavia, quando si gonfia la vista in una situazione autonoma, è possibile scegliere di allegare automaticamente la vista al relativo contenitore impostando su true. Non ho quasi mai impostato il vero, dato che aggiungo sempre la vista da solo.
frostymarvelous,

7
@unmultimedio è inutile solo per la vista radice restituita da onCreateView. Se si gonfiano ulteriori layout in quella vista radice o si sta gonfiando in un contesto diverso (ad es. In un'attività), è utile.
Joseph Earl,

36

Sembra un sacco di testo nelle risposte ma nessun codice, ecco perché ho deciso di rilanciare questa vecchia domanda con un esempio di codice, in diverse risposte citate dalla gente:

Se impostato su true, quando il layout viene gonfiato verrà automaticamente aggiunto alla gerarchia di visualizzazione del ViewGroup specificato nel secondo parametro come figlio.

Cosa significa effettivamente nel codice (ciò che la maggior parte dei programmatori capisce) è:

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

Si noti che il codice precedente sta aggiungendo il layout R.layout.child_viewcome figlio di a MyCustomLayoutcausa di attachToRootparam is truee assegna i parametri di layout del genitore esattamente nello stesso modo in cui avrei usato a livello di codice addViewo come se lo avessi fatto in xml:

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

Il codice seguente spiega lo scenario quando si passa attachRootcome 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);

Nel codice precedente si specifica che si desidera myViewessere il proprio oggetto radice e non collegarlo a nessun genitore, in seguito lo abbiamo aggiunto come parte della vista LinearLayoutma per un momento era una vista autonoma (senza genitore).

La stessa cosa accade con i frammenti, potresti aggiungerli a un gruppo già esistente e farne parte o semplicemente passare i parametri:

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

Per specificare che sarà la propria radice.


1
Tra tutti, questo è stato il più utile.
Wahib Ul Haq,

26

La documentazione e le due risposte precedenti dovrebbero essere sufficienti, solo alcuni pensieri da me.

Il inflatemetodo viene utilizzato per gonfiare i file di layout. Con quei layout gonfiati devi avere la possibilità di collegarli direttamente a un genitore ViewGroupo semplicemente gonfiare la gerarchia di visualizzazione da quel file di layout e lavorare con esso al di fuori della normale gerarchia di viste.

Nel primo caso il attachToRootparametro dovrà essere impostato su true(o molto semplicemente utilizzare il inflatemetodo che accetta un file di layout e una radice padre ViewGroup(non null)). In questo caso, il Viewreso è semplicemente ViewGroupquello passato nel metodo, ViewGroupal quale verrà aggiunta la gerarchia della vista gonfiata.

Per la seconda opzione, il valore restituito Viewè la radice ViewGroupdel file di layout. Se ricordi la nostra ultima discussione dalla include-mergedomanda di coppia, questo è uno dei motivi della mergelimitazione (quando un file di layout con mergecome root è gonfiato, devi fornire un genitore e attachedToRootdevi essere impostato su true). Se avevi un file di layout con la radice un mergetag ed attachedToRootera impostato su falseallora il inflatemetodo non avrà nulla da restituire in quanto mergenon ha un equivalente. Inoltre, come dice la documentazione, la inflateversione con attachToRootset su falseè importante perché è possibile creare la gerarchia di visualizzazione con il valore correttoLayoutParamsdal genitore. Ciò è importante in alcuni casi, in particolare con i figli di AdapterView, una sottoclasse di ViewGroup, per cui i addView()metodi impostati non sono supportati. Sono sicuro che ti ricordi di aver usato questa linea nel getView()metodo:

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

Questa linea assicura che il R.layout.row_layoutfile gonfiato abbia il corretto LayoutParamsdalla AdapterViewsottoclasse impostata sulla sua radice ViewGroup. Se non lo faresti, potresti avere dei problemi con il file di layout se il root fosse a RelativeLayout. L' TableLayout/TableRowhanno anche un po 'speciale e importante LayoutParamse si dovrebbe assicurarsi che il punto di vista in essi abbiano la corretta LayoutParams.


18

Io stesso sono stato anche confuso su quello che era il vero scopo di attachToRootin inflatemetodo. Dopo un po 'di studio sull'interfaccia utente, ho finalmente ottenuto la risposta:

genitore:

in questo caso è il widget / layout che circonda gli oggetti di visualizzazione che si desidera gonfiare utilizzando findViewById ().

attachToRoot:

allega le viste al loro genitore (le include nella gerarchia genitore), quindi qualsiasi evento touch che le viste ricevono saranno trasferite anche alla vista genitore. Ora spetta al genitore se vuole intrattenere quegli eventi o ignorarli. se impostato su false, non vengono aggiunti come figli diretti del genitore e il genitore non riceve alcun evento di tocco dalle viste.

Spero che questo cancella la confusione


Mi si risponde che già fornito qui: stackoverflow.com/questions/22326314/...
Neon Warge

11

Ho scritto questa risposta perché anche dopo aver attraversato diverse pagine StackOverflow non sono riuscito a capire chiaramente cosa significasse attachToRoot. Di seguito è riportato il metodo inflate () nella classe LayoutInflater.

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

Dai un'occhiata al file activity_main.xml , al layout button.xml e al file MainActivity.java che ho creato.

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

Quando eseguiamo il codice, non vedremo il pulsante nel layout. Questo perché il nostro layout dei pulsanti non viene aggiunto al layout dell'attività principale poiché attachToRoot è impostato su false.

LinearLayout ha un metodo addView (View view) che può essere usato per aggiungere Views a LinearLayout. Ciò aggiungerà il layout del pulsante nel layout dell'attività principale e renderà visibile il pulsante quando si esegue il codice.

root.addView(view);

Rimuoviamo la riga precedente e vediamo cosa succede quando impostiamo attachToRoot su true.

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

Ancora una volta vediamo che il layout dei pulsanti è visibile. Questo perché attachToRoot collega direttamente il layout gonfiato al genitore specificato. Che in questo caso è root LinearLayout. Qui non è necessario aggiungere le viste manualmente come abbiamo fatto nel caso precedente con il metodo addView (Visualizza vista).

Perché le persone ottengono IllegalStateException quando impostano attachToRoot su true per un frammento.

Questo perché per un frammento hai già specificato dove posizionare il layout del frammento nel tuo file di attività.

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

L' add (int parent, Fragment fragment) aggiunge il frammento che ha il suo layout al layout genitore. Se impostiamo attachToRoot su true, otterrai IllegalStateException: il figlio specificato ha già un genitore. Poiché il layout dei frammenti è già stato aggiunto al layout principale nel metodo add ().

Dovresti sempre passare false per attachToRoot quando stai gonfiando frammenti. È compito del FragmentManager aggiungere, rimuovere e sostituire i frammenti.

Torna al mio esempio. E se facciamo entrambi.

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

Nella prima riga, LayoutInflater collega il layout del pulsante al layout principale e restituisce un oggetto View che contiene lo stesso layout del pulsante. Nella seconda riga, aggiungiamo lo stesso oggetto Visualizza al layout principale padre. Ciò si traduce nella stessa IllegalStateException che abbiamo visto con Fragments (il bambino specificato ha già un genitore).

Tieni presente che esiste un altro metodo inflate () sovraccarico, che imposta attachToRoot su true per impostazione predefinita.

View inflate (int resource, ViewGroup root)

Spiegazione semplice e chiara, proprio quello che stavo cercando!
flyingAssistant

10

C'è molta confusione su questo argomento a causa della documentazione per il metodo inflate ().

In generale, se attachToRoot è impostato su true, il file di layout specificato nel primo parametro viene gonfiato e collegato al ViewGroup specificato nel secondo parametro in quel momento. Quando attachToRoot è falso, il file di layout del primo parametro viene gonfiato e restituito come vista e qualsiasi allegato vista si verifica in un altro momento.

Questo probabilmente non significa molto se non vedi molti esempi. Quando si chiama LayoutInflater.inflate () all'interno del metodo onCreateView di un frammento, sarà necessario passare false per attachToRoot perché l'attività associata a quel frammento è effettivamente responsabile dell'aggiunta della vista di quel frammento. Se si sta gonfiando manualmente e aggiungendo una vista a un'altra vista in un momento successivo, ad esempio con il metodo addView (), si vorrà passare in falso per attachToRoot perché l'allegato arriva in un momento successivo.

Puoi leggere molti altri esempi unici riguardanti le finestre di dialogo e le visualizzazioni personalizzate in un post sul blog che ho scritto proprio su questo argomento.

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


4

attachToRootimpostato su vero significa inflatedViewche verrà aggiunto alla gerarchia della vista padre. Pertanto, gli utenti possono "vedere" e rilevare eventi di tocco (o qualsiasi altra operazione dell'interfaccia utente). Altrimenti, è stato appena creato, non è stato aggiunto a nessuna gerarchia di viste e quindi non può essere visto o gestito eventi di tocco.

Per gli sviluppatori iOS nuovi su Android, attachToRootimpostato su true significa che si chiama questo metodo:

[parent addSubview:inflatedView];

Se andare oltre si potrebbe chiedere: Perché dovrei passare vista padre se ho impostato attachToRoota false? È perché l'elemento radice nella struttura ad albero XML necessita della vista padre per calcolare alcuni LayoutParams (come match parent).


0

Quando si definisce il genitore, attachToRoot determina se si desidera che il gonfiatore lo colleghi effettivamente al genitore o meno. In alcuni casi ciò causa problemi, come in ListAdapter causerà un'eccezione perché l'elenco tenta di aggiungere la vista all'elenco ma dice che è già allegato. In altri casi in cui stai semplicemente gonfiando la visualizzazione per aggiungere a un'attività, potrebbe essere utile e salvarti una riga di codice.


1
non fornisce un quadro chiaro che dovrebbe fornire una buona risposta.
Prakhar1001,

0

Ad esempio abbiamo un ImageView, un LinearLayoute un RelativeLayout. LinearLayout è figlio di RelativeLayout. sarà la View Hierarchy.

RelativeLayout
           ------->LinearLayout

e abbiamo un file di layout separato per ImageView

image_view_layout.xml

Allega alla radice:

//here container is the LinearLayout

    View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
  1. Qui v contiene il riferimento del layout del contenitore, ad esempio LinearLayout.e se si desidera impostare parametri come setImageResource(R.drawable.np);ImageView, è necessario trovarlo tramite il riferimento di parent ieview.findById()
  2. Parent di v sarà FrameLayout.
  3. LayoutParams sarà di FrameLayout.

Non collegare alla radice:

//here container is the LinearLayout
    View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
  1. Qui v contiene il layout del contenitore senza riferimento ma il riferimento diretto a ImageView che è gonfiato in modo da poter impostare i suoi parametri come view.setImageResource(R.drawable.np);senza riferimento findViewById. Ma il contenitore viene specificato in modo che ImageView ottenga i LayoutParams del contenitore in modo da poter dire che il riferimento del contenitore è solo per LayoutParams nient'altro.
  2. quindi in particolare il genitore sarà nullo.
  3. LayoutParams sarà di LinearLayout.

0

attachToRoot Impostato su true:

Se attachToRoot è impostato su true, il file di layout specificato nel primo parametro viene gonfiato e collegato al ViewGroup specificato nel secondo parametro.

Immagina di aver specificato un pulsante in un file di layout XML con la larghezza e l'altezza del layout impostate su 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>

Vogliamo ora aggiungere a livello di codice questo pulsante a un LinearLayout all'interno di un frammento o attività. Se il nostro LinearLayout è già una variabile membro, mLinearLayout, possiamo semplicemente aggiungere il pulsante con il seguente:

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

Abbiamo specificato che vogliamo gonfiare il pulsante dal suo file di risorse di layout; diciamo quindi a LayoutInflater che vogliamo collegarlo a mLinearLayout. I nostri parametri di layout sono rispettati perché sappiamo che il pulsante viene aggiunto a un LinearLayout. Il tipo di parametri del layout del pulsante dovrebbe essere LinearLayout.LayoutParams.

attachToRoot Impostato su false (non necessario per utilizzare false)

Se attachToRoot è impostato su false, il file di layout specificato nel primo parametro viene gonfiato e non collegato al ViewGroup specificato nel secondo parametro, ma quella vista gonfiata acquisisce LayoutParams del genitore che consente a tale vista di adattarsi correttamente al genitore.


Diamo un'occhiata a quando si desidera impostare attachToRoot su false. In questo scenario, la Vista specificata nel primo parametro di inflate () non è collegata al ViewGroup nel secondo parametro in questo momento.

Richiama l'esempio Button precedente, in cui vogliamo allegare un Button personalizzato da un file di layout a mLinearLayout. Possiamo ancora collegare il nostro pulsante a mLinearLayout passando false per attachToRoot: lo aggiungiamo solo manualmente in seguito.

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

Queste due righe di codice sono equivalenti a quelle che abbiamo scritto in precedenza in una riga di codice quando abbiamo passato true per attachToRoot. Trasmettendo false, diciamo che non vogliamo ancora collegare la nostra vista al gruppo View View. Stiamo dicendo che accadrà in qualche altro momento. In questo esempio, l'altro momento è semplicemente il metodo addView () utilizzato immediatamente sotto l'inflazione.

L'esempio false attachToRoot richiede un po 'più di lavoro quando aggiungiamo manualmente la vista a un ViewGroup.

attachToRoot Impostato su false (è obbligatorio false)

Quando si gonfia e si restituisce la vista di un frammento in onCreateView (), assicurarsi di passare false per attachToRoot. Se passi true, otterrai un IllegalStateException perché il figlio specificato ha già un genitore. Avresti dovuto specificare dove la vista del tuo frammento verrà rimessa nella tua attività. È compito del FragmentManager aggiungere, rimuovere e sostituire i frammenti.

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

Il contenitore root_viewGroup che conterrà il tuo frammento nella tua attività è il parametro ViewGroup che ti è stato dato in onCreateView () nel tuo frammento. È anche il ViewGroup che passi in LayoutInflater.inflate (). Tuttavia, FragmentManager gestirà il collegamento della vista del frammento a questo gruppo di visualizzazione. Non si desidera collegarlo due volte. Impostare attachToRoot su false.

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

return view;
}

Perché in primo luogo ci viene dato il ViewGroup genitore del nostro frammento se non vogliamo collegarlo in onCreateView ()? Perché il metodo inflate () richiede un ViewGroup root?

Si scopre che anche quando non stiamo aggiungendo immediatamente la nostra vista gonfiata di recente al suo gruppo di visualizzazione principale, dovremmo comunque utilizzare LayoutParams del padre affinché la nuova vista ne determini le dimensioni e la posizione ogni volta che viene infine collegata.

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


0

Sto solo condividendo alcuni punti che ho riscontrato mentre lavoravo su questo argomento,

Oltre alla risposta accettata, desidero alcuni punti che potrebbero essere di aiuto.

Quindi, quando ho usato attachToRoot come vero, la vista che è stata restituita era di tipo ViewGroup ovvero il ViewGroup radice del genitore che è stato passato come parametro per il metodo infllate (layoutResource, ViewGroup, attachToRoot) , non di tipo il layout che è stato passato ma su attachToRoot come falso otteniamo il tipo restituito dalla funzione di ViewGroup root di quella risorsa .

Lasciami spiegare con un esempio:

Se abbiamo un LinearLayout come layout di root e quindi vogliamo aggiungere TextView in esso tramite la funzione infllate .

quindi, usando attachToRoot come vera funzione di gonfiaggio, viene restituita una vista di tipo LinearLayout

mentre si utilizza attachToRoot come false infllate , la funzione restituisce una vista di tipo TextView

Spero che questa scoperta sia di qualche aiuto ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.