BottomNavigation (NavigationBar in Material3) ist eine Material Design-Komponente, die die Navigation zwischen drei bis fünf Hauptbildschirmen einer Anwendung ermöglicht. In Jetpack Compose wird die untere Leiste über NavigationBar oder BottomNavigation implementiert, wobei jedes Element durch ein Symbol und eine Textbeschriftung dargestellt wird. Laut Google Material Design (2025) wird BottomNavigation für iOS und Android als primäres Navigationsmuster empfohlen. Das Panel passt sich automatisch an die Höhe der Systemnavigation (Gesture Navigation) über WindowInsets an. NavigationBar in Material3 unterstützt dynamische Farbe, Badge und adaptive Abstände.
Wichtige Punkte
BottomNavigation ist eine Material Design-Komponente für eine untere Navigationsleiste, die drei bis fünf Menüpunkte anzeigt. In Jetpack Compose wird sie über die Composable-Funktionen BottomNavigation (Material2) und NavigationBar (Material3) implementiert. Die Leiste wird am unteren Bildschirmrand platziert und bietet schnellen Zugriff auf die wichtigsten Bereiche der Anwendung.
Der Hauptzweck von BottomNavigation ist die Organisation der primären Navigation zwischen Zielen auf Bildschirmebene. Jedes Panel-Element repräsentiert einen separaten Bildschirm (Startseite, Suche, Profil). Durch Antippen wird der Inhalt im Hauptbereich des Bildschirms gewechselt. BottomNavigation ist nicht für verschachtelte Navigation gedacht — dafür werden Tabs oben verwendet.
Laut den Material Design Guidelines (2025) sollte die untere Leiste nur Abschnitte enthalten, die von jedem Punkt der Anwendung aus zugänglich sind. Das Ausblenden der Leiste auf untergeordneten Bildschirmen (z. B. beim Öffnen einer Detailseite) ist eine Standardpraxis. In Compose wird die Sichtbarkeit von BottomNavigation über den Scaffold-Zustand oder bedingtes Rendering basierend auf der aktuellen NavController-Route gesteuert.
In Material3 wird empfohlen, NavigationBar anstelle der veralteten BottomNavigation zu verwenden. NavigationBar enthält dynamische Farbe, WindowInsets-Unterstützung und verbesserte Drück-Animation. Laut Google ist NavigationBar die zukunftsorientierte Komponente, während BottomNavigation aus M2 nur aus Gründen der Abwärtskompatibilität erhalten bleibt.
Jedes NavigationBar-Element wird über das Composable NavigationBarItem implementiert. Parameter: icon (Symbol), label (Textbeschriftung), selected (Auswahlzustand) und onClick (Rückruf). NavigationBarItem wendet automatisch die ausgewählte Farbe (primary) an und animiert die Größenänderung des Symbols bei Aktivierung.
@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 }
)
}
}
}
Symbole für NavigationBarItem sollten für den ausgewählten Zustand gefüllt (Filled) und für den nicht ausgewählten Zustand umrissen (Outlined) sein. Material Icons bietet beide Sets: Icons.Filled.Home und Icons.Outlined.Home. Standardmäßig zeigt NavigationBarItem die gefüllte Variante für ausgewählt an. Wenn eine andere Darstellung benötigt wird, übergeben Sie das Symbol bedingt: if (selected) Filled.X else Outlined.X.
Die Länge der Textbeschriftung sollte 12 Zeichen (Lateinisch) oder 8 Zeichen (Kyrillisch) nicht überschreiten. Lange Beschriftungen werden mit Auslassungspunkten abgeschnitten. Für Beschriftungen verwenden Sie maxLines = 1 und ellipsis = TextTruncation.Ellipsis. Wenn die Beschriftung immer angezeigt werden soll, entfernen Sie den Parameter alwaysShowLabel = true (Standardwert ist true).
| Parameter | Typ | Beschreibung |
|---|---|---|
| icon | @Composable () -> Unit | Symbol des Panel-Elements |
| label | @Composable (() -> Unit)? | Textbeschriftung unter dem Symbol |
| selected | Boolean | Markierung des ausgewählten Elements |
| onClick | () -> Unit | Rückruf bei Klick |
| badge | @Composable (() -> Unit)? | Badge mit Benachrichtigungszähler |
Das direkte Aktualisieren von selectedItem löst keine Navigation aus — zum Wechseln von Bildschirmen ist eine Integration mit NavController erforderlich. Das typische Muster: onClick in NavigationBarItem ruft navController.navigate(route) auf, und selectedItem wird aus der aktuellen NavController-Route bestimmt.
NavController speichert den aktuellen Zurück-Stapel. Um zu bestimmen, welches Panel-Element ausgewählt ist, vergleichen Sie die aktuelle Route (navController.currentBackStackEntryAsState().value?.destination?.route) mit den Element-Routen. Wenn die Route übereinstimmt, gilt das Element als ausgewählt. Dies eliminiert Zustandsduplikation.
@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 */ }
}
Wichtige Einstellungen in navigate(): popUpTo(startDestination) verhindert die Ansammlung des Zurück-Stapels beim Wechseln von Tabs, launchSingleTop verhindert Routenduplikation im Stapel, und restoreState stellt den Zustand des zuvor besuchten Tabs wieder her. Ohne diese Parameter erzeugt jeder Klick einen neuen Eintrag im Stapel, was zu falschem Verhalten der Zurück-Taste führt.
Material3 (M3) bietet NavigationBar und NavigationBarItem als Ersatz für BottomNavigation aus Material2. Hauptvorteile von M3: dynamische Farbe, adaptive Höhe, volle Breite Ripple-Animation und WindowInsets-Unterstützung für Systemnavigation.
Dynamische Farbe wählt automatisch NavigationBar-Farben basierend auf dem Gerätehintergrund (Android 12+). Das aktive Element wird in primary gefärbt, inaktive in onSurfaceVariant. Standardmäßig erhebt sich NavigationBar über die Systemnavigation durch Modifier.navigationBarsPadding(). Für manuelle Steuerung verwenden Sie den Parameter windowInsets.
NavigationBar in M3 animiert automatisch die Breite des aktiven Elements — das Symbol verschiebt sich nach links und die Beschriftung dehnt sich aus. Die Animation dauert 300 ms und verwendet das Standard-Compose-Easing. Wenn dieses Verhalten unerwünscht ist, zeigen Sie die Beschriftung immer über alwaysShowLabel = true in NavigationBarItem an.
Um Farben zu überschreiben, übergeben Sie NavigationBarColors im Parameter colors. Standardmäßig wird NavigationBarDefaults.colors() verwendet, aber benutzerdefinierte Werte können festgelegt werden: containerColor (Hintergrund der Leiste), contentColor (Farbe der Symbole), indicatorColor (Hintergrund des aktiven Elements). Im Gegensatz zu M2, wo Farben über BottomNavigationDefaults festgelegt wurden, verwendet M3 eine einheitliche NavColors-Schnittstelle.
Wenn das Projekt BottomNavigation aus M2 (androidx.compose.material) verwendet, empfiehlt Google die Migration zu NavigationBar aus M3. Die Migration umfasst das Ersetzen von Importen, das Aktualisieren von Parametern und das Hinzufügen von @OptIn(ExperimentalMaterial3Api::class). BottomNavigation aus M2 gilt als veraltet und erhält keine neuen Funktionen.
Der NavigationBar-Zustand speichert den Index des aktuell ausgewählten Elements. In einfachen Fällen wird remember { mutableIntStateOf(0) } verwendet. Für Produktionsanwendungen sollte der Index jedoch mit dem Navigationsgraphen synchronisiert werden und kein lokaler UI-Zustand sein.
Die Hauptansätze zur Zustandsverwaltung: NavController-basiert (empfohlen) und ViewModel-basiert. Im ersten Fall wird selectedItem aus der aktuellen NavController-Route berechnet. Im zweiten speichert das ViewModel den aktuellen Bildschirm, und NavigationBarItem liest den StateFlow aus dem ViewModel. Der NavController-basierte Ansatz ist einfacher, der ViewModel-basierte bietet mehr Kontrolle für komplexe Logik (Authentifizierung, A/B-Tests).
Bei Verwendung von saveState und restoreState speichert und stellt NavController automatisch den Zustand von LazyColumn und anderen Komponenten innerhalb jedes Tabs wieder her. Dies eliminiert die Notwendigkeit eines ViewModels zum Speichern der Scroll-Position. Das Speichern gilt jedoch nicht für Eingabefelder — sie erfordern SavedStateHandle oder ViewModel.
Badge ist ein kompakter Benachrichtigungsindikator, der auf dem NavigationBarItem-Symbol angezeigt wird. In Material3 wird der Badge über BadgedBox implementiert, der das Symbol umschließt und den Badge in der oberen rechten Ecke platziert. Der Badge kann eine Zahl (Benachrichtigungsanzahl) oder einen Punkt (einfacher Präsenzindikator) enthalten.
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "Startseite")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBox positioniert den Badge automatisch in der oberen rechten Ecke des untergeordneten Elements. Wenn die Anzahl 99 übersteigt, zeigt der Badge "99+" an. Um den Badge bei Null auszublenden, verwenden Sie if (count > 0). Der Material3-Badge hat standardmäßig die Fehlerfarbe (rot) mit weißem Text. Anpassung über BadgeDefaults.colors(containerColor, contentColor).
In Material2 fehlte der Badge — er musste manuell über Canvas oder mit Drittanbieter-Bibliotheken gezeichnet werden. Material3 löste dies mit einer nativen Komponente. Laut Android Developers (2025) wird BadgedBox in 40% der Apps mit NavigationBar verwendet, von denen 60% ungelesene Nachrichten anzeigen.
Häufig gestellte Fragen
Material Design empfiehlt 3 bis 5 Elemente. Weniger als drei sind unpraktisch (verwenden Sie stattdessen Tabs). Mehr als fünf beeinträchtigt die Lesbarkeit der Beschriftungen und die Größe des Touch-Ziels. Wenn mehr als 5 Abschnitte benötigt werden, verwenden Sie Navigation Rail (Tablets) oder Drawer.
Definieren Sie eine Liste von Routen, auf denen die Leiste sichtbar ist (normalerweise Stammrouten). Übergeben Sie in Scaffold bottomBar NavigationBar nur, wenn currentRoute in bottomNavRoutes ist. Eine Alternative ist NavHost mit einem separaten Graphen für verschachtelte Bildschirme ohne bottomBar.
NavigationBar aus Material3 unterstützt dynamische Farbe, BadgedBox, WindowInsets und animierte aktive Elementbreite. BottomNavigation aus Material2 ist eine veraltete API ohne diese Funktionen. Google empfiehlt, NavigationBar für neue Projekte zu verwenden und bestehende zu migrieren.
Das Problem ist das Fehlen von saveState und restoreState in navigate(). Ohne diese speichert NavController den Zustand des vorherigen Tabs nicht. Fügen Sie popUpTo(startDestination) { saveState = true } und restoreState = true in NavOptions hinzu, um LazyColumn, Scroll-Position und Eingabefelder zu erhalten.
Die Wechselanimation wird auf NavHost-Ebene gesteuert, nicht auf BottomNavigation-Ebene. Fügen Sie composable(route, enterTransition, exitTransition) mit benutzerdefinierten Animationen hinzu. Für BottomNavigation ist eine Überblendungsanimation (fadeIn + fadeOut) mit 300 ms Dauer typisch.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch