Fragment manager

FragmentManager ist die Klasse, die für das Ausführen von Aktionen an den Fragmenten Ihrer Anwendung verantwortlich ist, z. B. das Hinzufügen, Entfernen oder Ersetzen und Hinzufügen zum Back Stack.

Wenn Sie die Jetpack Navigation-Bibliothek verwenden, interagieren Sie möglicherweise nie direkt mit FragmentManager, da sie in Ihrem Namen mit FragmentManager zusammenarbeitet. Jede Anwendung, die Fragmente verwendet, nutzt jedoch auf einer Ebene FragmentManager. Es ist daher wichtig, zu verstehen, um was es sich dabei handelt und wie es funktioniert.

Auf dieser Seite werden folgende Themen behandelt:

  • Auf FragmentManager zugreifen
  • Die Rolle von FragmentManager in Bezug auf deine Aktivitäten und Fragmente.
  • So verwaltest du den Back Stack mit FragmentManager.
  • So stellen Sie Daten und Abhängigkeiten für Ihre Fragmente bereit.

Auf FragmentManager zugreifen

Sie können über eine Aktivität oder ein Fragment auf das FragmentManager zugreifen.

FragmentActivity und die zugehörigen abgeleiteten Klassen wie AppCompatActivity haben über die Methode getSupportFragmentManager() Zugriff auf den FragmentManager.

Fragmente können ein oder mehrere untergeordnete Fragmente hosten. Innerhalb eines Fragments können Sie einen Verweis auf das FragmentManager abrufen, das die untergeordneten Elemente des Fragments über getChildFragmentManager() verwaltet. Wenn Sie auf den Host FragmentManager zugreifen müssen, können Sie getParentFragmentManager() verwenden.

Die folgenden Beispiele zeigen die Beziehungen zwischen Fragmenten, ihren Hosts und den jeweiligen FragmentManager-Instanzen.

Zwei UI-Layoutbeispiele, die die Beziehungen zwischen Fragmenten und ihren Hostaktivitäten zeigen
Abbildung 1: Zwei UI-Layoutbeispiele, die die Beziehungen zwischen Fragmenten und ihren Hostaktivitäten zeigen.

Abbildung 1 zeigt zwei Beispiele, von denen jedes einen einzelnen Aktivitätshost hat. Bei der Hostaktivität in beiden Beispielen wird dem Nutzer die Navigation auf oberster Ebene als BottomNavigationView angezeigt, das dafür verantwortlich ist, das Hostfragment durch andere Bildschirme in der App auszutauschen. Jeder Bildschirm wird als separates Fragment implementiert.

Das Hostfragment in Beispiel 1 hostet zwei untergeordnete Fragmente, die einen Bildschirm mit geteilter Ansicht bilden. Das Hostfragment in Beispiel 2 hostet ein einzelnes untergeordnetes Fragment, das das Anzeigefragment einer Wischansicht darstellt.

Bei dieser Einrichtung können Sie sich jeden Host so vorstellen, als wäre mit einem FragmentManager ein FragmentManager verknüpft, der seine untergeordneten Fragmente verwaltet. Das ist in Abbildung 2 mit Property-Zuordnungen zwischen supportFragmentManager, parentFragmentManager und childFragmentManager dargestellt.

Jedem Host ist ein eigener FragmentManager zugeordnet, der seine untergeordneten Fragmente verwaltet
Abbildung 2: Jedem Host ist eine eigene FragmentManager zugeordnet, die die untergeordneten Fragmente verwaltet.

Die geeignete FragmentManager-Eigenschaft, auf die verwiesen werden soll, hängt davon ab, wo sich die Aufrufseite in der Fragmenthierarchie befindet und auf welchen Fragmentmanager Sie zugreifen möchten.

Sobald Sie einen Verweis auf FragmentManager haben, können Sie damit die Fragmente bearbeiten, die dem Nutzer angezeigt werden.

Untergeordnete Fragmente

