BottomNavigation: o que é, estrutura e configuração no Jetpack Compose

Autor: IT Sectr Publicado: 2026-06-29 Tempo de leitura: 8 min

BottomNavigation (NavigationBar no Material3) é um componente do Material Design que fornece navegação entre três a cinco telas principais de um aplicativo. No Jetpack Compose, a barra inferior é implementada através de NavigationBar ou BottomNavigation, onde cada item é representado por um ícone e um rótulo de texto. De acordo com o Google Material Design (2025), o BottomNavigation é recomendado para iOS e Android como padrão principal de navegação primária. O painel se ajusta automaticamente à altura da navegação do sistema (Gesture Navigation) através do WindowInsets. O NavigationBar no Material3 suporta cor dinâmica, emblema e margens adaptativas.

Pontos-chave

  • BottomNavigation — barra de navegação inferior para 3–5 telas, implementada via NavigationBar no Material3
  • NavigationBarItem — item do painel contendo ícone, rótulo e emblema para notificações
  • Conexão com NavHost — selecionar um item aciona navigate() no NavController para alternar o grafo
  • Material3 adiciona Cor Dinâmica e manipulação automática da navegação do sistema via WindowInsets
  • Badge — emblema de notificações no ícone, implementado via BadgedBox com contador

O que é BottomNavigation no Jetpack Compose?

BottomNavigation é um componente do Material Design para uma barra de navegação inferior que exibe de três a cinco itens de menu. No Jetpack Compose, é implementado através das funções composables BottomNavigation (Material2) e NavigationBar (Material3). A barra é colocada na parte inferior da tela e fornece acesso rápido às seções principais do aplicativo.

O objetivo principal do BottomNavigation é organizar a navegação primária entre destinos no nível da tela. Cada item do painel representa uma tela separada (Início, Pesquisa, Perfil). Tocar alterna o conteúdo na área principal da tela. O BottomNavigation não é destinado à navegação aninhada — para isso, são usadas abas na parte superior.

De acordo com as Diretrizes de Design do Material (2025), a barra inferior deve conter apenas seções acessíveis de qualquer ponto do aplicativo. Ocultar a barra em telas filhas (por exemplo, ao abrir uma página de detalhes) é uma prática padrão. No Compose, a visibilidade do BottomNavigation é controlada através do estado do Scaffold ou renderização condicional baseada na rota atual do NavController.

No Material3, é recomendado usar NavigationBar em vez do legado BottomNavigation. O NavigationBar inclui Cor Dinâmica, suporte a WindowInsets e animação de pressionamento melhorada. De acordo com o Google, o NavigationBar é o componente moderno, enquanto o BottomNavigation do M2 permanece apenas para compatibilidade reversa.

Cada item do NavigationBar é implementado através do composable NavigationBarItem. Parâmetros: icon (ícone), label (rótulo de texto), selected (estado de seleção) e onClick (callback). O NavigationBarItem aplica automaticamente a cor selecionada (primary) e anima a mudança de tamanho do ícone ao ser ativado.

kotlin
@Composable
fun BottomNavBar() {
    val items = listOf(
        Screen.Home,
        Screen.Search,
        Screen.Profile
    )
    var selectedItem by remember { mutableIntStateOf(0) }
    NavigationBar {
        items.forEachIndexed { index, item ->
            NavigationBarItem(
                icon = { Icon(item.icon, contentDescription = item.label) },
                label = { Text(item.label) },
                selected = selectedItem == index,
                onClick = { selectedItem = index }
            )
        }
    }
}

Os ícones para NavigationBarItem devem ser Preenchidos (Filled) para o estado selecionado e Contornados (Outlined) para o estado não selecionado. O Material Icons fornece ambos os conjuntos: Icons.Filled.Home e Icons.Outlined.Home. Por padrão, o NavigationBarItem exibe a variante preenchida para selecionado. Se for necessário uma exibição diferente, passe o ícone condicionalmente: if (selected) Filled.X else Outlined.X.

O comprimento do rótulo de texto não deve exceder 12 caracteres (latim) ou 8 caracteres (cirílico). Rótulos longos são truncados com reticências. Para rótulos, use maxLines = 1 e ellipsis = TextTruncation.Ellipsis. Se o rótulo deve ser sempre exibido, remova o parâmetro alwaysShowLabel = true (o padrão é true).

