Créer une application adaptative avec la navigation dynamique

1. Introduction

L'un des principaux avantages liés au développement de votre application sur la plate-forme Android est que vous pouvez toucher les utilisateurs dans différents formats (accessoires connectés, appareils pliables, tablettes, ordinateurs et télévision). Ils peuvent avoir besoin d'utiliser la même application sur des appareils à grand écran pour bénéficier d'un espace plus important. De plus en plus d'utilisateurs Android se servent de leurs applications sur divers appareils avec des tailles d'écran différentes et s'attendent à une expérience utilisateur de haute qualité sur tous ces appareils.

Jusqu'à présent, vous avez appris à créer des applications principalement destinées aux appareils mobiles. Dans cet atelier de programmation, vous découvrirez comment transformer vos applications pour qu'elles s'adaptent à d'autres tailles d'écran. Vous aurez recours à des modèles de mise en page conçus pour la navigation adaptative. Ces modèles sont attrayants et peuvent être utilisés à la fois pour les appareils mobiles et les grands écrans, tels que les pliables, les tablettes et les ordinateurs.

Conditions préalables

  • Vous maîtrisez la programmation Kotlin, y compris les classes, les fonctions et les conditions.
  • Vous maîtrisez l'utilisation des classes ViewModel.
  • Vous maîtrisez la création des fonctions Composables.
  • Vous savez créer des mises en page avec Jetpack Compose.
  • Vous savez exécuter des applications sur un appareil ou un émulateur.

Points abordés

  • Créer une navigation entre les écrans sans graphique de navigation pour les applications simples
  • Créer une mise en page de navigation adaptative avec Jetpack Compose
  • Créer un gestionnaire de retour personnalisé

Objectifs de l'atelier

  • Vous implémenterez la navigation dynamique dans l'application Reply existante afin d'adapter sa mise en page à toutes les tailles d'écran

Le produit fini ressemblera à l'image ci-dessous :

​​ Illustration de l'application Reply à la fin de cet atelier de programmation avec affichage du panneau de navigation à gauche. Le panneau de navigation affiche quatre onglets que l'utilisateur peut sélectionner : "Boîte de réception", "Messages envoyés", "Brouillons" et "Spam". À droite du panneau de navigation, une liste d'exemples d'e-mails s'affiche.

Ce dont vous avez besoin

  • Un ordinateur avec accès à Internet, un navigateur Web et Android Studio
  • Accès à GitHub

2. Présentation de l'application

Présentation de l'application Reply

L'application Reply est une application multi-écran semblable à un client de messagerie.

L'application Reply s'affiche en mode Téléphone. Elle affiche une liste d'exemples d'e-mails que l'utilisateur peut lire. Au bas de l'écran se trouvent quatre icônes représentant la boîte de réception, les messages envoyés, les brouillons et le spam.

Elle comprend quatre catégories différentes qui sont affichées dans différents onglets : "Boîte de réception", "Messages envoyés" "Brouillons" et "Spam".

Télécharger le code de démarrage

Dans Android Studio, ouvrez le dossier basic-android-kotlin-compose-training-reply-app.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, cliquez sur le bouton Télécharger le fichier ZIP pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Bienvenue dans Android Studio, cliquez sur Ouvrir.

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu Fichier > Ouvrir.

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Exécuter 8de56cba7583251f.png pour compiler et exécuter l'application. Assurez-vous que tout fonctionne comme prévu.

3. Tutoriel du code de démarrage

Répertoires importants de l'application Reply

Le répertoire de fichiers de l'application Reply affiche deux sous-répertoires développés : "data" et "ui". Dans le répertoire "ui", MainActivity.kt est sélectionné. MainActivity.kt apparaît à la fin de sa liste de contenus.

La couche de données et la couche d'interface utilisateur du projet d'application Reply sont divisées en différents répertoires. ReplyViewModel, ReplyUiState et les autres composables se trouvent dans le répertoire ui. Les classes data et enum qui définissent la couche de données et les classes du fournisseur de données se trouvent dans le répertoire data.

Initialisation des données dans l'application Reply

L'application Reply est initialisée avec les données via la méthode initilizeUIState() dans ReplyViewModel, qui est exécutée dans la fonction init.

ReplyViewModel.kt

