Gestore di frammenti

FragmentManager è la classe responsabile dell'esecuzione di azioni sui frammenti della tua app, ad esempio come aggiungerli, rimuoverli o sostituirli e aggiungerli allo stack posteriore.

Potresti non interagire mai direttamente con FragmentManager se usi alla libreria Jetpack Navigation, in quanto compatibile con le FragmentManager per tuo conto. Tuttavia, qualsiasi app che utilizza frammenti usare FragmentManager a un certo livello, quindi è importante capire è e come funziona.

Questa pagina riguarda:

  • Come accedere all'FragmentManager.
  • Il ruolo di FragmentManager in relazione alle tue attività e ai tuoi frammenti.
  • Come gestire il back stack con FragmentManager.
  • Come fornire dati e dipendenze ai tuoi frammenti.

Accedi a FragmentManager

Puoi accedere a FragmentManager da un'attività o da un frammento.

FragmentActivity e le sue sottoclassi, come AppCompatActivity, avranno accesso al FragmentManager tramite getSupportFragmentManager() .

I frammenti possono ospitare uno o più frammenti figlio. Interno un frammento, puoi ottenere un riferimento al FragmentManager che gestisce i figli del frammento attraverso getChildFragmentManager() Se devi accedere all'host FragmentManager, puoi utilizzare getParentFragmentManager()

Ecco un paio di esempi per vedere le relazioni tra frammenti, i relativi host e le istanze FragmentManager associate con ciascuno.

due esempi di layout UI che mostrano le relazioni tra
            frammenti e le relative attività host
Figura 1. Due esempi di layout dell'interfaccia utente che mostrano relazioni tra i frammenti e le loro attività host.

La figura 1 mostra due esempi, ognuno dei quali ha un singolo host di attività. La l'attività dell'host in entrambi questi esempi mostra la navigazione di primo livello per l'utente in qualità di utente BottomNavigationView responsabile dello scambio del frammento host con schermate dell'app. Ogni schermata è implementata come frammento separato.

Il frammento host nell'esempio 1 ospita due frammenti figlio che che compongono uno schermo in visualizzazione divisa. Il frammento host nell'esempio 2 ospita un frammento singolo che compone il frammento visualizzato di un visualizzazione scorrimento.

Data la configurazione, puoi considerare ogni host come se avesse un FragmentManager associate per gestire i frammenti figlio. Questo è illustrato in la figura 2 insieme alle mappature di proprietà tra supportFragmentManager, parentFragmentManager e childFragmentManager.

a ciascun host è associato il proprio FragmentManager
            che gestisce i propri frammenti figlio
Figura 2. Ogni host ha il proprio FragmentManager associati che gestisce e i suoi frammenti figlio.

La proprietà FragmentManager appropriata a cui fare riferimento dipende da dove callsite si trova nella gerarchia dei frammenti insieme al gestore dei frammenti a cui stai tentando di accedere.

Una volta ottenuto un riferimento a FragmentManager, puoi utilizzarlo per e manipolare i frammenti mostrati all'utente.

Frammenti figlio

In generale, l'app è composta da un solo numero o da un numero ridotto di attività nel progetto dell'applicazione, dove ogni attività rappresenta un gruppo di schermate correlate. L'attività può fornire un punto di riferimento navigazione di primo livello e una posizione per definire l'ambito di ViewModel oggetti e altri stati di visualizzazione tra i frammenti. Un frammento rappresenta una singola destinazione nel dell'app.

Se vuoi mostrare più frammenti contemporaneamente, ad esempio in una visualizzazione divisa o una dashboard, puoi utilizzare i frammenti secondari gestiti del frammento di destinazione e il relativo gestore dei frammenti figlio.

Altri casi d'uso per i frammenti figlio sono i seguenti:

  • Diapositive sulle schermate, utilizzando ViewPager2 in un frammento padre per gestire una serie di elementi figlio delle immagini frammentarie.
  • Navigazione secondaria all'interno di una serie di schermate correlate.
  • Jetpack Navigation utilizza frammenti figlio come singole destinazioni. Un l'attività ospita un unico NavHostFragment principale e riempie il relativo spazio con diversi frammenti di destinazione figlio man mano che gli utenti la tua app.

Utilizzare FragmentManager

FragmentManager gestisce il back stack dei frammenti. In fase di runtime, FragmentManager può eseguire operazioni sullo stack posteriore come l'aggiunta o la rimozione in risposta alle interazioni degli utenti. Ogni insieme di modifiche impegnate insieme in una singola unità chiamata FragmentTransaction Per una discussione più approfondita sulle transazioni con frammenti, consulta guida alle transazioni di frammento.

