La navigation est l'interaction de l'utilisateur avec l'UI d'une application pour accéder aux destinations de contenu. Les principes de navigation d'Android fournissent des consignes pour vous aider à créer une navigation cohérente et intuitive dans l'application.
Les UI responsives/adaptatives fournissent des destinations de contenu responsives et incluent souvent différents types d'éléments de navigation en réponse aux changements de taille d'écran (par exemple, une barre de navigation inférieure sur les petits écrans, un rail de navigation sur les écrans de taille moyenne ou un panneau de navigation persistant sur les grands écrans). Toutefois, les UI responsives/adaptatives doivent toujours respecter les principes de navigation.
Le composant Navigation de Jetpack applique ces principes de navigation et facilite le développement d'applications avec des interfaces utilisateur responsives/adaptatives.
Navigation responsive dans l'interface utilisateur
La taille de la fenêtre d'affichage occupée par une application a un effet sur l'ergonomie et la facilité d'utilisation. Les classes de taille de fenêtre vous permettent de déterminer les éléments de navigation appropriés (tels que les barres de navigation, les rails ou les panneaux) et de les placer là où ils sont les plus accessibles pour l'utilisateur. Dans les consignes de mise en page de Material Design, les éléments de navigation occupent un espace persistant sur le bord gauche de l'écran et peuvent se déplacer vers le bord inférieur lorsque la largeur de l'application est compacte. Le choix des éléments de navigation dépend en grande partie de la taille de la fenêtre de l'application et du nombre d'éléments qu'elle doit contenir.
Classe de taille de fenêtre | Peu d'éléments | Beaucoup d'éléments |
---|---|---|
Largeur compacte | Barre de navigation inférieure | Panneau de navigation (bord gauche ou bord inférieur) |
Largeur moyenne | Rail de navigation | Panneau de navigation (bord gauche) |
Largeur étendue | Rail de navigation | Panneau de navigation persistant (bord gauche) |
Les fichiers de ressources de mise en page peuvent être qualifiés par des points d'arrêt de classe de taille de fenêtre afin d'utiliser différents éléments de navigation pour différentes dimensions d'affichage.
<!-- 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>
Destinations de contenu responsives
Dans une UI responsive, la mise en page de chaque destination de contenu s'adapte aux changements de taille de la fenêtre. Votre application peut ajuster l'espacement de la mise en page, repositionner des éléments, ajouter ou supprimer du contenu, ou modifier les éléments d'interface utilisateur, y compris les éléments de navigation.
Lorsque chaque destination gère les événements de redimensionnement, les modifications sont isolées dans l'interface utilisateur. Le reste de l'état de l'application, y compris la navigation, n'est pas affecté.
La navigation ne doit pas être un effet secondaire d'un changement de taille de la fenêtre. Ne créez pas de destinations de contenu juste pour vous adapter à différentes tailles de fenêtres. Par exemple, ne créez pas de destinations de contenu distinctes pour les différents écrans d'un appareil pliable.
Lorsque la navigation vers des destinations de contenu dépend des changements de taille de la fenêtre, les problèmes suivants peuvent se présenter:
- L'ancienne destination (correspondant à la taille de fenêtre précédente) peut être visible momentanément avant d'accéder à la nouvelle destination.
- Pour garantir la réversibilité (par exemple, lorsqu'un appareil est plié et déplié), la navigation est requise pour chaque taille de fenêtre.
- Il peut être difficile de préserver l'état de l'application entre les destinations, car la navigation peut détruire l'état lors de l'affichage de la pile "Retour".
Par ailleurs, il est possible que votre application ne soit même pas au premier plan lorsque la taille de la fenêtre change. La mise en page de votre application peut nécessiter plus d'espace que l'application au premier plan. Lorsque l'utilisateur revient dans votre application, l'orientation et la taille de la fenêtre peuvent avoir changé.
Si votre application nécessite des destinations de contenu uniques en fonction de la taille de la fenêtre, envisagez de regrouper les destinations correspondantes dans une seule destination incluant d'autres mises en page adaptatives.
Destinations de contenu avec plusieurs mises en page
Dans le cadre d'un design responsif/adaptatif, une seule destination de navigation peut comporter d'autres mises en page en fonction de la taille de la fenêtre de l'application. La mise en page occupe la totalité de la fenêtre, mais elle varie selon la taille de cette dernière (conception adaptative).
La vue liste/détails est un bon exemple. Pour les tailles de fenêtre compactes, votre application affiche une mise en page pour la liste et une autre pour le détail. Au départ, l'accès à la destination de la vue Liste/Détail affiche uniquement la mise en page de la liste. Lorsqu'un élément de la liste est sélectionné, l'application affiche la mise en page du détail en lieu et place de la liste. Lorsque la commande "Retour" est sélectionnée, la mise en page de la liste s'affiche à nouveau et remplace les détails. Toutefois, pour les grandes tailles de fenêtre, les mises en page de la liste et des détails sont affichées côte à côte.
SlidingPaneLayout
vous permet de créer une seule destination de navigation qui affiche deux volets de contenu côte à côte sur un grand écran, mais un volet à la fois sur les petits écrans tels que les téléphones classiques.
<!-- 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>
Pour découvrir comment implémenter une mise en page Liste/Détail à l'aide de SlidingPaneLayout
, consultez Créer une mise en page à deux volets.
Utiliser un seul graphe de navigation
Pour offrir une expérience utilisateur cohérente sur tous les appareils ou dans toutes les fenêtres, utilisez un seul graphe de navigation dans lequel la mise en page de chaque destination de contenu est responsive.
Si vous utilisez un graphe de navigation différent pour chaque classe de taille de fenêtre, chaque fois que l'application passe d'une classe de taille à une autre, vous devez déterminer la destination actuelle de l'utilisateur dans les autres graphes, construire une pile "Retour" et rapprocher les informations d'état qui diffèrent entre les graphes.
Hôte de navigation imbriqué
Votre application peut inclure une destination de contenu qui possède ses propres destinations de contenu. Par exemple, dans une mise en page Liste/Détail, le volet des détails d'un élément peut inclure des éléments d'interface utilisateur qui permettent d'accéder à du contenu qui le remplace.
Pour implémenter ce type de sous-navigation, faites du volet d'informations un hôte de navigation imbriqué doté de son propre graphique de navigation qui spécifie les destinations accessibles à partir de ce volet:
<!-- 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>
Cet exemple diffère d'un graphe de navigation imbriqué, car le graphe de navigation du NavHost
imbriqué n'est pas connecté au graphe de navigation principal. Autrement dit, vous ne pouvez pas passer directement d'une destination à l'autre dans un graphe.
Pour en savoir plus, consultez la section Graphes de navigation imbriqués.
État préservé
Pour fournir des destinations de contenu responsives, votre application doit conserver son état lorsque l'appareil est pivoté ou plié, ou lorsque la fenêtre de l'application est redimensionnée. Par défaut, ces changements de configuration recréent les activités, les fragments et la hiérarchie des vues de l'application. Pour enregistrer l'état de l'interface utilisateur, nous vous recommandons d'utiliser un élément ViewModel
, qui persiste en cas de changement de la configuration. (Voir Enregistrer les états de l'interface utilisateur .)
Les changements de taille doivent être réversibles, par exemple lorsque l'utilisateur fait pivoter l'appareil dans un sens, puis dans l'autre.
Les mises en page responsives/adaptatives peuvent afficher différents contenus en fonction des différentes tailles de fenêtre. Elles ont donc souvent besoin d'enregistrer un état supplémentaire lié au contenu, même si cet état n'est pas applicable à la taille de la fenêtre actuelle. Par exemple, une mise en page peut avoir de l'espace pour afficher un widget de défilement supplémentaire uniquement pour des largeurs de fenêtre plus importantes. Si un événement de redimensionnement réduit trop la largeur de la fenêtre, le widget est masqué. Lorsque l'application applique à nouveau les dimensions précédentes, le widget de défilement redevient visible, et la position de défilement d'origine doit être restaurée.
Champs d'application ViewModel
Le guide du développeur Effectuer une migration vers le composant Navigation recommande une architecture à activité unique dans laquelle les destinations sont implémentées en tant que fragments et leurs modèles de données sont implémentés à l'aide de ViewModel
.
Un ViewModel
est toujours limité à un cycle de vie. Une fois ce cycle de vie terminé définitivement, ViewModel
est effacé et peut être supprimé. Le cycle de vie auquel le ViewModel
est limité (ce qui détermine dans quelle mesure le ViewModel
peut être partagée) dépend du délégué de propriété utilisé pour obtenir le ViewModel
.
Dans le scénario le plus simple, chaque destination de navigation est un fragment unique avec un état d'interface utilisateur complètement isolé. Chaque fragment peut ainsi utiliser le délégué de propriété viewModels()
pour obtenir un ViewModel
limité à ce fragment.
Pour partager l'état de l'UI entre des fragments, appliquez le champ d'application du ViewModel
à l'activité en appelant activityViewModels()
dans les fragments (l'équivalent pour Activity
est juste viewModels()
). Cette action permet à l'activité et aux fragments qui lui sont associés de partager l'instance ViewModel
.
Toutefois, dans une architecture à activité unique, ce champ d'application du ViewModel
reste efficace aussi longtemps que l'application. Par conséquent, le ViewModel
reste en mémoire même si aucun fragment ne l'utilise.
Supposons que votre graphe de navigation présente une séquence de destinations de fragment représentant un flux de paiement et que l'état actuel de l'ensemble du processus de paiement se trouve dans un ViewModel
partagé entre les fragments. L'application du champ d'application du ViewModel
à l'activité est non seulement trop large, mais présente également un autre problème: si l'utilisateur passe par le processus de paiement pour une commande, puis réitère cette opération pour une deuxième commande, les deux commandes utilisent la même instance du ViewModel
de paiement. Avant de procéder au règlement de la deuxième commande, vous devez effacer manuellement les données de la première commande. Toute erreur peut être coûteuse pour l'utilisateur.
Définissez plutôt la portée de l'élément ViewModel
sur un graphe de navigation dans le NavController
actuel. Créez un graphe de navigation imbriqué pour encapsuler les destinations incluses dans le processus de paiement. Ensuite, dans chacune de ces destinations de fragment, utilisez le délégué de propriété navGraphViewModels()
et transmettez l'ID du graphe de navigation pour obtenir le ViewModel
partagé. Cette approche garantit que lorsque l'utilisateur quitte le processus de paiement et que le graphe de navigation imbriqué n'est pas inclus dans le champ d'application, l'instance correspondante de ViewModel
est supprimée et n'est pas utilisée pour le paiement suivant.
Champ d'application | Délégué de propriété | Peut partager le ViewModel avec… |
---|---|---|
Fragment | Fragment.viewModels() |
Fragment uniquement |
Activité | Activity.viewModels() ou Fragment.activityViewModels() |
Activité et tous les fragments associés |
Graphe de navigation | Fragment.navGraphViewModels() |
Tous les fragments du même graphe de navigation |
Notez que si vous utilisez un hôte de navigation imbriqué (voir la section Hôte de navigation imbriqué), les destinations de cet hôte ne peuvent pas partager d'instances ViewModel
avec des destinations extérieures à l'hôte lorsque vous utilisez navGraphViewModels()
, car les graphes ne sont pas connectés. Dans ce cas, vous pouvez utiliser le champ d'application de l'activité à la place.