Im Allgemeinen besteht Ihre Anwendung aus einer einzelnen oder wenigen Aktivitäten in Ihrem Anwendungsprojekt, wobei jede Aktivität eine Gruppe zusammengehöriger Bildschirme darstellt. Die Aktivität kann einen Punkt zur Platzierung der Navigation auf oberster Ebene sowie einen Ort zur Auswahl von ViewModel-Objekten und anderen Ansichten zwischen den Fragmenten liefern. Ein Fragment stellt ein einzelnes Ziel in Ihrer Anwendung dar.

Wenn Sie mehrere Fragmente gleichzeitig anzeigen möchten, z. B. in einer geteilten Ansicht oder in einem Dashboard, können Sie untergeordnete Fragmente verwenden, die von Ihrem Zielfragment und seinem untergeordneten Fragmentmanager verwaltet werden.

Weitere Anwendungsfälle für untergeordnete Fragmente sind folgende:

  • Bildschirmfolien. Dabei wird ein ViewPager2 in einem übergeordneten Fragment verwendet, um eine Reihe von untergeordneten Fragmentansichten zu verwalten.
  • Subnavigation innerhalb einer Reihe zusammengehöriger Bildschirme.
  • Jetpack Navigation verwendet untergeordnete Fragmente als einzelne Ziele. Eine Aktivität hostet ein einzelnes übergeordnetes NavHostFragment-Element und füllt dessen Bereich mit verschiedenen untergeordneten Zielfragmenten, während Nutzer durch Ihre Anwendung navigieren.

FragmentManager verwenden

FragmentManager verwaltet den Back-Stack des Fragments. Während der Laufzeit kann das FragmentManager Back-Stack-Vorgänge wie das Hinzufügen oder Entfernen von Fragmenten als Reaktion auf Nutzerinteraktionen ausführen. Jeder Satz von Änderungen wird in einer einzigen Einheit festgeschrieben, die als FragmentTransaction bezeichnet wird. Eine ausführlichere Erläuterung von Fragmenttransaktionen finden Sie im Leitfaden zu Fragmenttransaktionen.

Wenn der Nutzer auf seinem Gerät auf die Schaltfläche „Zurück“ tippt oder wenn Sie FragmentManager.popBackStack() aufrufen, springt die oberste Fragmenttransaktion aus dem Stapel. Wenn sich keine weiteren Fragmenttransaktionen im Stack befinden und Sie keine untergeordneten Fragmente verwenden, wird das Back-Ereignis bei der Aktivität angezeigt. Wenn Sie untergeordnete Fragmente verwenden, lesen Sie den Abschnitt Besondere Hinweise zu untergeordneten und gleichgeordneten Fragmenten.

Wenn Sie addToBackStack() für eine Transaktion aufrufen, kann die Transaktion eine beliebige Anzahl von Vorgängen enthalten, z. B. das Hinzufügen mehrerer Fragmente oder das Ersetzen von Fragmenten in mehreren Containern.

Wenn der Back Stack geöffnet wird, werden alle diese Vorgänge zu einer einzigen atomaren Aktion umgekehrt. Wenn Sie jedoch vor dem popBackStack()-Aufruf zusätzliche Transaktionen festgeschrieben haben und addToBackStack() für die Transaktion nicht verwendet haben, werden diese Vorgänge nicht rückgängig gemacht. Daher sollten Sie innerhalb einer einzelnen FragmentTransaction vermeiden, dass Transaktionen, die den Back-Stack beeinträchtigen, mit Transaktionen verschachteln, die dies nicht tun.

Transaktion ausführen

Zum Anzeigen eines Fragments in einem Layoutcontainer verwenden Sie FragmentManager, um ein FragmentTransaction zu erstellen. Innerhalb der Transaktion können Sie dann einen add()- oder replace()-Vorgang für den Container ausführen.

Ein einfaches FragmentTransaction-Objekt könnte beispielsweise so aussehen:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack("name") // Name can be null
}

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack("name") // Name can be null
    .commit();

In diesem Beispiel ersetzt ExampleFragment das Fragment, das sich derzeit im Layout-Container befindet, der durch die ID R.id.fragment_container identifiziert wird. Wenn Sie die Klasse des Fragments für die Methode replace() bereitstellen, kann FragmentManager die Instanziierung mit seinem FragmentFactory verarbeiten. Weitere Informationen finden Sie im Abschnitt Abhängigkeiten für Fragmente bereitstellen.

