BottomNavigation (NavigationBar in Material3) is een Material Design-component die navigatie biedt tussen drie tot vijf hoofdschermen van de applicatie. In Jetpack Compose wordt het onderste paneel geïmplementeerd via NavigationBar of BottomNavigation, waarbij elk element wordt weergegeven door een pictogram en een tekstlabel. Volgens Google Material Design (2025) wordt BottomNavigation aanbevolen voor iOS en Android als het primaire navigatiepatroon. Het paneel past zich automatisch aan de hoogte van de systeemnavigatie (Gesture Navigation) aan via WindowInsets. NavigationBar in Material3 ondersteunt dynamic color, badge en adaptieve marges.
Belangrijkste punten
BottomNavigation is een Material Design-component voor het onderste navigatiepaneel dat drie tot vijf menupunten weergeeft. In Jetpack Compose wordt het geïmplementeerd via de composable-functies BottomNavigation (Material2) en NavigationBar (Material3). Het paneel bevindt zich onderaan het scherm en biedt snelle toegang tot de belangrijkste secties van de applicatie.
De hoofdtaak van BottomNavigation is het organiseren van primaire navigatie tussen schermniveaus. Elk panelelement vertegenwoordigt een apart scherm (Home, Search, Profile). Door te tikken wordt de inhoud in het hoofdgedeelte van het scherm gewisseld. BottomNavigation is niet bedoeld voor geneste navigatie — daarvoor worden tabbladen (Tabs) aan de bovenkant gebruikt.
Volgens Material Design Guidelines (2025) mag het onderste paneel alleen secties bevatten die vanuit elk punt van de applicatie toegankelijk zijn. Het verbergen van het paneel op onderliggende schermen (bijv. bij het openen van een detailpagina) is standaardpraktijk. In Compose wordt de zichtbaarheid van BottomNavigation beheerd via de Scaffold-status of voorwaardelijke weergave op basis van de huidige NavController-route.
In Material3 wordt aanbevolen NavigationBar te gebruiken in plaats van Legacy BottomNavigation. NavigationBar bevat Dynamic Color, ondersteuning voor WindowInsets en verbeterde tikanimatie. Volgens Google is NavigationBar een toekomstgerichte component, terwijl BottomNavigation uit M2 alleen voor achterwaartse compatibiliteit blijft.
Elk NavigationBar-element wordt geïmplementeerd via de composable NavigationBarItem. Parameters van het element: icon (pictogram), label (tekstlabel), selected (selectiestatus) en onClick (callback). NavigationBarItem past automatisch de geselecteerde kleur (primary) en de animatie van de pictogramgrootte bij activering toe.
@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 }
)
}
}
}
Pictogrammen voor NavigationBarItem moeten Filled zijn voor de geselecteerde status en Outlined voor de niet-geselecteerde status. Material Icons bieden beide sets: Icons.Filled.Home en Icons.Outlined.Home. Standaard toont NavigationBarItem de filled-variant voor selected. Als een andere weergave nodig is, geef het pictogram dan voorwaardelijk door: if (selected) Filled.X else Outlined.X.
De lengte van het tekstlabel van het element — niet meer dan 12 tekens (Latijns alfabet) of 8 tekens (Cyrillisch). Lange labels worden afgekapt met een weglatingsteken. Gebruik voor labels maxLines = 1 en ellipsis = TextTruncation.Ellipsis. Als het label altijd nodig is, stel dan de parameter alwaysShowLabel = true in (standaard true).
| Parameter | Type | Beschrijving |
|---|---|---|
| icon | @Composable () -> Unit | Pictogram van het panelelement |
| label | @Composable (() -> Unit)? | Tekstlabel onder het pictogram |
| selected | Boolean | Vlag van het geselecteerde element |
| onClick | () -> Unit | Callback bij tikken |
| badge | @Composable (() -> Unit)? | Badge met aantal meldingen |
Directe update van selectedItem stuurt de navigatie niet — voor het wisselen van schermen is integratie met NavController nodig. Het typische patroon: onClick in NavigationBarItem roept navController.navigate(route) aan, en selectedItem wordt bepaald uit de huidige route van NavController.
NavController slaat de huidige back stack op. Om te bepalen welk panelelement is geselecteerd, vergelijkt u de huidige route (navController.currentBackStackEntryAsState().value?.destination?.route) met de routes van de elementen. Als de route overeenkomt — wordt het element als geselecteerd beschouwd. Dit elimineert het dupliceren van de status.
@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 */ }
}
Belangrijke instellingen in navigate(): popUpTo(startDestination) voorkomt ophoping van de back stack bij het wisselen van tabbladen, launchSingleTop sluit duplicatie van de route in de stack uit, en restoreState herstelt de status van het vorige bezoek aan het tabblad. Zonder deze parameters creëert elke tik een nieuw item in de stack, wat leidt tot incorrect gedrag van de Terug-knop.
Material3 (M3) biedt NavigationBar en NavigationBarItem als vervanging voor BottomNavigation uit Material2. Belangrijkste voordelen van M3: Dynamic Color, adaptieve hoogte, ripple-animatie over de volledige breedte van het element en ondersteuning voor WindowInsets om rekening te houden met systeemnavigatie.
Dynamic Color selecteert automatisch de kleuren van NavigationBar op basis van de achtergrond van het apparaat (Android 12+). Het actieve element krijgt de primary-kleur, inactieve elementen de onSurfaceVariant-kleur. Standaard verheft NavigationBar zich boven de systeemnavigatie via Modifier.navigationBarsPadding(). Gebruik de parameter windowInsets voor handmatige bediening.
NavigationBar in M3 animeert automatisch de breedte van het actieve element — het pictogram schuift naar links en het label wordt breder. De animatie duurt 300 ms en gebruikt de standaard Compose-easing. Als dit gedrag ongewenst is, stel dan het label altijd zichtbaar via alwaysShowLabel = true in NavigationBarItem.
Om kleuren te overschrijven, geeft u NavigationBarColors door in de parameter colors. Standaard wordt NavigationBarDefaults.colors() gebruikt, maar u kunt eigen kleuren instellen: containerColor (achtergrond van het paneel), contentColor (kleur van pictogrammen), indicatorColor (achtergrond van het actieve element). In tegenstelling tot M2, waar kleuren werden ingesteld via BottomNavigationDefaults, wordt in M3 de uniforme interface NavColors gebruikt.
Als in het project BottomNavigation uit M2 (androidx.compose.material) wordt gebruikt, beveelt Google migratie naar M3 NavigationBar aan. Migratie omvat het vervangen van imports, het bijwerken van parameters en het toevoegen van @OptIn(ExperimentalMaterial3Api::class). BottomNavigation uit M2 wordt als verouderd beschouwd en krijgt geen nieuwe functies.
De status van NavigationBar slaat de index van het huidige geselecteerde element op. In eenvoudige gevallen wordt remember { mutableIntStateOf(0) } gebruikt. Voor productieapplicaties moet de index echter worden gesynchroniseerd met de navigatiegraaf, en niet alleen een lokale UI-status zijn.
De belangrijkste benaderingen voor statusbeheer: NavController-based (aanbevolen) en ViewModel-based. In het eerste geval wordt selectedItem berekend uit de huidige route van NavController. In het tweede geval slaat ViewModel het huidige scherm op en leest NavigationBarItem de StateFlow van ViewModel. NavController-based is eenvoudiger, ViewModel-based biedt meer controle bij complexe logica (autorisatie, A/B-tests).
Bij gebruik van saveState en restoreState slaat NavController automatisch de status van LazyColumn en andere componenten binnen elk tabblad op en herstelt deze. Dit elimineert de noodzaak van ViewModel voor het opslaan van de scrollpositie. Het opslaan is echter niet van toepassing op invoervelden — deze vereisen SavedStateHandle of ViewModel.
Badge is een compacte meldingsindicator die wordt weergegeven op het pictogram van NavigationBarItem. In Material3 wordt de badge geïmplementeerd via BadgedBox, die het pictogram omhult en de badge in de rechterbovenhoek plaatst. Badge kan een getal (aantal meldingen) of een punt (eenvoudige aanwezigheidsindicator) bevatten.
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "Home")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBox plaatst Badge automatisch in de rechterbovenhoek van het onderliggende element. Als het getal 99 overschrijdt, toont Badge „99+“. Gebruik de voorwaarde if (count > 0) om de badge bij een nulwaarde te verbergen. Badge uit Material3 heeft standaard de kleur error (rood) met witte tekst. Aanpassing via BadgeDefaults.colors(containerColor, contentColor).
In Material2 bestond de badge niet — deze moest handmatig worden getekend via Canvas of met behulp van bibliotheken van derden. Material3 loste dit probleem op met een native component. Volgens Android Developers (2025) wordt BadgedBox gebruikt in 40% van de applicaties met NavigationBar, waarvan 60% voor het weergeven van ongelezen berichten.
Veelgestelde vragen
Material Design beveelt 3 tot 5 elementen aan. Minder dan drie — niet doelmatig (beter Tabs gebruiken). Meer dan vijf — verslechtert de leesbaarheid van labels en verkleint het touch-target. Als meer dan 5 secties nodig zijn, gebruik dan Navigation Rail (tablets) of Drawer.
Bepaal de lijst met routes waar het paneel zichtbaar is (meestal de hoofd-routes). Geef in Scaffold bottomBar NavigationBar alleen door als currentRoute in bottomNavRoutes. Alternatief — NavHost met een aparte graaf voor geneste schermen zonder bottomBar.
NavigationBar uit Material3 ondersteunt Dynamic Color, BadgedBox, WindowInsets en geanimeerde breedte van het actieve element. BottomNavigation uit Material2 is een oude API zonder deze functies. Google beveelt NavigationBar aan voor nieuwe projecten en migratie van bestaande projecten.
Het probleem is het ontbreken van saveState en restoreState in navigate(). Zonder hen slaat NavController de status van het vorige tabblad niet op. Voeg popUpTo(startDestination) { saveState = true } en restoreState = true toe in NavOptions om LazyColumn, scroll en invoervelden te behouden.
De schakelanimatie wordt gecontroleerd op het niveau van NavHost, niet van BottomNavigation. Voeg composable(route, enterTransition, exitTransition) toe met aangepaste animaties. Voor BottomNavigation is een fading-animatie (fadeIn + fadeOut) met een duur van 300ms gebruikelijk.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook