Créer une navigation adaptative

La plupart des applications ont quelques destinations de premier niveau accessibles via l'interface utilisateur de navigation principale de l'application. Dans les fenêtres compactes, comme l'écran d'un téléphone standard, les destinations sont généralement affichées dans une barre de navigation en bas de la fenêtre. Dans une fenêtre agrandie, comme une application en plein écran sur une tablette, un rail de navigation à côté de l'application est généralement un meilleur choix, car les commandes de navigation sont plus faciles à atteindre lorsque vous tenez les côtés gauche et droit de l'appareil.

NavigationSuiteScaffold simplifie le passage d'une UI de navigation à une autre en affichant le composable d'UI de navigation approprié en fonction de WindowSizeClass. Cela inclut la modification dynamique de l'UI lors des changements de taille de la fenêtre d'exécution. Le comportement par défaut consiste à afficher l'un des composants d'interface utilisateur suivants :

  • Barre de navigation si la largeur ou la hauteur est compacte, ou si l'appareil est en position écran à plat
  • Rail de navigation pour tout le reste
Figure 1. NavigationSuiteScaffold affiche une barre de navigation dans les fenêtres compactes.
Figure 2. NavigationSuiteScaffold affiche un rail de navigation dans les fenêtres de grande taille.

Ajouter des dépendances

NavigationSuiteScaffold fait partie de la bibliothèque Material3 adaptive navigation suite. Ajoutez une dépendance pour la bibliothèque dans le fichier build.gradle de votre application ou de votre module :

Kotlin

implementation("androidx.compose.material3:material3-adaptive-navigation-suite")

Groovy

implementation 'androidx.compose.material3:material3-adaptive-navigation-suite'

Créer un échafaudage

Les deux parties principales de NavigationSuiteScaffold sont les éléments de la suite de navigation et le contenu de la destination sélectionnée. Vous pouvez définir directement les éléments de la suite de navigation dans un composable, mais il est courant de les définir ailleurs, par exemple dans une énumération :

enum class AppDestinations(
    @StringRes val label: Int,
    val icon: ImageVector,
    @StringRes val contentDescription: Int
) {
    HOME(R.string.home, Icons.Default.Home, R.string.home),
    FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
    SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
    PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile),
}

Pour utiliser NavigationSuiteScaffold, vous devez suivre la destination actuelle, ce que vous pouvez faire à l'aide de rememberSaveable :

var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }

Dans l'exemple suivant, le paramètre navigationSuiteItems (type NavigationSuiteScope) utilise sa fonction item pour définir l'UI de navigation d'une destination individuelle. L'UI de destination est utilisée dans les barres, rails et panneaux de navigation. Pour créer des éléments de navigation, vous devez effectuer une boucle sur votre AppDestinations (défini dans l'extrait précédent) :

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it }
            )
        }
    }
) {
    // TODO: Destination content.
}

Dans le lambda du contenu de la destination, utilisez la valeur currentDestination pour décider de l'UI à afficher. Si vous utilisez une bibliothèque de navigation dans votre application, utilisez-la ici pour afficher la destination appropriée. Une instruction "when" peut suffire :

NavigationSuiteScaffold(
    navigationSuiteItems = { /*...*/ }
) {
    // Destination content.
    when (currentDestination) {
        AppDestinations.HOME -> HomeDestination()
        AppDestinations.FAVORITES -> FavoritesDestination()
        AppDestinations.SHOPPING -> ShoppingDestination()
        AppDestinations.PROFILE -> ProfileDestination()
    }
}

Modifier les couleurs

NavigationSuiteScaffold crée un Surface sur toute la zone occupée par l'échafaudage, généralement la fenêtre entière. De plus, l'échafaudage dessine l'UI de navigation spécifique, telle qu'un NavigationBar. La surface et l'UI de navigation utilisent les valeurs spécifiées dans le thème de votre application, mais vous pouvez les remplacer.

Le paramètre containerColor spécifie la couleur de la surface. La valeur par défaut est la couleur d'arrière-plan de votre jeu de couleurs. Le paramètre contentColor spécifie la couleur du contenu sur cette surface. La valeur par défaut est la couleur "on" de l'élément spécifié pour containerColor. Par exemple, si containerColor utilise la couleur background, contentColor utilise la couleur onBackground. Pour en savoir plus sur le fonctionnement du système de couleurs, consultez Thématisation Material Design 3 dans Compose. Lorsque vous remplacez ces valeurs, utilisez celles définies dans votre thème afin que votre application soit compatible avec les modes d'affichage sombre et clair :

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    containerColor = MaterialTheme.colorScheme.primary,
    contentColor = MaterialTheme.colorScheme.onPrimary,
) {
    // Content...
}

L'UI de navigation est dessinée devant la surface NavigationSuiteScaffold. Les valeurs par défaut des couleurs de l'interface utilisateur sont fournies par NavigationSuiteDefaults.colors(), mais vous pouvez également les remplacer. Par exemple, si vous souhaitez que l'arrière-plan de la barre de navigation soit transparent, mais que les autres valeurs soient celles par défaut, remplacez navigationBarContainerColor :

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    navigationSuiteColors = NavigationSuiteDefaults.colors(
        navigationBarContainerColor = Color.Transparent,
    )
) {
    // Content...
}

Enfin, vous pouvez personnaliser chaque élément de l'interface utilisateur de navigation. Lorsque vous appelez la fonction item, vous pouvez transmettre une instance de NavigationSuiteItemColors. La classe spécifie les couleurs des éléments d'une barre de navigation, d'un rail de navigation et d'un panneau de navigation. Cela signifie que vous pouvez utiliser des couleurs identiques pour chaque type d'UI de navigation ou varier les couleurs en fonction de vos besoins. Définissez les couleurs au niveau de NavigationSuiteScaffold pour utiliser la même instance d'objet pour tous les éléments, puis appelez la fonction NavigationSuiteDefaults.itemColors() pour ne remplacer que celles que vous souhaitez modifier :

val myNavigationSuiteItemColors = NavigationSuiteDefaults.itemColors(
    navigationBarItemColors = NavigationBarItemDefaults.colors(
        indicatorColor = MaterialTheme.colorScheme.primaryContainer,
        selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer
    ),
)

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it },
                colors = myNavigationSuiteItemColors,
            )
        }
    },
) {
    // Content...
}

Personnaliser les types de navigation

Le comportement par défaut de NavigationSuiteScaffold modifie l'UI de navigation en fonction des classes de taille de fenêtre. Toutefois, vous pouvez ignorer ce comportement. Par exemple, si votre application affiche un seul grand volet de contenu pour un flux, elle peut utiliser un panneau de navigation permanent pour les fenêtres développées, mais revenir au comportement par défaut pour les classes de taille de fenêtre compacte et moyenne :

val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
    if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND)) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    layoutType = customNavSuiteType,
) {
    // Content...
}

Ressources supplémentaires