Quando l'utente tocca il pulsante Indietro sul dispositivo o quando chiami FragmentManager.popBackStack(), la transazione con il frammento più in alto esce dallo stack. Se non sono presenti altri frammenti transazioni in stack e, se non utilizzi frammenti, l'oggetto l'evento compare con l'attività. Se utilizzi frammenti figlio, consulta considerazioni speciali per i frammenti figlio e di pari livello.

Quando chiami addToBackStack() in una transazione, la transazione può includere un numero qualsiasi operazioni come l'aggiunta di più frammenti o la sostituzione di frammenti in più containerizzati.

Quando il pila posteriore è scoppiato, le operazioni si invertono come una singola azione atomica. Tuttavia, se ti impegni a ulteriori transazioni prima della chiamata popBackStack() e se non ha utilizzato addToBackStack() per la transazione, queste operazioni non invertire. Pertanto, all'interno di un singolo FragmentTransaction, evita interfoliando le transazioni che interessano il back stack con quelle che non lo fanno.

Esecuzione di una transazione

Per visualizzare un frammento all'interno di un contenitore di layout, utilizza FragmentManager per creare un FragmentTransaction. All'interno della transazione, puoi quindi esegui un add() oppure replace() dell'operazione sul container.

Ad esempio, un FragmentTransaction semplice potrebbe avere il seguente aspetto:

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 questo esempio, ExampleFragment sostituisce l'eventuale frammento, ovvero attualmente nel contenitore di layout identificato ID R.id.fragment_container. Fornire la classe del frammento all'oggetto replace() consente a FragmentManager di gestire l'istanza utilizzando il suo FragmentFactory. Per saperne di più, consulta Fornire dipendenze per i frammenti .

setReorderingAllowed(true) ottimizza le modifiche di stato dei frammenti coinvolti nella transazione per far sì che le animazioni e le transizioni funzionino correttamente. Per ulteriori informazioni navigare con animazioni e transizioni, vedi Transazioni frammentarie e Spostati tra i frammenti utilizzando le animazioni.

Chiamata in corso addToBackStack() esegue il commit della transazione nel back stack. L'utente può in seguito invertire transazione e recupera il frammento precedente toccando il pulsante Indietro . Se hai aggiunto o rimosso più frammenti all'interno di un singolo transazione, tutte queste operazioni vengono annullate quando diventa uno scoppiettante. Il nome facoltativo fornito nella chiamata a addToBackStack() fornisce puoi tornare a una transazione specifica utilizzando popBackStack()

Se non chiami addToBackStack() quando esegui una transazione che rimuove un frammento, che poi viene distrutto quando viene eseguito il commit della transazione e l'utente non può accedervi. Se chiama addToBackStack() quando rimuovi un frammento, solo STOPPED e sarà RESUMED quando l'utente torna indietro. La sua visuale è eliminata in questo caso. Per ulteriori informazioni, vedi Ciclo di vita dei frammenti.

Trova un frammento esistente

Puoi ottenere un riferimento al frammento corrente all'interno di un contenitore di layout utilizzando findFragmentById() Usa findFragmentById() per cercare un frammento in base all'ID specificato quando generato in modo artificioso da XML o dall'ID container se aggiunto in un FragmentTransaction. Ecco un esempio:

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

In alternativa, puoi assegnare un tag univoco a un frammento e ottenere un riferimento utilizzando findFragmentByTag() Puoi assegnare un tag utilizzando l'attributo XML android:tag sui frammenti che vengono definiti all'interno del layout o durante un evento add() o replace() all'interno di un FragmentTransaction.

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

Considerazioni speciali per i frammenti figlio e di pari livello

Solo un FragmentManager può controllare il back stack del frammento in qualsiasi momento. Se la tua app mostra più frammenti di pari livello nella schermata contemporaneamente o, se la tua app utilizza frammenti figlio, FragmentManager è progettato per gestire la navigazione principale dell'app.

Per definire il frammento di navigazione principale all'interno di una transazione di frammento, chiama il setPrimaryNavigationFragment() sulla transazione, passando l'istanza del frammento la cui childFragmentManager ha il controllo principale.

Considera la struttura di navigazione come una serie di livelli, con l'attività come strato più esterno, avvolgendo al di sotto ogni strato di frammenti figlio. Ogni livello ha un singolo frammento di navigazione principale.

Quando la schiena , il livello più interno controlla il comportamento di navigazione. Una volta lo strato più interno non ha più transazioni di frammenti da cui recuperare, il controllo ritorna allo strato successivo e questo processo si ripete finché raggiungere l'attività.

Quando due o più frammenti vengono visualizzati contemporaneamente, uno di questi è il frammento di navigazione principale. Impostazione di un frammento poiché il frammento di navigazione principale rimuove la designazione dalla precedente . Utilizzando l'esempio precedente, se imposti il frammento di dettaglio come frammento di navigazione principale, la designazione del frammento principale viene rimossa.