...
   init {
        initializeUIState()
    }

   private fun initializeUIState() {
        var mailboxes: Map<MailboxType, List<Email>> =
            LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
        _uiState.value =
            ReplyUiState(
                mailboxes = mailboxes,
                currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
                    ?: LocalEmailsDataProvider.defaultEmail
            )
    }
...

Composable au niveau de l'écran

Comme pour les autres applications, l'application Reply utilise le composable ReplyApp comme composable principal, où les éléments viewModel et uiState sont déclarés. Différentes fonctions viewModel sont également transmises en tant qu'arguments lambda pour le composable ReplyHomeScreen.

ReplyApp.kt

...
@Composable
fun ReplyApp(modifier: Modifier = Modifier) {
    val viewModel: ReplyViewModel = viewModel()
    val replyUiState = viewModel.uiState.collectAsState().value

    ReplyHomeScreen(
        replyUiState = replyUiState,
        onTabPressed = { mailboxType: MailboxType ->
            viewModel.updateCurrentMailbox(mailboxType = mailboxType)
            viewModel.resetHomeScreenStates()
        },
        onEmailCardPressed = { email: Email ->
            viewModel.updateDetailsScreenStates(
                email = email
            )
        },
        onDetailScreenBackPressed = {
            viewModel.resetHomeScreenStates()
        },
        modifier = modifier
    )
}

Autres composables

  • ReplyHomeScreen.kt : contient les composables de l'écran d'accueil, y compris les éléments de navigation.
  • ReplyHomeContent.kt : contient des composables qui définissent des composables plus détaillés de l'écran d'accueil.
  • ReplyDetailsScreen.kt : contient des composables d'écran et des composables de plus petite taille pour l'écran d'informations.

N'hésitez pas à examiner chaque fichier en détail pour vous familiariser avec les composables avant de passer à la section suivante de l'atelier de programmation.

4. Changer d'écran sans graphique de navigation

Dans le parcours précédent, vous avez appris à utiliser une classe NavHostController pour passer d'un écran à un autre. Avec Compose, vous pouvez également changer d'écran avec des instructions conditionnelles simples à l'aide d'états d'exécution modifiables. Cette fonctionnalité est particulièrement utile dans les petites applications telles que Reply, qui nécessitent uniquement de basculer entre deux écrans.

Changer d'écran via les changements d'état

Dans Compose, les écrans sont recomposés lorsqu'un changement d'état se produit. Vous pouvez modifier les écrans à l'aide de conditions simples pour répondre aux changements d'état.

Vous allez utiliserez des expressions conditionnelles pour afficher le contenu de l'écran d'accueil lorsque l'utilisateur s'y trouve, et l'écran de détails lorsque l'utilisateur n'est pas sur l'écran d'accueil.

Pour permettre à l'application Reply de changer d'écran en cas de changement d'état, procédez comme suit :

  1. Ouvrez le code de démarrage dans Android Studio.
  2. Dans le composable ReplyHomeScreen dans ReplyHomeScreen.kt, encapsulez le composable ReplyAppContent avec une instruction if lorsque la propriété isShowingHomepage de l'objet replyUiState correspond à true.

ReplyHomeScreen.kt

@Composable
fun ReplyHomeScreen(
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit = {},
    onEmailCardPressed: (Int) -> Unit = {},
    onDetailScreenBackPressed: () -> Unit = {},
    modifier: Modifier = Modifier
) {

...
    if (replyUiState.isShowingHomepage) {
        ReplyAppContent(
            replyUiState = replyUiState,
            onTabPressed = onTabPressed,
            onEmailCardPressed = onEmailCardPressed,
            navigationItemContentList = navigationItemContentList,
            modifier = modifier

        )
    }
}

Vous devez désormais tenir compte du scénario où l'utilisateur n'est pas sur l'écran d'accueil, et afficher dans ce cas l'écran des détails.

  1. Ajoutez une branche else avec le composable ReplyDetailsScreen dans le corps. Ajoutez replyUIState, onDetailScreenBackPressed et modifier comme arguments pour le composable ReplyDetailsScreen.

ReplyHomeScreen.kt