setReorderingAllowed(true) optimiert die Statusänderungen der an der Transaktion beteiligten Fragmente, sodass Animationen und Übergänge korrekt funktionieren. Weitere Informationen zum Navigieren mit Animationen und Übergängen finden Sie unter Fragmenttransaktionen und Mithilfe von Animationen zwischen Fragmenten wechseln.

Durch den Aufruf von addToBackStack() wird die Transaktion im Back-Stack übergeben. Der Nutzer kann die Transaktion später rückgängig machen und das vorherige Fragment wiederherstellen, indem er auf die Schaltfläche „Zurück“ tippt. Wenn Sie mehrere Fragmente innerhalb einer einzelnen Transaktion hinzugefügt oder entfernt haben, werden alle diese Vorgänge rückgängig gemacht, wenn der Back-Stack per Pop-up entfernt wird. Der optionale Name im addToBackStack()-Aufruf gibt Ihnen die Möglichkeit, mit popBackStack() zu einer bestimmten Transaktion zurückzukehren.

Wenn Sie bei der Durchführung einer Transaktion, mit der ein Fragment entfernt wird, nicht addToBackStack() aufrufen, wird das entfernte Fragment beim Commit der Transaktion gelöscht und der Nutzer kann nicht zu ihm zurückkehren. Wenn Sie beim Entfernen eines Fragments addToBackStack() aufrufen, lautet das Fragment nur STOPPED. Wenn der Nutzer zurückkehrt, erhält der Nutzer später den Wert RESUMED. Seine Ansicht wird in diesem Fall gelöscht. Weitere Informationen finden Sie unter Fragment-Lebenszyklus.

Vorhandenes Fragment suchen

Mit findFragmentById() können Sie einen Verweis auf das aktuelle Fragment in einem Layoutcontainer abrufen. Verwenden Sie findFragmentById(), um ein Fragment entweder anhand der angegebenen ID, wenn es aus XML aufgebläht wird, oder nach der Container-ID, wenn es in einem FragmentTransaction-Element hinzugefügt wird, zu suchen. Beispiel:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment =
        (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);

Alternativ können Sie einem Fragment ein eindeutiges Tag zuweisen und mit findFragmentByTag() eine Referenz abrufen. Sie können ein Tag mithilfe des XML-Attributs android:tag bei Fragmenten zuweisen, die in Ihrem Layout oder während eines add()- oder replace()-Vorgangs innerhalb einer FragmentTransaction definiert werden.

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container, "tag")
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentByTag("tag") as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

Besondere Hinweise für untergeordnete und gleichgeordnete Fragmente

Es kann jeweils immer nur eine FragmentManager den Back-Stack des Fragments steuern. Wenn in Ihrer Anwendung mehrere gleichgeordnete Fragmente gleichzeitig auf dem Bildschirm angezeigt werden oder wenn in Ihrer Anwendung untergeordnete Fragmente verwendet werden, wird eine FragmentManager für die primäre Navigation der Anwendung vorgesehen.

Um das primäre Navigationsfragment innerhalb einer Fragmenttransaktion zu definieren, rufen Sie für die Transaktion die Methode setPrimaryNavigationFragment() auf und übergeben Sie die Instanz des Fragments, dessen childFragmentManager das primäre Steuerelement hat.

Betrachten Sie die Navigationsstruktur als eine Reihe von Ebenen, wobei die Aktivität die äußerste Ebene ist, die jede Schicht untergeordneter Fragmente darunter umschließt. Jede Ebene hat ein einzelnes primäres Navigationsfragment.

Wenn das Ereignis „Back“ eintritt, steuert die innerste Ebene das Navigationsverhalten. Sobald die innerste Ebene keine weiteren Fragmenttransaktionen mehr für ein Pop-Back-up hat, kehrt die Kontrolle zur nächsten Ebene zurück und dieser Vorgang wird wiederholt, bis Sie die Aktivität erreichen.

