Frammenti all'interno di frammenti


145

Mi chiedo se questo sia effettivamente un bug nell'API di Android:

Ho un setup in questo modo:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. È un menu che carica il frammento n. 2 (una schermata di ricerca) nel riquadro di destra.
  2. È una schermata di ricerca che contiene il frammento n. 3, che è un elenco di risultati.
  3. L'elenco dei risultati viene utilizzato in diversi punti (incluso come frammento di alto livello funzionante a sé stante).

Questa funzionalità funziona perfettamente su un telefono (dove 1, 2 e 3 sono ActivityFragment).

Tuttavia, quando ho usato questo codice:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

Dove R.id.leftPanee R.id.rightPanesono <fragment>s in un layout lineare orizzontale.

Comprendo che il codice sopra rimuove il frammento residente e lo sostituisce con un nuovo frammento. Brillante ... Ovviamente non è quello che succede perché quando questo codice viene eseguito la seconda volta si ottiene la seguente eccezione:

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

Ciò è causato dal fatto che il contenitore per FragmentNumber3 è stato duplicato e non ha più un ID univoco. Il frammento iniziale non è stato distrutto (?) Prima di aggiungerne uno nuovo (nella mia mente ciò significa che non è stato sostituito ).

Qualcuno può dirmi se questo è possibile ( questa risposta suggerisce che non lo è) o è un bug?


1
possibile duplicato di Fragment Inside Fragment
rds

6
@rds questa è una domanda antica, un po 'inutile contrassegnarla come duplicata.
pietv8x,

Risposte:


203

I frammenti nidificati non sono attualmente supportati. Cercare di inserire un frammento nell'interfaccia utente di un altro frammento comporterà un comportamento indefinito e probabilmente rotto.

Aggiornamento : frammenti nidificati sono supportati da Android 4.2 (e Android Support Library rev 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

NOTA (come da questo documento ): " Nota: non è possibile gonfiare un layout in un frammento quando quel layout include a <fragment>. I frammenti nidificati sono supportati solo se aggiunti a un frammento in modo dinamico. "


14
Non supportato perché non era un obiettivo di progettazione per l'implementazione iniziale. Ho ascoltato molte richieste per la funzione, quindi probabilmente verrà fatto ad un certo punto, ma come al solito ci sono molte altre cose in competizione con la priorità.
hackbod,

4
Ci sono riuscito estendendo FragmentActivity, FragmentManager e FragmentTransaction. La premessa di base è estendere DeferringFragmentActivity nelle mie attività, fornendo le stesse API quindi nessun altro cambio di codice. Quando chiamo getFragmentManager, ricevo un'istanza che DeferringFragmentManager e quando chiamo beginTransaction, ottengo una DeferredTransaction. Questa transazione memorizza POJO con il metodo e gli argomenti chiamati. Quando il commit è call, cerchiamo prima tutte le DeferredTransactions in sospeso. Una volta che tutte le transazioni sono state impegnate, iniziamo una transazione reale ed eseguiamo tutti i metodi memorizzati con args.
dskinner,

11
Quel punto è adesso. I nidificati Fragmentora fanno parte dell'API di Android, yay! developer.android.com/about/versions/… .
Alex Lockwood,

9
Wow, che incubo: se usi <framment> su un frammento e quel frammento usa frammenti secondari, non fallisce con un chiaro errore ("non puoi aggiungere frammenti secondari a frammenti di layout") - fallisce misteriosamente con eccezioni come "il frammento non ha creato una vista". Il debug dura diverse ore ...
Glenn Maynard,

6
@ MartínMarconcini certo ma questo non è affatto evidente in base alla funzionalità fornita dall'API. Se qualcosa non è permesso, dovrebbe essere chiaramente documentato, non lasciare allo sviluppatore di strapparsi i capelli perché qualcosa non funziona come ti aspetteresti.
dc

98

I frammenti nidificati sono supportati in Android 4.2 e versioni successive

La libreria di supporto Android ora supporta anche frammenti nidificati , quindi è possibile implementare progetti di frammenti nidificati su Android 1.6 e versioni successive.

Per nidificare un frammento, è sufficiente chiamare getChildFragmentManager () sul frammento in cui si desidera aggiungere un frammento. Ciò restituisce un FragmentManager che è possibile utilizzare come si fa normalmente dall'attività di livello superiore per creare transazioni di frammenti. Ad esempio, ecco del codice che aggiunge un frammento all'interno di una classe di frammenti esistente:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

Per avere maggiori idee sui frammenti nidificati, segui questi tutorial
Parte 1
Parte 2
Parte 3

ed ecco un post SO che discute delle migliori pratiche per i frammenti nidificati .


il principale svantaggio di Nestedfragment è che non possiamo chiamare optionmenu da childfragment :( se stiamo usando ABS!
LOG_TAG

Puoi per favore guardare nel mio problema ?? È molto simile .. stackoverflow.com/questions/32240138/… . Per me il framnet figlio non viene gonfiato dal codice
Nicks

33

.. puoi ripulire il tuo frammento nidificato nel destroyviewmetodo del frammento genitore :

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }

4
Se esegui alcuni test del ciclo di vita con SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), vedrai che questo codice provoca un errore quando un'altra attività va in cima con il completamento sempre abilitato (IllegalStateException: Impossibile eseguire questa azione dopo onSaveInstanceState). Il wrapping del codice sopra in try / catch non è la soluzione più elegante ma sembra far funzionare tutto.
Theo