@Composable
fun ReplyHomeScreen(
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit = {},
    onEmailCardPressed: (Int) -> Unit = {},
    onDetailScreenBackPressed: () -> Unit = {},
    modifier: Modifier = Modifier
) {

...

    if (replyUiState.isShowingHomepage) {
        ReplyAppContent(
            replyUiState = replyUiState,
            onTabPressed = onTabPressed,
            onEmailCardPressed = onEmailCardPressed,
            navigationItemContentList = navigationItemContentList,
            modifier = modifier

        )
    } else {
        ReplyDetailsScreen(
            replyUiState = replyUiState,
            onBackPressed = onDetailScreenBackPressed,
            modifier = modifier
        )
    }
}

L'objet replyUiState est un objet d'état. Par conséquent, en cas de modification de la propriété isShowingHomepage correspondant à l'objet replyUiState, le composable ReplyHomeScreen est recomposé, et l'instruction if/else est réévaluée au moment de l'exécution. Cette approche permet de naviguer entre différents écrans sans avoir à utiliser de classe NavHostController.

Illustration animée de l'application Reply sur un émulateur de téléphone, qui montre le passage de l'écran d'accueil à la page d'informations. L'écran d'accueil affiche une liste d'e-mails avec les quatre icônes associées aux messages en bas de l'écran (boîte de réception, messages envoyés, brouillons et spam). La page d'informations affiche l'intégralité du texte d'un e-mail, accompagné des boutons "Répondre" et "Répondre à tous".

Créer un gestionnaire de retour personnalisé

L'un des avantages du composable NavHost pour changer d'écran est que la direction des écrans précédents est enregistrée dans la pile arrière. Ces écrans enregistrés permettent au bouton "Retour" du système de revenir facilement à l'écran précédent lorsqu'il est appelé. Étant donné que l'application Reply n'utilise pas de NavHost, vous devez ajouter le code pour gérer le bouton "Retour" manuellement. C'est ce que vous allez faire dans un instant.

Pour créer un gestionnaire de retour personnalisé dans l'application Reply, procédez comme suit :

  1. Sur la première ligne du composable ReplyDetailsScreen, ajoutez un composable BackHandler.
  2. Appelez la fonction onBackPressed() dans le corps du composable BackHandler.

ReplyDetailsScreen.kt

...
import androidx.activity.compose.BackHandler
...
@Composable
fun ReplyDetailsScreen(
    replyUiState: ReplyUiState,
    modifier: Modifier = Modifier,
    onBackPressed: () -> Unit = {},
) {
    BackHandler {
        onBackPressed()
    }
...

5. Exécuter l'application sur les appareils à grand écran

Vérifier votre application avec l'émulateur redimensionnable

Pour créer des applications vraiment utiles, les développeurs doivent comprendre l'expérience utilisateur attendue dans différents facteurs de forme. Par conséquent, il est important de tester les applications dans divers formats dès le début du processus de développement.

Pour atteindre cet objectif, il existe de nombreux émulateurs de différentes tailles. Cependant, cette opération peut s'avérer fastidieuse, en particulier lorsque vous créez plusieurs écrans à la fois. Vous devrez peut-être également tester la façon dont une application en cours d'exécution réagit aux changements de taille d'écran, tels que les changements d'orientation, les changements de taille de fenêtre sur un ordinateur et les changements d'état des appareils pliables.

Avec l'introduction de l'émulateur redimensionnable, Android Studio vous permet de tester ces scénarios.

Pour configurer l'émulateur redimensionnable, procédez comme suit :

  1. Assurez-vous d'exécuter Android Studio Chipmunk | 2021.2.1 ou version ultérieure.
  2. Dans Android Studio, sélectionnez Outils > Gestionnaire d'appareils.

Le menu "Outils" affiche la liste des options. Le gestionnaire d'appareils, qui figure à la moitié de la liste, est sélectionné.

  1. Dans Gestionnaire d'appareils, cliquez sur Créer un appareil. La barre d'outils du gestionnaire d'appareils affiche deux options de menu : "Virtuel" et "Physique". En dessous de ces options, apparaît le bouton "Créer un appareil".
  2. Sélectionnez la catégorie Téléphone et l'appareil Redimensionnable (expérimental).
  3. Cliquez sur Suivant.

La fenêtre "Gestionnaire d'appareils" vous invite à sélectionner une définition d'appareil. Une liste d'options s'affiche avec un champ de recherche juste au-dessus. La catégorie "Téléphone" est sélectionnée, de même que le nom de la définition d'appareil "Redimensionnable (expérimental)".

  1. Sélectionnez Niveau d'API 33.
  2. Cliquez sur Suivant.

La fenêtre "Configuration d'appareils virtuels" affiche une invite vous permettant de sélectionner une image système. L'API Tiramisu est sélectionnée.

  1. Attribuez un nom au nouvel appareil virtuel Android.
  2. Cliquez sur Terminer.

L'écran de configuration d'appareils virtuels Android s'affiche. L'écran de configuration comprend un champ de texte permettant de saisir le nom de l'appareil virtuel Android. Sous le champ de nom se trouve la liste des options d'appareil, y compris la définition de l'appareil (Redimensionnable expérimental), l'image système (Tiramisu) et l'orientation (l'orientation Portrait est sélectionnée par défaut). Les boutons intitulés "Modifier" s'affichent à droite de la définition de l'appareil et des informations système sur l'image système, tandis qu'une option "Paysage" apparaît à droite de l'option "Portrait" sélectionnée. En bas à droite, quatre boutons s'affichent : "Annuler", "Précédent", "Suivant" (qui est grisé et qui ne peut pas être sélectionné) et "Terminer".

