FragmentManager
è la classe responsabile dell'esecuzione di azioni sui frammenti dell'app, ad esempio l'aggiunta, la rimozione o la sostituzione e l'aggiunta allo stack di back-forward.
Non puoi mai interagire direttamente con FragmentManager
se utilizzi la libreria Jetpack Navigation, che funziona con FragmentManager
per tuo conto. Tuttavia, qualsiasi app che utilizza i frammenti utilizza FragmentManager
a un certo livello, quindi è importante capire di cosa si tratta e come funziona.
In questa pagina vengono trattati i seguenti argomenti:
- Come accedere al
FragmentManager
. - Il ruolo di
FragmentManager
in relazione alle tue attività e ai tuoi frammenti. - Come gestire lo stack precedente con
FragmentManager
. - Come fornire dati e dipendenze ai frammenti.
Accedere a FragmentManager
Puoi accedere a FragmentManager
da un'attività o da un frammento.
FragmentActivity
e le sue sottoclassi, come
AppCompatActivity
,
hanno accesso alla FragmentManager
tramite il
metodo
getSupportFragmentManager()
.
I frammenti possono ospitare uno o più frammenti figlio. All'interno di un frammento, puoi ottenere un riferimento al FragmentManager
che gestisce gli elementi secondari del frammento tramite getChildFragmentManager()
.
Se devi accedere al relativo host FragmentManager
, puoi utilizzare
getParentFragmentManager()
.
Ecco un paio di esempi per vedere le relazioni tra i frammenti, i relativi host e le istanze FragmentManager
associate a ognuno.
![due esempi di layout dell'interfaccia utente che mostrano le relazioni tra i frammenti e le relative attività host](https://developer.android.google.cn/static/images/guide/fragments/fragment-host.png?authuser=5&hl=it)
La Figura 1 mostra due esempi, ognuno dei quali ha un singolo host attività. L'attività host in entrambi questi esempi mostra all'utente la navigazione di primo livello come BottomNavigationView
responsabile della sostituzione del frammento host con schermate diverse nell'app. Ogni schermata viene implementata come un frammento separato.
Il frammento host nell'esempio 1 ospita due frammenti figlio che compongono uno schermo divisa. Il frammento host nell'esempio 2 ospita un singolo frammento figlio che costituisce il frammento di visualizzazione di una visualizzazione a scorrimento.
Data questa configurazione, puoi considerare ogni host come un elemento FragmentManager
associato che gestisce i relativi frammenti figlio. Questo è illustrato nella
Figura 2 insieme alle mappature delle proprietà tra supportFragmentManager
,
parentFragmentManager
e childFragmentManager
.
![a ogni host è associato il proprio FragmentManager che gestisce i relativi frammenti figlio](https://developer.android.google.cn/static/images/guide/fragments/manager-mappings.png?authuser=5&hl=it)
FragmentManager
che gestisce i
frammenti figlio.La proprietà FragmentManager
appropriata a cui fare riferimento dipende da dove si trova il sito di chiamata nella gerarchia dei frammenti e dal gestore di frammenti a cui stai tentando di accedere.
Una volta ottenuto un riferimento a FragmentManager
, puoi utilizzarlo per
manipolare i frammenti visualizzati dall'utente.
Frammenti figlio
In generale, l'app è composta da un numero singolo o ridotto di attività nel progetto dell'applicazione e ogni attività rappresenta un gruppo di schermate correlate. L'attività potrebbe fornire un punto in cui posizionare la navigazione di primo livello e un punto in cui definire l'ambito degli oggetti ViewModel
e di altri stati di visualizzazione tra i frammenti. Un frammento rappresenta una singola destinazione
nella tua app.
Se vuoi mostrare più frammenti contemporaneamente, ad esempio in una visualizzazione divisa o in una dashboard, puoi utilizzare i frammenti figlio gestiti dal frammento di destinazione e dal relativo gestore di frammenti figlio.
Altri casi d'uso per i frammenti figlio sono i seguenti:
- Schermata di diapositive utilizzando un elemento
ViewPager2
in un frammento principale per gestire una serie di visualizzazioni di frammenti figlio. - Navigazione secondaria all'interno di una serie di schermate correlate.
- Jetpack Navigation utilizza i frammenti secondari come singole destinazioni. Un'attività ospita un unico elemento
NavHostFragment
principale e riempie il suo spazio con diversi frammenti di destinazione figlio mentre gli utenti navigano nell'app.
Utilizzare FragmentManager
FragmentManager
gestisce lo stack di back dei frammenti. In fase di runtime, FragmentManager
può eseguire operazioni di back stack come l'aggiunta o la rimozione di frammenti in risposta alle interazioni degli utenti. Ogni insieme di modifiche viene
associato come singola unità denominata
FragmentTransaction
.
Per una discussione più approfondita sulle transazioni con frammenti, consulta la guida alle transazioni con frammenti.
Quando l'utente tocca il pulsante Indietro sul dispositivo o quando chiami FragmentManager.popBackStack()
, dallo stack esce la transazione del frammento più in alto. Se non ci sono più transazioni sui frammenti nello stack e se non stai utilizzando i frammenti figlio, l'evento Indietro mostra all'attività. Se utilizzi frammenti figlio, consulta
considerazioni particolari per i frammenti figlio e di pari livello.
Quando chiami addToBackStack()
in una transazione, quest'ultima può includere un numero illimitato di operazioni, come l'aggiunta di più frammenti o la sostituzione di frammenti in più container.
Quando si apre lo stack posteriore, tutte
queste operazioni vengono invertite come una singola azione atomica. Tuttavia, se hai eseguito il commit di ulteriori transazioni prima della chiamata popBackStack()
e se non hai utilizzato addToBackStack()
per la transazione, queste operazioni non vengono annullate. Di conseguenza, all'interno di un singolo FragmentTransaction
, evita
di interpretare le transazioni che influiscono sullo stack di back-end con quelle che non lo fanno.
Eseguire una transazione
Per visualizzare un frammento all'interno di un contenitore di layout, utilizza FragmentManager
per creare un elemento FragmentTransaction
. All'interno della transazione, puoi quindi eseguire un'operazione add()
o replace()
sul container.
Ad esempio, un semplice FragmentTransaction
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 che si trova attualmente nel contenitore di layout identificato dall'ID R.id.fragment_container
. Fornisce la classe del frammento al metodo
replace()
consente a FragmentManager
di gestire l'istanza utilizzando il relativo
FragmentFactory
.
Per ulteriori informazioni, consulta la sezione Fornire dipendenze ai frammenti.
setReorderingAllowed(true)
ottimizza le modifiche di stato dei frammenti coinvolti nella transazione
in modo che le animazioni e le transizioni funzionino correttamente. Per ulteriori informazioni sulla navigazione con le animazioni e le transizioni, consulta Transazioni relative ai frammenti e Spostati tra i frammenti utilizzando le animazioni.
La chiamata a
addToBackStack()
commette il commit della transazione nello stack di backup. L'utente può in seguito invertire la transazione e ripristinare il frammento precedente toccando il pulsante Indietro. Se hai aggiunto o rimosso più frammenti in una singola transazione, tutte queste operazioni vengono annullate quando viene saltato lo stack posteriore. Il nome facoltativo fornito nella chiamata addToBackStack()
ti consente di tornare a una transazione specifica utilizzando popBackStack()
.
Se non chiami addToBackStack()
quando esegui una transazione che rimuove un frammento, il frammento rimosso viene eliminato quando viene eseguito il commit della transazione e l'utente non può accedervi nuovamente. Se chiami addToBackStack()
durante la rimozione di un frammento, questo sarà solo STOPPED
e, in un secondo momento, sarà RESUMED
quando l'utente torna indietro. In questo caso, la sua visualizzazione viene eliminata. Per ulteriori informazioni, consulta Ciclo di vita dei frammenti.
Trova un frammento esistente
Puoi ottenere un riferimento al frammento attuale all'interno di un container di layout
utilizzando
findFragmentById()
.
Utilizza findFragmentById()
per cercare un frammento in base all'ID specificato quando
viene aumentato in modo artificioso da XML o in base all'ID contenitore quando viene aggiunto in
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
definiti all'interno del tuo layout o durante un'operazione add()
o replace()
all'interno di un'istanza 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 elemento FragmentManager
può controllare lo stack di back stack dei frammenti alla volta. Se la tua app mostra più frammenti di pari livello sullo schermo contemporaneamente o se l'app utilizza frammenti figlio, un FragmentManager
è designato per gestire la navigazione principale dell'app.
Per definire il frammento di navigazione principale all'interno di una transazione di frammento, chiama il metodo setPrimaryNavigationFragment()
sulla transazione, passando l'istanza del frammento il cui childFragmentManager
ha il controllo principale.
Considera la struttura di navigazione come una serie di livelli, in cui l'attività è il livello più esterno e racchiude ogni livello di frammenti figlio al di sotto. Ogni livello ha un singolo frammento di navigazione principale.
Quando si verifica l'evento Indietro, il livello più interno controlla il comportamento di navigazione. Una volta che il livello più interno non ha più transazioni con i frammenti da cui derivare, il controllo torna al livello successivo e questo processo si ripete finché non raggiungi l'attività.
Quando vengono visualizzati contemporaneamente due o più frammenti, solo uno di questi rappresenta il frammento di navigazione principale. L'impostazione di un frammento come frammento di navigazione principale rimuove la designazione dal frammento precedente. Utilizzando l'esempio precedente, se imposti il frammento di dettaglio come frammento di navigazione principale, la designazione del frammento principale viene rimossa.
Supporto di più back stack
In alcuni casi, l'app potrebbe dover supportare più back stack. Un esempio comune è se l'app utilizza una barra di navigazione in basso. FragmentManager
consente
di supportare più back stack con i metodi saveBackStack()
e
restoreBackStack()
. Questi metodi consentono di passare da uno stack all'altro salvando uno stack precedente e ripristinandone uno diverso.
saveBackStack()
funziona in modo simile alla chiamata di popBackStack()
con il parametro facoltativo name
: vengono visualizzati tramite popup la transazione specificata e tutte quelle successive nello stack. La differenza è che saveBackStack()
salva lo stato di tutti i frammenti nelle transazioni visualizzate tramite POP.
Ad esempio, supponi di aver precedentemente aggiunto un frammento allo stack di supporto eseguendo il commit di un FragmentTransaction
utilizzando addToBackStack()
, come mostrato 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 questa transazione con frammento e lo stato di
ExampleFragment
chiamando saveBackStack()
:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
Puoi chiamare restoreBackStack()
con lo stesso parametro del nome per ripristinare tutte le transazioni popup 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 e aggiungerlo 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 è quella utilizzata. Tuttavia, durante una modifica della configurazione, l'attività e tutti i suoi frammenti vengono eliminati e poi ricreati con le risorse Android più applicabili.
L'elemento FragmentManager
gestisce tutto questo per te: ricrea le istanze dei tuoi frammenti, le collega all'host e ricrea lo stato dello stack di back.
Per impostazione predefinita, FragmentManager
utilizza un elemento FragmentFactory
fornito dal framework per creare l'istanza di una nuova istanza del frammento. Questa fabbrica predefinita utilizza il riflesso per trovare e richiamare un costruttore senza argomento per il frammento. Ciò significa che non puoi utilizzare questo valore di fabbrica predefinito per fornire dipendenze al frammento. Ciò significa inoltre che per impostazione predefinita qualsiasi costruttore personalizzato utilizzato per creare il frammento la prima volta non viene utilizzato durante la ricreazione.
Per fornire dipendenze al frammento o per utilizzare un costruttore personalizzato, crea una sottoclasse FragmentFactory
personalizzata e poi esegui l'override di FragmentFactory.instantiate
.
Puoi quindi eseguire l'override della fabbrica predefinita di FragmentManager
con la tua fabbrica personalizzata, che viene quindi utilizzata per creare un'istanza dei frammenti.
Supponiamo che tu abbia un DessertsFragment
responsabile della visualizzazione di dolci più popolari nella tua città e che DessertsFragment
abbia una dipendenza da una classe DessertsRepository
che gli fornisce le informazioni necessarie per mostrare l'interfaccia utente corretta all'utente.
Puoi definire DessertsFragment
in modo da richiedere un'istanza DessertsRepository
nel relativo 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.
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
, sostituendo il metodo instantiate()
per fornire una logica di creazione personalizzata dei frammenti per un oggetto DessertsFragment
.
Altre classi di frammenti vengono gestite dal comportamento predefinito di FragmentFactory
tramite super.instantiate()
.
Puoi quindi designare MyFragmentFactory
come fabbrica da utilizzare durante la creazione dei frammenti della tua app impostando una proprietà in FragmentManager
. Devi impostare questa proprietà prima di super.onCreate()
dell'attività per assicurarti che MyFragmentFactory
venga utilizzato durante la ricreazione dei 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 la creazione di frammenti in tutta la gerarchia di frammenti dell'attività. In altre parole, l'elemento childFragmentManager
di eventuali frammenti figlio che aggiungi utilizza il valore di fabbrica dei frammenti personalizzati impostato qui, a meno che non venga eseguito l'override a un livello inferiore.
Test con FragmentFactory
In un'architettura a singola attività, testa i frammenti
isolandoli utilizzando la
classe FragmentScenario
. Poiché non puoi fare affidamento sulla logica onCreate
personalizzata delle tue attività, puoi passare 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 ed esempi completi, consulta Test dei frammenti.