La navigazione è l'interazione dell'utente con l'interfaccia utente di un'applicazione per accedere alle destinazioni dei contenuti. I principi di navigazione di Android forniscono linee guida che ti aiutano a creare una navigazione coerente e intuitiva dell'app.
Le UI adattabili/responsive forniscono destinazioni di contenuti adattabili e spesso includono diversi tipi di elementi di navigazione in risposta alle variazioni delle dimensioni dello schermo, ad esempio una barra di navigazione in basso su schermi piccoli, una barra di navigazione su schermi di medie dimensioni o un riquadro di navigazione persistente su schermi di grandi dimensioni, ma devono comunque essere conformi ai principi di navigazione.
Il componente Navigation di Jetpack implementa i principi di navigazione e facilita lo sviluppo di app con UI responsive/adattative.
Navigazione dell'interfaccia utente adattabile
Le dimensioni della finestra di visualizzazione occupata da un'app influiscono sull'ergonomia e sull'usabilità. Le classi di dimensioni della finestra ti consentono di determinare gli elementi di navigazione appropriati (come barre di navigazione, riquadri o riquadri a scomparsa) e di posizionarli dove sono più accessibili per l'utente. Nelle linee guida per il layout di Material Design, gli elementi di navigazione occupano uno spazio permanente sul bordo anteriore del display e possono spostarsi sul bordo inferiore quando la larghezza dell'app è compatta. La scelta degli elementi di navigazione dipende in gran parte dalle dimensioni della finestra dell'app e dal numero di elementi che l'elemento deve contenere.
Classe delle dimensioni della finestra | Pochi elementi | Molti articoli |
---|---|---|
larghezza compatta | barra di navigazione in basso | riquadro di navigazione (bordo anteriore o inferiore) |
larghezza media | barra di navigazione | riquadro di navigazione (bordo anteriore) |
larghezza espansa | barra di navigazione | riquadro di navigazione a scomparsa permanente (bordo anteriore) |
I file di risorse di layout possono essere qualificati in base alle interruzioni della classe delle dimensioni della finestra per utilizzare elementi di navigazione diversi per dimensioni di visualizzazione diverse.
<!-- res/layout/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- res/layout-w600dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.navigationrail.NavigationRailView
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- res/layout-w1240dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
... />
<!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>
Destinazioni dei contenuti adattabili
In un'interfaccia utente adattabile, il layout di ogni destinazione dei contenuti si adatta alle modifiche delle dimensioni della finestra. L'app può regolare la spaziatura del layout, riposizionare gli elementi, aggiungere o rimuovere contenuti o modificare gli elementi dell'interfaccia utente, inclusi gli elementi di navigazione.
Quando ogni singola destinazione gestisce gli eventi di ridimensionamento, le modifiche vengono isolate per l'interfaccia utente. Il resto dello stato dell'app, inclusa la navigazione, non è interessato.
La navigazione non deve verificarsi come effetto collaterale delle modifiche delle dimensioni della finestra. Non creare destinazioni dei contenuti solo per adattarsi a dimensioni diverse delle finestre. Ad esempio, non creare destinazioni dei contenuti diverse per le diverse schermate di un dispositivo pieghevole.
La navigazione alle destinazioni dei contenuti come effetto collaterale delle modifiche delle dimensioni della finestra presenta i seguenti problemi:
- La destinazione precedente (per le dimensioni della finestra precedenti) potrebbe essere visibile per un breve periodo prima di passare alla nuova destinazione
- Per mantenere la reversibilità (ad esempio, quando un dispositivo è chiuso e aperto), la navigazione è obbligatoria per ogni dimensione della finestra
- Mantenere lo stato dell'applicazione tra le destinazioni può essere difficile, poiché la navigazione può distruggere lo stato al momento del popping della backstack
Inoltre, l'app potrebbe non essere nemmeno in primo piano quando si verificano le modifiche delle dimensioni della finestra. Il layout della tua app potrebbe richiedere più spazio dell'app in primo piano e, quando l'utente torna alla tua app, l'orientamento e le dimensioni della finestra potrebbero essere cambiati.
Se la tua app richiede destinazioni dei contenuti uniche in base alle dimensioni della finestra, valuta la possibilità di combinare le destinazioni pertinenti in un'unica destinazione che includa layout alternativi e adattabili.
Destinazioni dei contenuti con layout alternativi
Nell'ambito di un design responsive/adattabile, una singola destinazione di navigazione può avere layout alternativi a seconda delle dimensioni della finestra dell'app. Ogni layout occupa l'intera finestra, ma vengono presentati layout diversi per dimensioni diverse della finestra (design adattabile).
Un esempio canonico è la visualizzazione elenco-dettagli. Per le dimensioni compatte delle finestre, l'app mostra un layout dei contenuti per l'elenco e uno per i dettagli. Se si accede alla destinazione della visualizzazione dei dettagli dell'elenco, inizialmente viene visualizzato solo il layout dell'elenco. Quando viene selezionato un elemento dell'elenco, l'app mostra il layout dei dettagli, sostituendo l'elenco. Quando il controllo Indietro è selezionato, viene visualizzato il layout dell'elenco, che sostituisce i dettagli. Tuttavia, per le dimensioni della finestra espanse, i layout di elenco e dettaglio vengono visualizzati affiancati.
SlidingPaneLayout
ti consente di creare una singola destinazione di navigazione
che mostra due riquadri di contenuti affiancati su schermi di grandi dimensioni, ma solo un riquadro
alla volta su schermi piccoli, come quelli dei telefoni convenzionali.
<!-- Single destination for list and detail. -->
<navigation ...>
<!-- Fragment that implements SlidingPaneLayout. -->
<fragment
android:id="@+id/article_two_pane"
android:name="com.example.app.ListDetailTwoPaneFragment" />
<!-- Other destinations... -->
</navigation>
Per informazioni dettagliate sull'implementazione di un layout di elenco e dettagli utilizzando SlidingPaneLayout
, consulta Creare un layout a due riquadri.
Un grafico di navigazione
Per offrire un'esperienza utente coerente su qualsiasi dispositivo o dimensione della finestra, utilizza un singolo grafico di navigazione in cui il layout di ogni destinazione dei contenuti sia adattabile.
Se utilizzi un grafo di navigazione diverso per ogni classe di dimensioni della finestra, ogni volta che l'app passa da una classe all'altra, devi determinare la destinazione corrente dell'utente negli altri grafici, creare una pila di ritorno e riconciliare le informazioni sullo stato che differiscono tra i grafici.
Host di navigazione nidificato
La tua app potrebbe includere una destinazione dei contenuti che ha destinazioni dei contenuti proprie. Ad esempio, in un layout dettagliato dell'elenco, il riquadro dei dettagli dell'articolo potrebbe includere elementi dell'interfaccia utente che rimandano a contenuti che sostituiscono i dettagli dell'articolo.
Per implementare questo tipo di navigazione secondaria, imposta il riquadro dei dettagli come un orientamento di navigazione nidificato con un proprio grafico di navigazione che specifica le destinazioni a cui si accede dal riquadro dei dettagli:
<!-- layout/two_pane_fragment.xml -->
<androidx.slidingpanelayout.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"/>
<!-- Detail pane is a nested navigation host. Its graph is not connected
to the main graph that contains the two_pane_fragment destination. -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detail_pane"
android:layout_width="300dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
È diverso da un grafico di navigazione nidificato perché il grafico di navigazione del NavHost
nidificato non è collegato al grafico di navigazione principale, ovvero non puoi passare direttamente dalle destinazioni di un grafico alle destinazioni dell'altro.
Per ulteriori informazioni, consulta la sezione Grafici di navigazione nidificati.
Stato conservato
Per fornire destinazioni di contenuti adattabili, l'app deve mantenere il proprio stato quando il dispositivo viene ruotato o chiuso o quando le dimensioni della finestra dell'app vengono modificate. Per impostazione predefinita,
modifiche alla configurazione come queste ricreano le attività, i frammenti
e la gerarchia delle visualizzazioni dell'app. Il modo consigliato per salvare lo stato dell'interfaccia utente è con un
ViewModel
, che rimane invariato anche dopo le modifiche alla configurazione. (vedi Salvare gli stati della UI ).
Le modifiche alle dimensioni devono essere reversibili, ad esempio quando l'utente ruota il dispositivo e poi lo ruota di nuovo.
I layout adattabili possono mostrare contenuti diversi a seconda delle dimensioni della finestra. Pertanto, spesso devono salvare uno stato aggiuntivo relativo ai contenuti, anche se non è applicabile alle dimensioni correnti della finestra. Ad esempio, un layout potrebbe avere spazio per mostrare un widget di scorrimento aggiuntivo solo con larghezze della finestra maggiori. Se un evento di ridimensionamento fa sì che la larghezza della finestra diventi troppo ridotta, il widget viene nascosto. Quando l'app viene ridimensionata alle dimensioni precedenti, il widget scorrevole diventa di nuovo visibile e la posizione di scorrimento originale dovrebbe essere ripristinata.
Ambiti ViewModel
La guida per gli sviluppatori Eseguire la migrazione al componente di navigazione prescrive un'architettura a singola attività in cui le destinazioni vengono implementate come frammenti e i relativi modelli di dati vengono implementati utilizzando ViewModel
.
Un ViewModel
è sempre associato a un ciclo di vita e, quando questo termina definitivamente, il ViewModel
viene eliminato e può essere ignorato. Il ciclo di vita
a cui è associato ViewModel
e, di conseguenza, la misura in cui
ViewModel
può essere condiviso, dipendono dal delegato della proprietà utilizzato per ottenere
ViewModel
.
Nel caso più semplice, ogni destinazione di navigazione è un singolo frammento con uno stato dell'interfaccia utente completamente isolato. Pertanto, ogni frammento può utilizzare il delegato della proprietà viewModels()
per ottenere un ViewModel
limitato a quel frammento.
Per condividere lo stato dell'interfaccia utente tra i frammenti, definisci l'ambito di ViewModel
per l'attività chiamando activityViewModels()
nei frammenti (l'equivalente di Activity
è semplicemente viewModels()
). In questo modo, l'attività e gli eventuali frammenti collegati possono condividere l'istanza ViewModel
.
Tuttavia, in un'architettura a singola attività, questo ambito ViewModel
dura
effettivamente per tutta la durata dell'app, quindi ViewModel
rimane in memoria anche se nessun
fragment lo utilizza.
Supponiamo che il grafo di navigazione abbia una sequenza di destinazioni dei frammenti
che rappresentano un flusso di pagamento e che lo stato corrente dell'intera esperienza di pagamento
si trovi in un ViewModel
condiviso tra i frammenti. L'ambito diViewModel
per l'attività non è solo troppo ampio, ma espone anche a un altro problema: se l'utente completa il flusso di pagamento per un ordine e poi lo ripete per un secondo ordine, entrambi gli ordini utilizzano la stessa istanza delViewModel
di pagamento. Prima del pagamento del secondo ordine, devi cancellare manualmente i dati del primo ordine. Eventuali errori potrebbero essere costosi per l'utente.
Scegli invece come ambito ViewModel
un grafo di navigazione nell'NavController
corrente. Crea un grafo di navigazione nidificato per incapsulare le destinazioni che fanno parte del flusso di pagamento. Poi, in ciascuna di queste destinazioni del frammento, utilizza il delegato della proprietà navGraphViewModels()
e passa l'ID del grafico di navigazione per ottenere il ViewModel
condiviso. In questo modo,
quando l'utente esce dal flusso di pagamento e il grafo di navigazione nidificato non è più in
ambito, l'istanza corrispondente di ViewModel
viene ignorata e non verrà
utilizzata per il pagamento successivo.
Ambito | Rappresentante della proprietà | Può condividere ViewModel con |
---|---|---|
Frammento | Fragment.viewModels() |
Solo frammento |
Attività | Activity.viewModels() o Fragment.activityViewModels() |
Attività e tutti i frammenti associati |
Grafico di navigazione | Fragment.navGraphViewModels() |
Tutti i frammenti nello stesso grafo di navigazione |
Tieni presente che se utilizzi un host di navigazione nidificato (vedi la sezione Host di navigazione nidificato), le destinazioni in questo host non possono condividere istanze ViewModel
con destinazioni esterne all'host quando utilizzi navGraphViewModels()
perché i grafici non sono collegati. In questo caso, puoi utilizzare l'ambito dell'attività.