Exécuter l'application sur un émulateur à grand écran

Maintenant que vous avez configuré l'émulateur redimensionnable, voyons ce à quoi ressemble l'application sur un grand écran.

  1. Exécutez l'application sur l'émulateur redimensionnable.
  2. Sélectionnez Tablette comme mode d'affichage.

Émulateur redimensionnable affichant l'application Reply sur un écran de téléphone. L'application affiche une liste de messages avec quatre icônes en bas de l'écran représentant la boîte de réception, les messages envoyés, les brouillons et le spam.

  1. Inspectez l'application en mode Tablette et en mode Paysage.

Émulateur redimensionnable affichant l'application Reply sur un écran de tablette, avec un corps allongé. Des icônes apparaissent en bas de l'écran et représentent la boîte de réception, les messages envoyés, les brouillons et le spam.

Notez que l'écran de la tablette est allongé horizontalement. Bien que cette orientation fonctionne, elle ne convient pas forcément à une utilisation sur grand écran. Voyons maintenant comment résoudre ce problème.

Conception pour les grands écrans

Lorsque l'on regarde cette application sur une tablette, on ne peut s'empêcher de penser que son design est médiocre et qu'elle n'est pas très attrayante. Le fait est que cette mise en page n'est pas adaptée aux grands écrans.

Lorsque vous concevez une application destinée à un affichage sur grand écran (tablettes et pliables, par exemple), vous devez tenir compte de l'ergonomie et de la proximité des doigts de l'utilisateur sur l'écran. Avec les appareils mobiles, les doigts des utilisateurs ont facilement accès à la majeure partie de l'écran. L'emplacement des éléments interactifs, tels que les boutons et les éléments de navigation, n'est pas déterminant. Toutefois, pour les grands écrans, placer des éléments interactifs essentiels au milieu de l'écran peut nuire à leur accessibilité.

Comme vous pouvez le voir dans l'application Reply, la conception pour les grands écrans ne se limite pas à allonger ou à agrandir les éléments de l'interface utilisateur. Profitez de l'espace supplémentaire dont vous disposez pour créer une expérience différente pour vos utilisateurs. Par exemple, vous pouvez ajouter une autre mise en page sur le même écran pour éviter d'avoir à passer à un autre écran ou pour permettre le mode multitâche.

Application Reply, avec l'écran de détails affiché sur la page d'accueil, ainsi que le panneau de navigation et la liste de diffusion. Un exemple d'e-mail s'affiche à droite de la liste de diffusion. Les boutons "Répondre" et "Répondre à tous" figurent sous l'exemple d'e-mail.

Cette conception peut accroître la productivité des utilisateurs et stimuler l'engagement. Avant de déployer cette conception, vous devez d'abord apprendre à créer différentes mises en page pour différentes tailles d'écran.

6. Adapter la mise en page à différentes tailles d'écran

Qu'est-ce qu'un point d'arrêt ?

Vous vous demandez peut-être comment afficher différentes mises en page pour la même application. La réponse courte consiste à utiliser des conditions au niveau de différents états, comme vous l'avez fait au début de cet atelier de programmation.

Pour créer une application adaptative, vous devez modifier la mise en page en fonction de la taille de l'écran. Le point de mesure où la mise en page change s'appelle "point d'arrêt". Material Design a créé une plage de points d'arrêt adaptée à la plupart des écrans Android.