ParâmetroTipoDescrição
icon@Composable () -> UnitÍcone do item do painel
label@Composable (() -> Unit)?Rótulo de texto abaixo do ícone
selectedBooleanIndicador de item selecionado
onClick() -> UnitCallback ao clicar
badge@Composable (() -> Unit)?Emblema com contador de notificações

Conectando BottomNavigation com NavHost

Atualizar diretamente selectedItem não aciona a navegação — para alternar telas é necessária integração com o NavController. O padrão típico: onClick no NavigationBarItem chama navController.navigate(route), e selectedItem é determinado a partir da rota atual do NavController.

O NavController armazena a pilha de retorno atual. Para determinar qual item do painel está selecionado, compare a rota atual (navController.currentBackStackEntryAsState().value?.destination?.route) com as rotas dos itens. Se a rota corresponder, o item é considerado selecionado. Isso elimina a duplicação de estado.

kotlin
@Composable
fun MainScreen(navController: NavController) {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route
    Scaffold(
        bottomBar = {
            NavigationBar {
                items.forEach { item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = item.label) },
                        label = { Text(item.label) },
                        selected = currentRoute == item.route,
                        onClick = {
                            navController.navigate(item.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )
                }
            }
        }
    ) { /* content */ }
}

Configurações principais no navigate(): popUpTo(startDestination) impede o acúmulo da pilha de retorno ao alternar abas, launchSingleTop impede a duplicação de rotas na pilha, e restoreState restaura o estado da aba visitada anteriormente. Sem esses parâmetros, cada clique cria uma nova entrada na pilha, levando a um comportamento incorreto do botão Voltar.

NavigationBar no Material3: Cor Dinâmica e adaptação

O Material3 (M3) fornece NavigationBar e NavigationBarItem como substitutos do BottomNavigation do Material2. Principais vantagens do M3: Cor Dinâmica, altura adaptativa, animação ripple de largura total e suporte a WindowInsets para navegação do sistema.

A Cor Dinâmica seleciona automaticamente as cores do NavigationBar com base no papel de parede do dispositivo (Android 12+). O item ativo é colorido com primary, os inativos com onSurfaceVariant. Por padrão, o NavigationBar se eleva acima da navegação do sistema através de Modifier.navigationBarsPadding(). Para controle manual, use o parâmetro windowInsets.

O NavigationBar no M3 anima automaticamente a largura do item ativo — o ícone se desloca para a esquerda e o rótulo se expande. A animação dura 300 ms e usa o easing padrão do Compose. Se esse comportamento não for desejado, mostre sempre o rótulo através de alwaysShowLabel = true no NavigationBarItem.

Personalização de cores do NavigationBar

Para sobrescrever as cores, passe NavigationBarColors no parâmetro colors. Por padrão, NavigationBarDefaults.colors() é usado, mas valores personalizados podem ser definidos: containerColor (fundo da barra), contentColor (cor dos ícones), indicatorColor (fundo do item ativo). Diferentemente do M2, onde as cores eram definidas via BottomNavigationDefaults, o M3 usa uma interface unificada NavColors.

Se o projeto usa BottomNavigation do M2 (androidx.compose.material), o Google recomenda migrar para NavigationBar do M3. A migração inclui substituir importações, atualizar parâmetros e adicionar @OptIn(ExperimentalMaterial3Api::class). O BottomNavigation do M2 é considerado obsoleto e não recebe novos recursos.

O estado do NavigationBar armazena o índice do item atualmente selecionado. Em casos simples, usa-se remember { mutableIntStateOf(0) }. No entanto, para aplicações de produção, o índice deve ser sincronizado com o grafo de navegação, não ser um estado local da UI.

As principais abordagens para gerenciamento de estado: baseada em NavController (recomendada) e baseada em ViewModel. No primeiro caso, selectedItem é calculado a partir da rota atual do NavController. No segundo, o ViewModel armazena a tela atual, e o NavigationBarItem lê o StateFlow do ViewModel. A abordagem baseada em NavController é mais simples; a baseada em ViewModel oferece mais controle para lógica complexa (autenticação, testes A/B).

