Creare una navigazione adattiva

La maggior parte delle app ha alcune destinazioni di primo livello accessibili tramite l'interfaccia utente di navigazione principale dell'app. Nelle finestre compatte, come il display di uno smartphone standard, le destinazioni vengono in genere visualizzate in una barra di navigazione nella parte inferiore della finestra. In una finestra espansa, ad esempio un'app a schermo intero su un tablet, una barra di navigazione accanto all'app è di solito una scelta migliore poiché i controlli di navigazione sono più facili da raggiungere tenendo i lati sinistro e destro del dispositivo.

NavigationSuiteScaffold semplifica il passaggio tra le UI di navigazione mostrando il composable dell'interfaccia di navigazione appropriato in base a WindowSizeClass. Sono incluse le modifiche dinamiche dell'interfaccia utente durante le modifiche delle dimensioni della finestra in fase di esecuzione. Il comportamento predefinito è mostrare uno dei seguenti componenti dell'interfaccia utente:

  • Barra di navigazione se la larghezza o l'altezza sono compatte o se il dispositivo è in posizione da tavolo
  • Barra di navigazione per tutto il resto
Figura 1. NavigationSuiteScaffold mostra una barra di navigazione nelle finestre compatte.
Figura 2. NavigationSuiteScaffold mostra una barra di navigazione nelle finestre espanse.

Aggiungi dipendenze

NavigationSuiteScaffold fa parte della raccolta della suite di navigazione adattiva Material3. Aggiungi una dipendenza per la libreria nel file build.gradle della tua app o del tuo modulo:

Kotlin

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

Groovy

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

Creare un'impalcatura

Le due parti principali di NavigationSuiteScaffold sono gli elementi della suite di navigazione e i contenuti per la destinazione selezionata. Puoi definire direttamente gli elementi della suite di navigazione in un composable, ma è comune definirli altrove, ad esempio in un enum:

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),
}

Per utilizzare NavigationSuiteScaffold, devi monitorare la destinazione corrente, cosa che puoi fare utilizzando rememberSaveable:

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

Nell'esempio seguente, il parametro navigationSuiteItems (tipo NavigationSuiteScope utilizza la relativa funzione item per definire l'interfaccia utente di navigazione per una singola destinazione. L'interfaccia utente di destinazione viene utilizzata nelle barre di navigazione, nelle barre laterali e nei riquadri. Per creare elementi di navigazione, esegui un ciclo su AppDestinations (definito nello snippet precedente):

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

All'interno della lambda dei contenuti di destinazione, utilizza il valore currentDestination per decidere quale UI visualizzare. Se nella tua app utilizzi una libreria di navigazione, utilizzala qui per visualizzare la destinazione appropriata. Una dichiarazione when può essere sufficiente:

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

Cambia colori

NavigationSuiteScaffold crea un Surface sull'intera area occupata dall'apposita struttura, in genere l'intera finestra. Inoltre, lo scafo disegna la particolare interfaccia utente di navigazione, ad esempio un NavigationBar. Sia l'interfaccia utente della visualizzazione sia quella di navigazione utilizzano i valori specificati nel tema dell'app, ma puoi sostituirli.

Il parametro containerColor specifica il colore della superficie. Il valore predefinito è il colore di sfondo della combinazione di colori. Il parametro contentColor specifica il colore dei contenuti su quella superficie. Il valore predefinito è il colore "on" di ciò che è specificato per containerColor. Ad esempio, se containerColor utilizza il colore background, contentColor utilizza il colore onBackground. Per ulteriori dettagli sul funzionamento del sistema di colori, consulta Definizione dei temi di Material Design 3 in Compose. Quando sostituisci questi valori, utilizza i valori definiti nel tema in modo che l'app supporti le modalità di visualizzazione Buio e Luce:

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

L'interfaccia utente di navigazione viene disegnata davanti alla superficie NavigationSuiteScaffold. I valori predefiniti per i colori dell'interfaccia utente sono forniti da NavigationSuiteDefaults.colors(), ma puoi anche sostituire questi valori. Ad esempio, se vuoi che lo sfondo della barra di navigazione sia trasparente, ma che gli altri valori siano quelli predefiniti, override navigationBarContainerColor:

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

In definitiva, puoi personalizzare ogni elemento nell'interfaccia utente di navigazione. Quando chiami la funzione item, puoi passare un'istanza di NavigationSuiteItemColors. La classe specifica i colori degli elementi di una barra di navigazione, di un riquadro di navigazione e di un riquadro scorrevole di navigazione. Ciò significa che puoi avere colori identici in ogni tipo di UI di navigazione oppure puoi variare i colori in base alle tue esigenze. Definisci i colori a livello di NavigationSuiteScaffold per utilizzare la stessa istanza di oggetto per tutti gli elementi e chiama la funzione NavigationSuiteDefaults.itemColors() per sostituire solo quelli che vuoi modificare:

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

Personalizzare i tipi di navigazione

Il comportamento predefinito di NavigationSuiteScaffold modifica l'interfaccia utente di navigazione in base alle classi di dimensioni della finestra. Tuttavia, potresti voler sovrascrivere questo comportamento. Ad esempio, se la tua app mostra un singolo riquadro di contenuti di grandi dimensioni per un feed, potrebbe utilizzare un riquadro di navigazione permanente per le finestre espanse, ma ripristinare il comportamento predefinito per le classi di dimensioni delle finestre compatte e medie:

val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
    if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

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

Risorse aggiuntive