Un tableau indique la plage de points d'arrêt (en dp) pour différents types et configurations d'appareils. La plage de 0 à 599 dp est réservée aux téléphones en mode Portait, aux téléphones en mode Paysage, aux fenêtres compactes, à 4 colonnes et à 8 marges minimum. La plage de 600 à 839 dp est réservée aux petites tablettes pliables en mode Portrait ou Paysage, avec une taille de fenêtre moyenne, 12 colonnes et 12 marges minimum. La plage de 840 dp ou plus est destinée aux tablettes de grande taille en mode Portrait ou Paysage, avec une classe de taille de fenêtre agrandie, 12 colonnes et 32 marges minimum. Les notes du tableau indiquent que les marges et l'espacement sont flexibles et n'ont pas besoin d'être de taille égale. De plus, les téléphones en mode Paysage qui entrent dans la plage de points d'arrêt comprise entre 0 et 599 dp sont considérés comme des exceptions.

Ce tableau des plages de points d'arrêt indique, par exemple, que si votre application est en cours d'exécution sur un appareil dont la taille d'écran est inférieure à 600 dp, vous devez afficher la mise en page mobile.

Utiliser des classes de taille de fenêtre

L'API WindowSizeClass créée pour Compose facilite l'implémentation des points d'arrêt Material Design.

Les classes de taille de fenêtre présentent trois catégories de tailles : compacte, moyenne et grande, à la fois pour la largeur et la hauteur.

Ce schéma représente les classes de taille de fenêtre pour la largeur. Ce schéma représente les classes de taille de fenêtre pour la hauteur.

Pour implémenter l'API WindowSizeClass dans l'application Reply, procédez comme suit :

  1. Ajoutez la dépendance material3-window-size-class au fichier build.gradle du module.

build.gradle