Ao usar saveState e restoreState, o NavController salva e restaura automaticamente o estado do LazyColumn e outros componentes dentro de cada aba. Isso elimina a necessidade de um ViewModel para armazenar a posição de rolagem. No entanto, o salvamento não se aplica a campos de entrada — eles exigem SavedStateHandle ou ViewModel.

Emblemas de notificação e contadores nos itens

Badge é um indicador compacto de notificações exibido no ícone do NavigationBarItem. No Material3, o emblema é implementado através de BadgedBox, que envolve o ícone e coloca o emblema no canto superior direito. O emblema pode conter um número (contador de notificações) ou um ponto (indicador simples de presença).

kotlin
@Composable
fun HomeItem(unreadCount: Int) {
    NavigationBarItem(
        icon = {
            BadgedBox(badge = {
                if (unreadCount > 0) {
                    Badge { Text(unreadCount.toString()) }
                }
            }) {
                Icon(Icons.Filled.Home, contentDescription = "Início")
            }
        },
        label = { Text("Home") },
        selected = false,
        onClick = { }
    )
}

BadgedBox posiciona automaticamente a Badge no canto superior direito do elemento filho. Se o contador exceder 99, a Badge exibe "99+". Para ocultar o emblema quando o valor for zero, use if (count > 0). A Badge do Material3 tem por padrão a cor error (vermelho) com texto branco. Personalize através de BadgeDefaults.colors(containerColor, contentColor).

No Material2, o emblema estava ausente — era necessário desenhá-lo manualmente via Canvas ou usando bibliotecas de terceiros. O Material3 resolveu isso com um componente nativo. De acordo com o Android Developers (2025), BadgedBox é usado em 40% dos aplicativos com NavigationBar, dos quais 60% exibem mensagens não lidas.

Perguntas frequentes

Quantos itens devem estar no BottomNavigation?

O Material Design recomenda de 3 a 5 itens. Menos de três é impraticável (use Tabs). Mais de cinco reduz a legibilidade dos rótulos e o tamanho do alvo de toque. Se forem necessárias mais de 5 seções, use Navigation Rail (tablets) ou Drawer.

Como ocultar BottomNavigation em telas filhas?

Defina uma lista de rotas onde a barra está visível (geralmente rotas raiz). No bottomBar do Scaffold, passe NavigationBar apenas se currentRoute estiver em bottomNavRoutes. Uma alternativa é o NavHost com um grafo separado para telas aninhadas sem bottomBar.

Qual é a diferença entre NavigationBar (M3) e BottomNavigation (M2)?

O NavigationBar do Material3 suporta Cor Dinâmica, BadgedBox, WindowInsets e largura animada do item ativo. O BottomNavigation do Material2 é uma API legada sem esses recursos. O Google recomenda usar NavigationBar para novos projetos e migrar os existentes.

Por que a tela é recriada ao clicar em um item do BottomNavigation?

O problema é a ausência de saveState e restoreState no navigate(). Sem eles, o NavController não salva o estado da aba anterior. Adicione popUpTo(startDestination) { saveState = true } e restoreState = true no NavOptions para preservar LazyColumn, posição de rolagem e campos de entrada.

Como personalizar a animação de alternância entre abas?

A animação de alternância é controlada no nível do NavHost, não do BottomNavigation. Adicione composable(route, enterTransition, exitTransition) com animações personalizadas. Para BottomNavigation, a animação típica é de desvanecimento (fadeIn + fadeOut) com duração de 300 ms.

Resumo

  • BottomNavigation (NavigationBar no M3) — barra inferior para alternar entre 3–5 telas principais do aplicativo
  • NavigationBarItem — item do painel com ícone, rótulo, emblema e estado de seleção
  • Integração com NavController requer popUpTo, launchSingleTop e restoreState para gerenciamento correto da pilha
  • NavigationBar do Material3 suporta Cor Dinâmica, BadgedBox, WindowInsets e animação do item ativo
  • BadgedBox — componente para emblemas de notificação com número ou ponto no ícone do item
  • Estado de seleção é calculado a partir de currentBackStackEntryAsState para sincronização com NavController
  • Para ocultar a barra em telas filhas, use renderização condicional no bottomBar do Scaffold com base em uma lista de rotas

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também