Fragmente innerhalb von Fragmenten

145

Ich frage mich, ob dies tatsächlich ein Fehler in der Android-API ist:

Ich habe ein Setup wie folgt:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. Ist ein Menü, das Fragment Nr. 2 (Ein Suchbildschirm) im rechten Bereich lädt.
  2. Ist ein Suchbildschirm, der Fragment Nr. 3 enthält, bei dem es sich um eine Ergebnisliste handelt.
  3. Die Ergebnisliste wird an mehreren Stellen verwendet (auch als eigenständiges funktionierendes Fragment auf hoher Ebene).

Diese Funktionalität funktioniert perfekt auf einem Telefon (wobei 1 & 2 und 3 ActivityFragments sind).

Als ich diesen Code verwendete:

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

Wo R.id.leftPaneund R.id.rightPanesind <fragment>s in einem horizontalen linearen Layout.

Nach meinem Verständnis entfernt der obige Code das residente Fragment und ersetzt es dann durch ein neues Fragment. Genial ... Offensichtlich passiert das nicht, denn wenn dieser Code das zweite Mal ausgeführt wird, tritt die folgende Ausnahme auf:

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

Dies liegt daran, dass der Container für FragmentNumber3 dupliziert wurde und keine eindeutige ID mehr hat. Das ursprüngliche Fragment wurde nicht zerstört (?), Bevor das neue hinzugefügt wurde (meiner Meinung nach bedeutet dies, dass es nicht ersetzt wurde ).

Kann mir jemand sagen, ob dies möglich ist ( diese Antwort deutet darauf hin, dass dies nicht der Fall ist) oder ob es sich um einen Fehler handelt?

Graeme
quelle
1
mögliches Duplikat von Fragment Inside Fragment
rds
6
@rds Dies ist eine alte Frage, etwas sinnlos als doppelt zu markieren.
pietv8x

Antworten:

203

Verschachtelte Fragmente werden derzeit nicht unterstützt. Der Versuch, ein Fragment in die Benutzeroberfläche eines anderen Fragments einzufügen, führt zu undefiniertem und wahrscheinlich fehlerhaftem Verhalten.

Update : Verschachtelte Fragmente werden ab Android 4.2 (und Android Support Library Version 11) unterstützt: http://developer.android.com/about/versions/android-4.2.html#NestedFragments

HINWEIS (gemäß diesem Dokument ): " Hinweis: Sie können ein Layout nicht in ein Fragment aufblasen, wenn dieses Layout a enthält <fragment>. Verschachtelte Fragmente werden nur unterstützt, wenn sie einem Fragment dynamisch hinzugefügt werden. "

Hackbod
quelle
14
Wird nicht unterstützt, da dies kein Entwurfsziel für die Erstimplementierung war. Ich habe viele Anfragen für das Feature gehört, daher wird es wahrscheinlich irgendwann gemacht, aber wie üblich gibt es viele andere Dinge, die vorrangig damit konkurrieren.
Hackbod
4
Ich habe dies durch die Erweiterung von FragmentActivity, FragmentManager und FragmentTransaction erreicht. Grundvoraussetzung ist, die DeferringFragmentActivity in meinen Aktivitäten zu erweitern und dieselbe API bereitzustellen, damit sich kein anderer Code ändert. Wenn ich getFragmentManager aufrufe, erhalte ich eine Instanz, die DeferringFragmentManager aufruft, und wenn ich beginTransaction aufrufe, erhalte ich eine DeferredTransaction. Diese Transaktion speichert POJOs mit der aufgerufenen Methode und den aufgerufenen Argumenten. Wenn Commit aufgerufen wird, suchen wir zuerst nach ausstehenden DeferredTransactions. Sobald alle Transaktionen festgeschrieben wurden, starten wir eine echte Transaktion und führen alle gespeicherten Methoden mit args aus.
Dskinner
11
Dieser Punkt ist jetzt. Verschachtelte Fragments sind jetzt Teil der Android-API, yay! developer.android.com/about/versions/… .
Alex Lockwood
9
Wow, was für ein Albtraum: Wenn Sie <fragment> für ein Fragment verwenden und dieses Fragment zufällig untergeordnete Fragmente verwendet, schlägt dies nicht mit einem eindeutigen Fehler fehl ("kann Layoutfragmenten keine untergeordneten Fragmente hinzufügen") - es scheitert auf mysteriöse Weise mit Ausnahmen wie "Fragment hat keine Ansicht erstellt". Das Debuggen dauert mehrere Stunden ...
Glenn Maynard
6
@ MartínMarconcini sicher, aber das ist aufgrund der von der API bereitgestellten Funktionalität überhaupt nicht ersichtlich. Wenn etwas nicht erlaubt ist, sollte es klar dokumentiert werden und nicht dem Entwickler überlassen werden, seine Haare herauszuziehen, da etwas nicht so funktioniert, wie Sie es erwarten würden.
dcow
98

Verschachtelte Fragmente werden in Android 4.2 und höher unterstützt

Die Android Support Library unterstützt jetzt auch verschachtelte Fragmente , sodass Sie verschachtelte Fragmentdesigns unter Android 1.6 und höher implementieren können.