...
dependencies {
...
"androidx.compose.material3:material3-window-size-class:$material3_version"
...
  1. Cliquez sur Synchroniser maintenant pour synchroniser Gradle après avoir ajouté la dépendance.

Le bouton "Synchroniser maintenant" s'affiche sous les onglets pour vous permettre de sélectionner différents fichiers .kt et .gradle. À droite du bouton "Synchroniser maintenant" se trouve un autre bouton intitulé "Ignorer ces modifications".

Maintenant que le fichier build.grade est à jour, vous pouvez créer une variable qui stockera la taille de la fenêtre de l'application à tout moment.

  1. Dans la fonction onCreate() du fichier MainActivity.kt, attribuez la méthode calculateWindowSizeClass avec le contexte this transmis dans le paramètre à une variable nommée windowSize.
  2. Importez le package calculateWindowSizeClass approprié.

MainActivity.kt

...
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass

...

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ReplyTheme {
                val windowSize = calculateWindowSizeClass(this)
                ReplyApp()

...
  1. Notez la couleur rouge de la syntaxe calculateWindowSizeClass, qui entraîne l'affichage de l'ampoule rouge. Cliquez sur cette ampoule à gauche de la variable windowSize, puis sélectionnez Activer 'ExperimentalMaterial3WindowSizeClassApi' sur 'onCreate' pour créer une annotation au-dessus de la méthode onCreate().

Dans le code, la ligne "val windowSize = computeWindowSizeClass(this)" est sélectionnée, alors que l'icône de droite représentant une ampoule s'affiche à gauche de la ligne de code. Sous l'ampoule sélectionnée, une liste d'options permettant de corriger l'erreur, en sélectionnant "Activer 'ExperimentMaterial3WindowSizeClassApi' sur 'onCreate'".

Vous pouvez utiliser la variable WindowWidthSizeClass dans MainActivity.kt pour déterminer la mise en page à afficher dans différents composables. Préparons le composable ReplyApp à recevoir cette valeur.

  1. Dans le fichier ReplyApp.kt, modifiez le composable ReplyApp pour accepter WindowWidthSizeClass en tant que paramètre, puis importez le package approprié.

ReplyApp.kt

...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...

@Composable
fun ReplyApp(
    windowSize: WindowWidthSizeClass,
    modifier: Modifier = Modifier
) {
...
  1. Transmettez la variable windowSize au composant ReplyApp dans la méthode onCreate() du fichier MainActivity.kt.

MainActivity.kt

...
         setContent {
            ReplyTheme {
                val windowSize = calculateWindowSizeClass(this)
                ReplyApp(
                    windowSize = windowSize.widthSizeClass
                )
...

Vous devez également mettre à jour l'aperçu de l'application pour le paramètre windowSize.

  1. Transmettez WindowWidthSizeClass.Compact en tant que paramètre windowSize au composable ReplyApp pour le composant d'aperçu, puis importez le package approprié.

MainActivity.kt

...
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
...

@Preview(showBackground = true)
@Composable
fun ReplyAppPreview() {
    ReplyTheme {
        ReplyApp(
            windowSize = WindowWidthSizeClass.Compact,
        )
    }
}
  1. Pour modifier la mise en page de l'application en fonction de la taille de l'écran, ajoutez une instruction when dans le composable ReplyApp en fonction de la valeur WindowWidthSizeClass.

ReplyApp.kt

...

@Composable
fun ReplyApp(
    windowSize: WindowWidthSizeClass,
    modifier: Modifier = Modifier
) {
    val viewModel: ReplyViewModel = viewModel()
    val replyUiState = viewModel.uiState.collectAsState().value

    when (windowSize) {
        WindowWidthSizeClass.Compact -> {
        }
        WindowWidthSizeClass.Medium -> {
        }
        WindowWidthSizeClass.Expanded -> {
        }
        else -> {
        }
    }
...

À ce stade, vous avez établi une base pour utiliser les valeurs WindowSizeClass afin de modifier la mise en page dans votre application. L'étape suivante consiste à déterminer l'apparence de votre application sur différentes tailles d'écran.

7. Implémenter une mise en page pour la navigation adaptative

Implémenter la navigation adaptative dans l'interface utilisateur

Actuellement, la navigation en bas de l'écran est utilisée pour toutes les tailles d'écran.

Navigation en bas de l'écran pour l'application Reply.

Comme nous l'avons vu précédemment, cette mise en page n'est pas idéale, car il est difficile pour les utilisateurs d'accéder à ces éléments de navigation essentiels sur les grands écrans. Heureusement, vous trouverez des recommandations pour l'affichage des différents éléments de navigation en fonction de différentes classes de taille de fenêtre dans la section Navigation pour les interfaces utilisateur responsives. Pour l'application Reply, vous pouvez implémenter les éléments suivants :

Un tableau indique les classes de taille de fenêtre et les quelques éléments qui s'affichent. La largeur compacte affiche une barre de navigation inférieure en bas de l'écran. La largeur moyenne affiche un rail de navigation. La largeur étendue affiche un panneau de navigation persistant avec un bord au début.

Le rail de navigation est un autre composant de navigation conçu par Material Design. Il permet d'accéder à des options de navigation compactes pour les destinations principales depuis l'application.

Dans l'application Reply, un rail de navigation affiche quatre icônes à la verticale : "Boîte de réception", "Messages envoyés", "Brouillons" et "Spam".

De même, un panneau de navigation persistant/permanent a été créé par Material Design pour assurer un accès ergonomique sur les grands écrans.

Dans l'application Reply, un panneau de navigation permanent affiche quatre onglets avec les icônes correspondantes, ainsi que le nom de l'onglet : "Boîte de réception", "Messages envoyés", "Brouillons" et "Spam".

Implémenter un panneau de navigation

Si vous souhaitez créer un panneau de navigation pour les écrans étendus, vous pouvez utiliser le paramètre navigationType. Pour ce faire, procédez comme suit :

  1. Pour représenter différents types d'éléments de navigation, créez un fichier WindowStateUtils.kt dans un nouveau package utils, qui se trouve sous le répertoire ui.
  2. Ajoutez une classe Enum pour représenter différents types d'éléments de navigation.

WindowStateUtils.kt

package com.example.reply.ui.utils

enum class ReplyNavigationType {
    BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
}

Pour implémenter correctement le panneau de navigation, vous devez déterminer le type de navigation en fonction de la taille de la fenêtre de l'application.

  1. Dans le composable ReplyApp, créez une variable navigationType et attribuez-lui la valeur ReplyNavigationType appropriée, en fonction de la taille de l'écran indiquée dans l'instruction when.

ReplyApp.kt

...
import com.example.reply.ui.utils.ReplyNavigationType
...
    when (windowSize) {
        WindowWidthSizeClass.Compact -> {
            navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
        }
        WindowWidthSizeClass.Medium -> {
            navigationType = ReplyNavigationType.NAVIGATION_RAIL
        }
        WindowWidthSizeClass.Expanded -> {
            navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        }
        else -> {
            navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
        }
    }
...

Vous pouvez utiliser la valeur navigationType dans le composable ReplyHomeScreen. Pour ce faire, commencez par définir cette valeur comme paramètre pour le composable.

  1. Dans le composable ReplyHomeScreen, ajoutez navigationType comme paramètre.

ReplyHomeScreen.kt

...
@Composable
fun ReplyHomeScreen(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit = {},
    onEmailCardPressed: (Email) -> Unit = {},
    onDetailScreenBackPressed: () -> Unit = {},
    modifier: Modifier = Modifier
)

...

  1. Transmettez navigationType dans le composable ReplyHomeScreen.

ReplyApp.kt

...
   ReplyHomeScreen(
        navigationType = navigationType,
        replyUiState = replyUiState,
        onTabPressed = { mailboxType: MailboxType ->
            viewModel.updateCurrentMailbox(mailboxType = mailboxType)
            viewModel.resetHomeScreenStates()
        },
        onEmailCardPressed = { email: Email ->
            viewModel.updateDetailsScreenStates(
                email = email
            )
        },
        onDetailScreenBackPressed = {
            viewModel.resetHomeScreenStates()
        },
        modifier = modifier
    )
...

Vous pouvez ensuite créer une branche pour présenter le contenu de l'application avec un panneau de navigation lorsque l'utilisateur ouvrira l'application sur un écran étendu et affichera l'écran d'accueil.

  1. Dans le corps du composable ReplyHomeScreen, ajoutez une instruction if pour la condition navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER && replyUiState.isShowingHomepage.

ReplyHomeScreen.kt

import androidx.compose.material3.PermanentNavigationDrawer
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit = {},
    onEmailCardPressed: (Email) -> Unit = {},
    onDetailScreenBackPressed: () -> Unit = {},
    modifier: Modifier = Modifier
) {
...
    if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
    ) {
    }

    if (replyUiState.isShowingHomepage) {
            ReplyAppContent(
                replyUiState = replyUiState,
...
  1. Pour concevoir le panneau permanent, créez le composable PermanentNavigationDrawer dans le corps de l'instruction "if" et ajoutez le composable NavigationDrawerContent en tant qu'entrée pour le paramètre drawerContent.
  2. Ajoutez le composable ReplyAppContent en tant qu'argument lambda final de PermanentNavigationDrawer.

ReplyHomeScreen.kt

...
    if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
    ) {
        PermanentNavigationDrawer(
            drawerContent = {
                NavigationDrawerContent(
                    selectedDestination = replyUiState.currentMailbox,
                    onTabPressed = onTabPressed,
                    navigationItemContentList = navigationItemContentList
                )
            }
        ) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier

            )
        }
    }

...
  1. Ajoutez une branche else qui utilisera le corps de l'ancien composable afin de conserver l'ancienne branche pour les écrans non étendus.

ReplyHomeScreen.kt

...
    if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
        && replyUiState.isShowingHomepage
    ) {
        PermanentNavigationDrawer(
            drawerContent = {
                NavigationDrawerContent(
                    selectedDestination = replyUiState.currentMailbox,
                    onTabPressed = onTabPressed,
                    navigationItemContentList = navigationItemContentList
                )
            }
        ) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier

            )
        }
    } else {
        if (replyUiState.isShowingHomepage) {
            ReplyAppContent(
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        } else {
            ReplyDetailsScreen(
                replyUiState = replyUiState,
                onBackPressed = onDetailScreenBackPressed,
                modifier = modifier
            )
        }
    }
}
...
  1. Ajoutez une annotation "Expérimental" au composable ReplyHomeScreen. Cela est nécessaire, car l'API PermanentNavigationDrawer est encore en phase expérimentale.

ReplyHomeScreen.kt

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: (MailboxType) -> Unit = {},
    onEmailCardPressed: (Email) -> Unit = {},
    onDetailScreenBackPressed: () -> Unit = {},
    modifier: Modifier = Modifier
) {
...
  1. Exécutez l'application en mode Tablette. L'écran suivant devrait s'afficher :

L'application Reply s'affiche en mode Tablette avec un panneau de navigation à gauche de l'écran et une liste d'e-mails à droite.

Implémenter un rail de navigation

Comme pour l'implémentation du panneau de navigation, vous devez utiliser le paramètre navigationType pour passer d'un élément de navigation à un autre.

Commençons par ajouter un rail de navigation pour les écrans de taille moyenne.

  1. Commencez par préparer le composable ReplyAppContent en ajoutant navigationType comme paramètre.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit) = {},
    onEmailCardPressed: (Email) -> Unit = {},
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {
...
  1. Transmettez la valeur navigationType dans les deux composables ReplyAppContent.

ReplyHomeScreen.kt

...
            ReplyAppContent(
                navigationType = navigationType,
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
        }
    } else {
        if (replyUiState.isShowingHomepage) {
            ReplyAppContent(
                navigationType = navigationType,
                replyUiState = replyUiState,
                onTabPressed = onTabPressed,
                onEmailCardPressed = onEmailCardPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = modifier
            )
...

Ajoutons à présent des branches, qui permettront à l'application d'afficher des rails de navigation dans certains cas.

  1. Dans la première ligne du corps du composable ReplyAppContent, encapsulez le composableReplyNavigationRail autour du composable AnimatedVisibility et définissez le paramètre visibility sur true si la valeur ReplyNavigationType correspond à NavigationRail.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit) = {},
    onEmailCardPressed: (Email) -> Unit = {},
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {
        AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
            ReplyNavigationRail(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
            )
        }
        Column(
            modifier = Modifier
                .fillMaxSize()            .background(MaterialTheme.colorScheme.inverseOnSurface)
        ) {
            ReplyListOnlyContent(
                replyUiState = replyUiState,
                onEmailCardPressed = onEmailCardPressed,
                modifier = Modifier.weight(1f)
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList

            )
        }

}
...
  1. Pour aligner correctement les composables, encapsulez le composable AnimatedVisibility et le composable Column qui se trouve dans le corps de ReplyAppContent dans un composable Row.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit) = {},
    onEmailCardPressed: (Email) -> Unit = {},
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier.fillMaxSize()) {
        AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
            ReplyNavigationRail(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList
            )
        }
        Column(
            modifier = Modifier
                .fillMaxSize()            .background(MaterialTheme.colorScheme.inverseOnSurface)
        ) {
            ReplyListOnlyContent(
                replyUiState = replyUiState,
                onEmailCardPressed = onEmailCardPressed,
                modifier = Modifier.weight(1f)
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList

            )
        }
    }
}
...