Questo ha quasi funzionato. Più tardi ho ottenuto uno StackOverflow sul disegno dell'interfaccia utente. Evita
assolutamente

14

Ho un'applicazione che sto sviluppando strutturata in modo simile alle schede nella barra delle azioni che lancia frammenti, alcuni di questi frammenti hanno più frammenti incorporati al loro interno.

Stavo ottenendo lo stesso errore quando ho provato a eseguire l'applicazione. Sembra che se si istanziano i frammenti all'interno del layout XML dopo che una scheda è stata deselezionata e quindi riselezionata, otterrei l'errore del gonfiatore.

Ho risolto questo problema sostituendo tutti i frammenti in xml con Linearlayouts e quindi usando una transazione di gestione frammento / frammento per creare un'istanza dei frammenti, tutto sembra funzionare correttamente almeno a livello di test in questo momento.

Spero che questo ti aiuti.


Qualcuno può commentare l'efficacia di questo approccio? Trovo sfortunato essere in grado di usare i frammenti solo a un livello di profondità - potrebbe anche non usarli affatto allora. Aggiungendoli a livello di codice ai viewgroup di segnaposto funzionerà senza avvertimenti?
Rafael Nobre,

Sembra che funzioni ancora per me, li cambio dentro e fuori dal visore anche nessun problema. Un avvertimento lo sto facendo solo su nido d'ape non compatibile con Ice Cream Sandwich.
draksia,

4

Ho affrontato lo stesso problema, ho lottato un paio di giorni con esso e dovrei dire che il modo più semplice per superare Ho scoperto che è usare fragment.hide () / fragment.show () quando la scheda è selezionata / deselezionata ().

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

Quando si verifica la rotazione dello schermo, tutti i frammenti padre e figlio vengono distrutti correttamente.

Questo approccio ha anche un ulteriore vantaggio: l'uso di hide () / show () non fa perdere il loro stato alle viste dei frammenti, quindi non è necessario ripristinare, ad esempio, la posizione di scorrimento precedente per ScrollViews.

Il problema è che non so se sia corretto non staccare i frammenti quando non sono visibili. Penso che l'esempio ufficiale di TabListener sia stato progettato pensando che i frammenti sono riutilizzabili e non dovresti inquinare con loro la memoria, tuttavia, penso che se hai solo alcune schede e sai che gli utenti passeranno da una all'altra frequentemente sarà opportuno tenerli attaccati all'attività corrente.

Vorrei sentire i commenti di sviluppatori più esperti.


0

Se trovi che il tuo frammento nidificato non viene rimosso o duplicato (ad es. Al riavvio dell'attività, sullo schermo ruota) prova a cambiare:

transaction.add(R.id.placeholder, newFragment);

per

transaction.replace(R.id.placeholder, newFragment);

Se sopra non aiuta, prova:

Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

Imparato qui

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.