Créer une navigation adaptative

La plupart des applications comportent 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 basculement entre les interfaces utilisateur de navigation en affichant le composable d'interface utilisateur de navigation approprié en fonction de WindowSizeClass. Cela inclut la modification dynamique de l'interface utilisateur lors des changements de taille de la fenêtre au moment de l'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 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 agrandies.

Ajouter des dépendances

NavigationSuiteScaffold fait partie de la bibliothèque de suite de navigation adaptative Material3. Ajoutez une dépendance pour la bibliothèque dans le fichier build.gradle de votre application ou 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 navigationSuiteItems paramètre (type NavigationSuiteScope) utilise sa item fonction pour définir l'interface utilisateur de navigation pour une destination individuelle. L'interface utilisateur de destination est utilisée dans les barres de navigation, les rails et les panneaux. Pour créer des éléments de navigation, vous parcourez vos AppDestinations (définis 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'interface utilisateur à 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 une Surface sur toute la zone occupée par l'échafaudage, généralement la fenêtre entière. De plus, l'échafaudage dessine l'interface utilisateur de navigation particulière, telle qu'une NavigationBar. La surface et l'interface utilisateur 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 valeur par défaut est la couleur d'arrière-plan de votre palette 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 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èmes Material Design 3 dans Compose. Lorsque vous remplacez ces valeurs, utilisez des valeurs définies dans votre thème afin que votre application soit compatible avec les modes d'affichage clair et sombre :

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

L'interface utilisateur 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 remplacer ces valeurs. 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 item fonction, 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'interface utilisateur de navigation ou les varier en fonction de vos besoins. Définissez les couleurs au niveau NavigationSuiteScaffold pour utiliser la même instance d'objet pour tous les éléments et appelez la fonction NavigationSuiteDefaults.itemColors() pour ne remplacer que ceux 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'interface utilisateur de navigation en fonction des classes de taille de fenêtre. Toutefois, vous pouvez remplacer 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 agrandies, mais revenir au comportement par défaut pour les classes de taille de fenêtre compactes et moyennes :

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