Enfin, vérifions que la barre de navigation inférieure s'affiche dans certains cas.

  1. Après le composable ReplyListOnlyContent, encapsulez le composable ReplyBottomNavigationBar avec un composable AnimatedVisibility.
  2. Définissez le paramètre visible lorsque la valeur ReplyNavigationType correspond à BOTTOM_NAVIGATION.

ReplyHomeScreen.kt

...
            ReplyListOnlyContent(
                replyUiState = replyUiState,
                onEmailCardPressed = onEmailCardPressed,
                modifier = Modifier.weight(1f)
            )
            AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
                ReplyBottomNavigationBar(
                    currentTab = replyUiState.currentMailbox,
                    onTabPressed = onTabPressed,
                    navigationItemContentList = navigationItemContentList
                )
            }
...
  1. Exécutez l'application en mode Pliable non plié. L'écran suivant devrait s'afficher :

L'application Reply s'affiche dans un appareil pliable, avec le rail de navigation à gauche de l'écran et la liste des e-mails à droite.

8. Télécharger le code de solution

Pour télécharger le code de l'atelier de programmation terminé, utilisez la commande git suivante :

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git

cd basic-android-kotlin-compose-training-reply-app
git checkout nav-update

Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.

Si vous souhaitez voir le code de solution, affichez-le sur GitHub.

9. Conclusion

Félicitations ! Vous êtes à deux doigts d'adapter l'application Reply à toutes les tailles d'écran avec une mise en page de navigation adaptative. Vous avez amélioré l'expérience utilisateur en proposant de nombreux formats Android. Dans le prochain atelier de programmation, vous renforcerez vos compétences en matière d'applications adaptatives en implémentant des mises en page, des tests et des aperçus adaptatifs.

N'oubliez pas de partager le fruit de vos efforts sur les réseaux sociaux avec le hashtag #AndroidBasics.

En savoir plus