Supporta più back stack

In alcuni casi, la tua app potrebbe dover supportare più back stack. Un comune un esempio è l'uso di una barra di navigazione in basso per l'app. FragmentManager consente supporta più back stack con saveBackStack() e restoreBackStack(). Questi metodi ti consentono di passare da una modalità all'altra salvando uno stack arretrato e ripristinandone uno diverso.

saveBackStack() funziona in modo simile alla chiamata di popBackStack() con il Parametro name: la transazione specificata e tutte le transazioni successive alla data vengono compilate in modalità popup. La differenza è che saveBackStack() salva stato di tutti i frammenti nello stato transazioni.

Ad esempio, supponiamo che in precedenza tu abbia aggiunto un frammento allo stack posteriore eseguendo il commit di un FragmentTransaction utilizzando addToBackStack(), come mostrato in nell'esempio seguente:

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 questo caso, puoi salvare la transazione di frammento e lo stato ExampleFragment chiamando il numero saveBackStack():

Kotlin

supportFragmentManager.saveBackStack("replacement")

Java

supportFragmentManager.saveBackStack("replacement");

Puoi chiamare restoreBackStack() con lo stesso parametro nome per ripristinare tutti le transazioni popolate e tutti gli stati dei frammenti salvati:

Kotlin

supportFragmentManager.restoreBackStack("replacement")

Java

supportFragmentManager.restoreBackStack("replacement");

Fornisci dipendenze ai tuoi frammenti

Quando aggiungi un frammento, puoi creare un'istanza del frammento manualmente, aggiungilo a FragmentTransaction.

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

Quando esegui il commit della transazione del frammento, l'istanza del frammento che hai creato è l'istanza utilizzata. Tuttavia, durante un modifica della configurazione, dell'attività e tutti i suoi frammenti vengono distrutti e quindi ricreati il più applicabile Risorse Android. FragmentManager gestisce tutto questo per te: ricrea le istanze dei tuoi frammenti, li collega all'host e ricrea il back stack stato.

Per impostazione predefinita, FragmentManager utilizza un FragmentFactory che il framework fornisce per creare un'istanza di una nuova istanza del frammento. Questo La fabbrica predefinita usa la riflessione per trovare e richiamare un costruttore senza argomento. per il frammento. Ciò significa che non puoi utilizzare questa fabbrica predefinita fornire dipendenze al frammento. Significa anche che qualsiasi il costruttore che hai usato per creare il frammento la prima volta non viene usato durante la ricreazione.

Per fornire dipendenze al frammento o usare qualsiasi tipo di costruttore, crea invece una sottoclasse FragmentFactory personalizzata e poi sostituisci FragmentFactory.instantiate Puoi quindi eseguire l'override del valore di fabbrica predefinito di FragmentManager con personalizzata, che viene poi utilizzata per creare un'istanza dei frammenti.

Supponiamo che tu abbia un DessertsFragment responsabile della visualizzazione dolci popolari nella tua città e DessertsFragment ha una dipendenza da una classe DessertsRepository che le fornisce le informazioni necessarie per mostrare all'utente l'interfaccia utente corretta.

Puoi definire DessertsFragment in modo che richieda un DessertsRepository nel suo costruttore.

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.

    ...
}

Una semplice implementazione di FragmentFactory potrebbe essere simile alla seguente: per eseguire le operazioni indicate di seguito.

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

Questo esempio esegue le sottoclassi FragmentFactory, eseguendo l'override di instantiate() per fornire una logica di creazione dei frammenti personalizzata per un DessertsFragment. Altre classi di frammenti sono gestite dal comportamento predefinito di Da FragmentFactory a super.instantiate().

Puoi quindi designare MyFragmentFactory come fabbrica da utilizzare quando creare i frammenti della tua app impostando una proprietà FragmentManager. Devi impostare questa proprietà prima dell'inizio super.onCreate() per garantire che MyFragmentFactory venga utilizzato quando a ricreare i frammenti.

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

L'impostazione di FragmentFactory nell'attività sostituisce il frammento creazione in tutta la gerarchia dei frammenti dell'attività. In altre parole, il valore childFragmentManager di tutti i frammenti secondari che aggiungi utilizza il parametro valore di fabbrica del frammento impostato qui, a meno che non venga sostituito a un livello inferiore.

Esegui test con FragmentFA

In un'architettura a singola attività, testa i tuoi frammenti in utilizzando FragmentScenario . Poiché non puoi fare affidamento sulla logica onCreate personalizzata del tuo attività, puoi passare il valore FragmentFactory come argomento al test dei frammenti, come mostrato nell'esempio seguente:

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

Per informazioni dettagliate su questo processo di test e per esempi completi, consulta Testare i frammenti.