Criar uma navegação adaptável

A maioria dos apps tem alguns destinos de nível superior que podem ser acessados pela interface de navegação principal do app. Em janelas compactas, como uma tela de smartphone padrão, os destinos geralmente são exibidos em uma barra de navegação na parte de baixo da janela. Em uma janela expandida, como um app em tela cheia em um tablet, uma coluna de navegação ao lado do app geralmente é uma opção melhor, já que os controles de navegação são mais fáceis de alcançar ao segurar os lados esquerdo e direito do dispositivo.

NavigationSuiteScaffold simplifica a troca entre interfaces de navegação mostrando o elemento combinável de interface de navegação apropriado com base em WindowSizeClass. Isso inclui a mudança dinâmica da interface durante as mudanças de tamanho da janela de execução. O comportamento padrão é mostrar um dos seguintes componentes de interface:

  • Barra de navegação se a largura ou altura for compacta ou se o dispositivo estiver na posição de mesa
  • Coluna de navegação para todo o resto
Figura 1. NavigationSuiteScaffold mostra uma barra de navegação em janelas compactas.
Figura 2. NavigationSuiteScaffold mostra uma coluna de navegação em janelas expandidas.

Adicionar dependências

NavigationSuiteScaffold faz parte da biblioteca de navegação adaptável do Material 3. Adicione uma dependência da biblioteca no arquivo build.gradle do app ou módulo:

Kotlin

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

Groovy

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

Criar um scaffold

As duas partes principais de NavigationSuiteScaffold são os itens do conjunto de navegação e o conteúdo do destino selecionado. É possível definir diretamente os itens do conjunto de navegação em um elemento combinável, mas é comum que eles sejam definidos em outro lugar, por exemplo, em uma enumeração:

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

Para usar NavigationSuiteScaffold, é necessário monitorar o destino atual, o que pode ser feito usando rememberSaveable:

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

No exemplo a seguir, o parâmetro navigationSuiteItems (tipo NavigationSuiteScope) usa a função item para definir a interface de navegação de um destino individual. A interface de destino é usada em barras de navegação, colunas e gavetas. Para criar itens de navegação, faça um loop nas AppDestinations (definidas no snippet anterior):

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

Na lambda de conteúdo de destino, use o valor currentDestination para decidir qual interface mostrar. Se você usar uma biblioteca de navegação no app, use-a aqui para mostrar o destino apropriado. Uma instrução "when" pode ser suficiente:

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

Mudar cores

NavigationSuiteScaffold cria uma Surface em toda a área ocupada pelo scaffold, normalmente a janela inteira. Além disso, o scaffold desenha a interface de navegação específica, como um NavigationBar. A superfície e a interface de navegação usam os valores especificados no tema do app, mas é possível substituir os valores do tema.

O parâmetro containerColor especifica a cor da superfície. O padrão é a cor de plano de fundo do esquema de cores. O parâmetro contentColor especifica a cor do conteúdo na superfície. O padrão é a cor "on" do que for especificado para containerColor. Por exemplo, se containerColor usar a cor background, contentColor usará a cor onBackground. Consulte o tema do Material Design 3 no Compose para mais detalhes sobre como o sistema de cores funciona. Ao substituir esses valores, use valores definidos no tema para que o app seja compatível com os modos de exibição claro e escuro:

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

A interface de navegação é desenhada na frente da superfície NavigationSuiteScaffold. Os valores padrão das cores da interface são fornecidos por NavigationSuiteDefaults.colors(), mas você também pode substituir esses valores. Por exemplo, se você quiser que o plano de fundo da barra de navegação seja transparente, mas que os outros valores sejam os padrões, substitua navigationBarContainerColor:

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

Por fim, é possível personalizar cada item na interface de navegação. Ao chamar a item função, você pode transmitir uma instância de NavigationSuiteItemColors. A classe especifica as cores dos itens em uma barra de navegação, coluna de navegação e gaveta de navegação. Isso significa que você pode ter cores idênticas em cada tipo de interface de navegação ou variar as cores com base nas suas necessidades. Defina as cores no nível NavigationSuiteScaffold para usar a mesma instância de objeto para todos os itens e chame a função NavigationSuiteDefaults.itemColors() para substituir apenas aqueles que você quer mudar:

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

Personalizar tipos de navegação

O comportamento padrão de NavigationSuiteScaffold muda a interface de navegação com base nas classes de tamanho da janela. No entanto, talvez você queira substituir esse comportamento. Por exemplo, se o app mostrar um único painel grande de conteúdo para um feed, ele poderá usar uma gaveta de navegação permanente para janelas expandidas, mas ainda voltará ao comportamento padrão para classes de tamanho de janela compactas e médias:

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

Outros recursos