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 avec grand écran, tels que les appareils pliables, les tablettes et les ordinateurs.

Prérequis

  • 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 Composable.
  • Vous savez créer des mises en page avec Jetpack Compose.
  • Vous savez comment 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 :

56cfa13ef31d0b59.png

​​

Ce dont vous avez besoin

  • Un ordinateur avec accès à Internet, un navigateur Web et Android Studio
  • Un 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.

a1af0f9193718abf.png

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

Télécharger le code de démarrage

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

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 :

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 initializeUIState() 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.

8443a3ef1a239f6e.gif

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,
    onBackPressed: () -> Unit,
    modifier: Modifier = Modifier
) {
    BackHandler {
        onBackPressed()
    }
... 

5. Exécuter une application sur des 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. Dans Android Studio, sélectionnez Tools > Device Manager (Outil > Gestionnaire d'appareils).

Le menu &quot;Outils&quot; affiche la liste des options. Le gestionnaire d&#39;appareils, qui figure à la moitié de la liste, est sélectionné.

  1. Dans Device Manager (Gestionnaire d'appareils), cliquez sur l'icône + pour créer un appareil virtuel.

La barre d&#39;outils du gestionnaire d&#39;appareils affiche deux options de menu, dont l&#39;option &quot;Create virtual device&quot; (Créer un appareil virtuel).

  1. Sélectionnez la catégorie Phone (Téléphone) et l'appareil Resizable (Expermiental) (Redimensionnable [expérimental]).
  2. Cliquez sur Next (Suivant).

La fenêtre &quot;Gestionnaire d&#39;appareils&quot; vous invite à sélectionner une définition d&#39;appareil. Une liste d&#39;options s&#39;affiche avec un champ de recherche juste au-dessus. La catégorie

  1. Sélectionnez API Level 34 (niveau d'API 34) ou un niveau supérieur.
  2. Cliquez sur Next (Suivant).

La fenêtre &quot;Virtual Device Configuration&quot; (Configuration d&#39;appareils virtuels) affiche une invite vous permettant de sélectionner une image système. Le niveau d&#39;API 34 est sélectionné.

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

L&#39;écran de configuration d&#39;appareils virtuels Android s&#39;affiche. L&#39;écran de configuration comprend un champ de texte permettant de saisir le nom de l&#39;appareil virtuel Android. Sous le champ de nom se trouve la liste des options d&#39;appareil, y compris la définition de l&#39;appareil (Redimensionnable expérimental), l&#39;image système (Tiramisu) et l&#39;orientation (l&#39;orientation Portrait est sélectionnée par défaut). Lecture des boutons

Exécuter l'application sur un émulateur avec 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 Tablet (Tablette) comme mode d'affichage.

bfacf9c20a30b06b.png

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

bb0fa5e954f6ca4b.png

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 appareils 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.

f50e77a4ffd923a.png

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&#39;arrêt (en dp) pour différents types et configurations d&#39;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&#39;espacement sont flexibles et n&#39;ont pas besoin d&#39;être de taille égale. De plus, les téléphones en mode Paysage qui entrent dans la plage de points d&#39;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.kts du module.

build.gradle.kts

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

b4c912a45fa8b7f4.png

Maintenant que le fichier build.gradle.kts 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 layoutDirection = LocalLayoutDirection.current
            Surface (
               // ...
            ) {
                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().

f8029f61dfad0306.png

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 {
                Surface {
                    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 ReplyAppCompactPreview() {
    ReplyTheme {
        Surface {
            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.

f39984211e4dd665.png

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&#39;affichent. La largeur compacte affiche une barre de navigation inférieure en bas de l&#39;é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.

1c73d20ace67811c.png

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.

6795fb31e6d4a564.png

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
...
    val navigationType: 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
...
@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 = {
                PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
                    NavigationDrawerContent(
                        selectedDestination = replyUiState.currentMailbox,
                        onTabPressed = onTabPressed,
                        navigationItemContentList = navigationItemContentList,
                        modifier = Modifier
                            .wrapContentWidth()
                            .fillMaxHeight()
                            .background(MaterialTheme.colorScheme.inverseOnSurface)
                            .padding(dimensionResource(R.dimen.drawer_padding_content))
                    )
                }
            }
        ) {
            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 = {
                PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
                    NavigationDrawerContent(
                        selectedDestination = replyUiState.currentMailbox,
                        onTabPressed = onTabPressed,
                        navigationItemContentList = navigationItemContentList,
                        modifier = Modifier
                            .wrapContentWidth()
                            .fillMaxHeight()
                            .background(MaterialTheme.colorScheme.inverseOnSurface)
                            .padding(dimensionResource(R.dimen.drawer_padding_content))
                    )
                }
            }
        ) {
            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. Exécutez l'application en mode Tablette. L'écran suivant devrait s'afficher :

2dbbc2f88d08f6a.png

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 visible sur true si la valeur ReplyNavigationType correspond à NAVIGATION_RAIL.

ReplyHomeScreen.kt

...
@Composable
private fun ReplyAppContent(
    navigationType: ReplyNavigationType,
    replyUiState: ReplyUiState,
    onTabPressed: ((MailboxType) -> Unit),
    onEmailCardPressed: (Email) -> Unit,
    navigationItemContentList: List<NavigationItemContent>,
    modifier: Modifier = Modifier
) {
    Box(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)
                    .padding(
                        horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
                    )
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList,
                  modifier = Modifier
                      .fillMaxWidth()
            )
        }
    }
}     
... 
  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) {
        AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
            val navigationRailContentDescription = stringResource(R.string.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)
                    .padding(
                        horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
                )
            )
            ReplyBottomNavigationBar(
                currentTab = replyUiState.currentMailbox,
                onTabPressed = onTabPressed,
                navigationItemContentList = navigationItemContentList,
                modifier = Modifier
                    .fillMaxWidth()
            )
        }
    }
}

... 

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)
        .padding(
            horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
        )

)
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
    val bottomNavigationContentDescription = stringResource(R.string.navigation_bottom)
    ReplyBottomNavigationBar(
        currentTab = replyUiState.currentMailbox,
        onTabPressed = onTabPressed,
        navigationItemContentList = navigationItemContentList,
        modifier = Modifier
            .fillMaxWidth()
    )
}

... 
  1. Exécutez l'application en mode Pliable non plié. L'écran suivant devrait s'afficher :

bfacf9c20a30b06b.png

8. Télécharger le code de solution

Pour télécharger le code de cet atelier de programmation terminé, utilisez les commandes Git suivantes :

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