Créer une navigation adaptative

La plupart des applications comportent quelques destinations de niveau supérieur accessibles via l'UI 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, une barre 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 changement d'UI de navigation 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 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 sur une table
  • 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 développées.

Ajouter des dépendances

NavigationSuiteScaffold fait partie de la bibliothèque de la suite de navigation adaptative Material 3. 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 principales parties 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 un é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 spécifique. 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 de contenu de destination, utilisez la valeur currentDestination pour déterminer 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 dans les cas suivants:

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 l'ensemble de la zone occupée par l'échafaudage, généralement la fenêtre complète. De plus, l'échafaudage dessine l'UI de navigation spécifique, comme 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 remplacer les valeurs du thème.

Le paramètre containerColor spécifie la couleur de la surface. La couleur par défaut est celle de l'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 "marche" de ce qui est 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 la section Thématisation Material Design 3 dans Compose. Lorsque vous remplacez ces valeurs, utilisez les valeurs définies dans votre thème afin que votre application prenne en charge 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 les valeurs par défaut, remplacez navigationBarContainerColor:

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

Vous pouvez finalement personnaliser chaque élément de l'UI 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 avoir des couleurs identiques pour chaque type d'UI de navigation, ou que vous pouvez 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 et 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.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

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

Ressources supplémentaires