Wenn zwei oder mehr Fragmente gleichzeitig angezeigt werden, ist nur eines davon das primäre Navigationsfragment. Wird ein Fragment als primäres Navigationsfragment festgelegt, wird die Bezeichnung des vorherigen Fragments entfernt. Wenn Sie im vorherigen Beispiel das Detailfragment als primäres Navigationsfragment festlegen, wird die Bezeichnung des Hauptfragments entfernt.

Unterstützung mehrerer Back-Stacks

In einigen Fällen muss Ihre Anwendung möglicherweise mehrere Back-Stacks unterstützen. Ein gängiges Beispiel ist, wenn Ihre Anwendung eine untere Navigationsleiste verwendet. Mit FragmentManager können mit den Methoden saveBackStack() und restoreBackStack() mehrere Back-Stacks unterstützt werden. Mit diesen Methoden können Sie zwischen Back-Stacks wechseln, indem Sie einen Back-Stack speichern und einen anderen wiederherstellen.

saveBackStack() funktioniert ähnlich wie das Aufrufen von popBackStack() mit dem optionalen name-Parameter: Die angegebene Transaktion und alle nachfolgenden Transaktionen im Stapel werden per Pop-up übertragen. Der Unterschied besteht darin, dass saveBackStack() den Status aller Fragmente in den per Pop-up gespeicherten Transaktionen speichert.

Angenommen, Sie haben dem Back-Stack zuvor ein Fragment hinzugefügt, indem Sie mithilfe von addToBackStack() ein FragmentTransaction mit Commit übergeben haben, wie im folgenden Beispiel gezeigt:

Kotlin

supportFragmentManager.commit {
  replace<ExampleFragment>(R.id.fragment_container)
  setReorderingAllowed(true)
  addToBackStack("replacement")
}

Java

supportFragmentManager.beginTransaction()
  .replace(R.id.fragment_container, ExampleFragment.class, null)
  // setReorderingAllowed(true) and the optional string argument for
  // addToBackStack() are both required if you want to use saveBackStack()
  .setReorderingAllowed(true)
  .addToBackStack("replacement")
  .commit();

In diesem Fall können Sie diese Fragmenttransaktion und den Status von ExampleFragment durch Aufrufen von saveBackStack() speichern:

Kotlin

supportFragmentManager.saveBackStack("replacement")

Java

supportFragmentManager.saveBackStack("replacement");

Sie können restoreBackStack() mit demselben Namensparameter aufrufen, um alle per Pop-up gespeicherten Transaktionen und alle gespeicherten Fragmentstatus wiederherzustellen:

Kotlin

supportFragmentManager.restoreBackStack("replacement")

Java

supportFragmentManager.restoreBackStack("replacement");

Abhängigkeiten für Fragmente bereitstellen

Beim Hinzufügen eines Fragments können Sie es manuell instanziieren und dem FragmentTransaction hinzufügen.

Kotlin

fragmentManager.commit {
    // Instantiate a new instance before adding
    val myFragment = ExampleFragment()
    add(R.id.fragment_view_container, myFragment)
    setReorderingAllowed(true)
}

Java

// Instantiate a new instance before adding
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
    .add(R.id.fragment_view_container, myFragment)
    .setReorderingAllowed(true)
    .commit();

Wenn Sie einen Commit für die Fragmenttransaktion durchführen, wird die Instanz des von Ihnen erstellten Fragments als Instanz verwendet. Während einer Konfigurationsänderung werden jedoch Ihre Aktivität und alle zugehörigen Fragmente gelöscht und dann mit den geeignetsten Android-Ressourcen neu erstellt. Das FragmentManager übernimmt dies für Sie: Instanzen Ihrer Fragmente werden neu erstellt, an den Host angehängt und der Back-Stack-Status neu erstellt.

Standardmäßig verwendet FragmentManager eine FragmentFactory, die vom Framework zur Instanziierung einer neuen Instanz des Fragments bereitgestellt wird. Diese Standard-Factory verwendet Reflexion, um einen No-Argument-Konstruktor für Ihr Fragment zu finden und aufzurufen. Das bedeutet, dass Sie diese Standard-Factory nicht verwenden können, um Abhängigkeiten für Ihr Fragment bereitzustellen. Das bedeutet auch, dass der benutzerdefinierte Konstruktor, den Sie zum ersten Mal zum Erstellen des Fragments verwendet haben, standardmäßig nicht bei der Neuerstellung verwendet wird.