Um ein Fragment zu verschachteln, rufen Sie einfach getChildFragmentManager () für das Fragment auf, in dem Sie ein Fragment hinzufügen möchten. Dies gibt einen FragmentManager zurück, den Sie wie gewohnt aus der Aktivität der obersten Ebene verwenden können, um Fragmenttransaktionen zu erstellen. Hier ist beispielsweise ein Code, der ein Fragment aus einer vorhandenen Fragmentklasse hinzufügt:

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

Um mehr über verschachtelte Fragmente zu erfahren, lesen Sie bitte diese Tutorials
Teil 1
Teil 2
Teil 3

und hier ist ein SO-Beitrag, in dem Best Practices für verschachtelte Fragmente erörtert werden .

Raneez Ahmed
quelle
Hauptnachteil von Nestedfragment ist, dass wir das Optionsmenü nicht von childfragment aus aufrufen können :( wenn wir ABS verwenden!
LOG_TAG
Können Sie bitte in meiner Ausgabe nachsehen? Es ist sehr ähnlich .. stackoverflow.com/questions/32240138/… . Für mich wird das Kinder-Framnet nicht vom Code aufgeblasen
Nicks
33

.. Sie können Ihr verschachteltes Fragment in der destroyviewMethode des übergeordneten Fragments bereinigen :

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }
Furykid
quelle
4
Wenn Sie einige Lebenszyklustests mit SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ) durchführen, werden Sie feststellen , dass dieser Code einen Fehler verursacht, wenn eine andere Aktivität mit immer aktiviertem Finish ausgeführt wird (IllegalStateException: Diese Aktion kann nicht ausgeführt werden) nach onSaveInstanceState). Das Einschließen des obigen Codes in try / catch ist nicht die eleganteste Lösung, aber es scheint alles zum Laufen zu bringen.
Theo
Das hat fast funktioniert. Später bekam ich einen Stackoverflow zum Zeichnen der Benutzeroberfläche. Vermeiden Sie auf jeden Fall verschachtelte Fragmente ...
Neteinstein
14

Ich habe eine Anwendung, die ich entwickle und die ähnlich wie Tabs in der Aktionsleiste aufgebaut ist, die Fragmente startet. Einige dieser Fragmente enthalten mehrere eingebettete Fragmente.

Ich habe den gleichen Fehler erhalten, als ich versucht habe, die Anwendung auszuführen. Es scheint, als würde der Inflator-Fehler angezeigt, wenn Sie die Fragmente innerhalb des XML-Layouts instanziieren, nachdem eine Registerkarte nicht ausgewählt und dann erneut ausgewählt wurde.

Ich habe dieses Problem gelöst, indem ich alle Fragmente in XML durch Linearlayouts ersetzt und dann einen Fragmentmanager / eine Fragmenttransaktion verwendet habe, um die Fragmente zu instanziieren. Im Moment scheint alles korrekt zu funktionieren, zumindest auf Testebene.

Ich hoffe das hilft dir weiter.

Draksie
quelle
Kann jemand die Wirksamkeit dieses Ansatzes kommentieren? Ich finde es bedauerlich, Fragmente nur eine Ebene tief verwenden zu können - könnte sie dann genauso gut überhaupt nicht verwenden. Das programmgesteuerte Hinzufügen zu Platzhalter-Ansichtsgruppen funktioniert ohne Einschränkungen?
Rafael Nobre
Scheint immer noch für mich zu arbeiten, ich tausche sie auch problemlos in den Betrachter ein und aus. Eine Einschränkung, die ich nur bei Waben mache, die nicht mit Eiscremesandwich kompatibel sind.
Draksia
4

Ich hatte mit dem gleichen Problem zu kämpfen, hatte ein paar Tage damit zu kämpfen und sollte sagen, dass der einfachste Weg, dies zu überwinden, die Verwendung von fragment.hide () / fragment.show () ist, wenn die Registerkarte ausgewählt / nicht ausgewählt ist ().

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

Wenn eine Bildschirmrotation auftritt, werden alle übergeordneten und untergeordneten Fragmente korrekt zerstört.

Dieser Ansatz hat auch einen zusätzlichen Vorteil: Die Verwendung von hide () / show () führt nicht dazu, dass Fragmentansichten ihren Status verlieren, sodass beispielsweise die vorherige Bildlaufposition für ScrollViews nicht wiederhergestellt werden muss.

Das Problem ist, dass ich nicht weiß, ob es richtig ist, Fragmente nicht zu trennen, wenn sie nicht sichtbar sind. Ich denke, das offizielle Beispiel von TabListener wurde mit dem Gedanken entworfen, dass Fragmente wiederverwendbar sind und Sie den Speicher nicht mit ihnen verschmutzen sollten. Ich denke jedoch, wenn Sie nur wenige Registerkarten haben und wissen, dass Benutzer häufig zwischen ihnen wechseln werden wird angemessen sein, um sie an die aktuelle Aktivität gebunden zu halten.

Ich würde gerne Kommentare von erfahreneren Entwicklern hören.

ievgen
quelle
0

Wenn Sie feststellen, dass Ihr verschachteltes Fragment nicht entfernt oder dupliziert wird (z. B. beim Neustart der Aktivität, beim Drehen des Bildschirms), versuchen Sie Folgendes zu ändern:

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

zu

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

Wenn oben nicht hilft, versuchen Sie:

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

gelernt hier

Voy
quelle