Wenn Sie Abhängigkeiten für das Fragment bereitstellen oder einen benutzerdefinierten Konstruktor verwenden möchten, erstellen Sie stattdessen eine benutzerdefinierte abgeleitete FragmentFactory-Klasse und überschreiben dann FragmentFactory.instantiate. Anschließend können Sie die Standard-Factory von FragmentManager durch Ihre benutzerdefinierte Factory überschreiben, die dann zur Instanziierung Ihrer Fragmente verwendet wird.

Angenommen, Sie haben eine DessertsFragment, die für die Anzeige beliebter Desserts in Ihrer Heimatstadt verantwortlich ist, und dass DessertsFragment von einer DessertsRepository-Klasse abhängig ist, die ihr die Informationen bereitstellt, die zum Anzeigen der richtigen UI für den Nutzer erforderlich sind.

Sie können den DessertsFragment so definieren, dass in seinem Konstruktor eine DessertsRepository-Instanz erforderlich ist.

Kotlin

class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() {
    ...
}

Java

public class DessertsFragment extends Fragment {
    private DessertsRepository dessertsRepository;

    public DessertsFragment(DessertsRepository dessertsRepository) {
        super();
        this.dessertsRepository = dessertsRepository;
    }

    // Getter omitted.

    ...
}

Eine einfache Implementierung von FragmentFactory sieht in etwa so aus:

Kotlin

class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
            when (loadFragmentClass(classLoader, className)) {
                DessertsFragment::class.java -> DessertsFragment(repository)
                else -> super.instantiate(classLoader, className)
            }
}

Java

public class MyFragmentFactory extends FragmentFactory {
    private DessertsRepository repository;

    public MyFragmentFactory(DessertsRepository repository) {
        super();
        this.repository = repository;
    }

    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className);
        if (fragmentClass == DessertsFragment.class) {
            return new DessertsFragment(repository);
        } else {
            return super.instantiate(classLoader, className);
        }
    }
}

Dieses Beispiel erstellt eine abgeleitete Klasse von FragmentFactory und überschreibt die Methode instantiate(), um eine benutzerdefinierte Logik zur Fragmenterstellung für einen DessertsFragment bereitzustellen. Andere Fragmentklassen werden vom Standardverhalten von FragmentFactory bis super.instantiate() verarbeitet.

Sie können dann MyFragmentFactory als Factory festlegen, die beim Erstellen der Fragmente Ihrer Anwendung verwendet werden soll. Dazu legen Sie eine Eigenschaft für FragmentManager fest. Sie müssen diese Eigenschaft vor dem super.onCreate() Ihrer Aktivität festlegen, damit MyFragmentFactory bei der Neuerstellung der Fragmente verwendet wird.

Kotlin

class MealActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance())
        super.onCreate(savedInstanceState)
    }
}

Java

public class MealActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        DessertsRepository repository = DessertsRepository.getInstance();
        getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
        super.onCreate(savedInstanceState);
    }
}

Wenn Sie FragmentFactory in der Aktivität festlegen, wird die Fragmenterstellung in der gesamten Fragmenthierarchie der Aktivität überschrieben. Mit anderen Worten, der childFragmentManager aller untergeordneten Fragmente, die Sie hinzufügen, verwendet die hier festgelegte benutzerdefinierte Fragment-Factory, sofern diese nicht auf einer niedrigeren Ebene überschrieben wird.

Mit FragmentFactory testen

Testen Sie die Fragmente in einer Architektur mit einer einzelnen Aktivität isoliert mithilfe der Klasse FragmentScenario. Da Sie sich nicht auf die benutzerdefinierte onCreate-Logik Ihrer Aktivität verlassen können, können Sie stattdessen FragmentFactory als Argument an Ihren Fragmenttest übergeben, wie im folgenden Beispiel gezeigt:

// Inside your test
val dessertRepository = mock(DessertsRepository::class.java)
launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment {
    // Test Fragment logic
}

Ausführliche Informationen zu diesem Testprozess und vollständige Beispiele finden Sie unter Fragmente testen.