BottomNavigation (NavigationBar v Material3) je komponenta Material Design, která zajišťuje navigaci mezi třemi až pěti hlavními obrazovkami aplikace. V Jetpack Compose je spodní panel implementován pomocí NavigationBar nebo BottomNavigation, kde každý prvek je reprezentován ikonou a textovým štítkem. Podle Google Material Design (2025) je BottomNavigation doporučen pro iOS a Android jako hlavní vzor primární navigace. Panel se automaticky přizpůsobuje výšce systémové navigace (Gesture Navigation) pomocí WindowInsets. NavigationBar v Material3 podporuje dynamic color, badge a adaptivní okraje.
Hlavní body
BottomNavigation je komponenta Material Design pro spodní navigační panel, který zobrazuje tři až pět položek nabídky. V Jetpack Compose je implementován pomocí composable funkcí BottomNavigation (Material2) a NavigationBar (Material3). Panel je umístěn ve spodní časti obrazovky a poskytuje rychlý přístup k klíčovým sekcím aplikace.
Hlavním úkolem BottomNavigation je organizace primární navigace mezi úrovněmi obrazovek. Každý prvek panelu představuje samostatnou obrazovku (Home, Search, Profile). Stisknutí přepíná obsah v hlavní oblasti obrazovky. BottomNavigation není určen pro vnořenou navigaci — k tomu slouží karty (Tabs) v horní časti.
Podle Material Design Guidelines (2025) by měl spodní panel obsahovat pouze ty sekce, které jsou přístupné z jakéhokoliv místa aplikace. Skrytí panelu na dceřiných obrazovkách (např. při otevření stránky s detaily) je běžnou praxí. V Compose je viditelnost BottomNavigation řízena pomocí stavu Scaffold nebo podmínečného vykreslení na základě aktuální trasy NavController.
V Material3 se doporučuje používat NavigationBar místo Legacy BottomNavigation. NavigationBar obsahuje Dynamic Color, podporu WindowInsets a vylepšenou animaci stisknutí. Podle Google je NavigationBar perspektivní komponentou, zatímco BottomNavigation z M2 zůstává pouze pro zpětnou kompatibilitu.
Každý prvek NavigationBar je implementován pomocí composable NavigationBarItem. Parametry prvku: icon (ikona), label (textový štítek), selected (stav výběru) a onClick (zpětné volání). NavigationBarItem automaticky aplikuje vybranou barvu (primary) a animaci změny velikosti ikony při aktivaci.
@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 }
)
}
}
}
Ikony pro NavigationBarItem by měly být Filled pro vybraný stav a Outlined pro nevybraný stav. Material Icons poskytují obě sady: Icons.Filled.Home a Icons.Outlined.Home. Ve výchozím nastavení NavigationBarItem zobrazuje filled variantu pro selected. Pokud je potřeba jiné zobrazení, předejte ikonu podmínečně: if (selected) Filled.X else Outlined.X.
Délka textového štítku prvku — ne více než 12 znaků (latinka) nebo 8 znaků (cyrilice). Dlouhé štítky jsou zkráceny troma tečkami. Pro štítky používejte maxLines = 1 a ellipsis = TextTruncation.Ellipsis. Pokud je štítek vždy potřeba, nastavte parametr alwaysShowLabel = true (výchozí true).
| Parametr | Typ | Popis |
|---|---|---|
| icon | @Composable () -> Unit | Ikona prvku panelu |
| label | @Composable (() -> Unit)? | Textový štítek pod ikonou |
| selected | Boolean | Příznak vybraného prvku |
| onClick | () -> Unit | Zpětné volání při stisknutí |
| badge | @Composable (() -> Unit)? | Odznak s počtem oznámení |
Přímá aktualizace selectedItem nepřesměrovává navigaci — pro přepínání obrazovek je potřebná integrace s NavController. Typický vzor: onClick v NavigationBarItem volá navController.navigate(route) a selectedItem je určen z aktuální trasy NavController.
NavController ukládá aktuální back stack. Pro určení, který prvek panelu je vybrán, porovnejte aktuální trasu (navController.currentBackStackEntryAsState().value?.destination?.route) s trasami prvků. Pokud se trasa shoduje — prvek je považován za vybraný. Toto eliminuje duplikací stavu.
@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 */ }
}
Klíčové nastavení v navigate(): popUpTo(startDestination) zabraňuje hromadění back stack při přepínání karet, launchSingleTop vylučuje duplikací trasy v zásobníku a restoreState obnovuje stav předchozí návštěvy karty. Bez těchto parametrů každý stisk vytváří nový záznam v zásobníku, co vede k nesprávnému chování tlačítka Zpět.
Material3 (M3) poskytuje NavigationBar a NavigationBarItem jako náhradu za BottomNavigation z Material2. Hlavní výhody M3: Dynamic Color, adaptivní výška, ripple animace přes celou šířku prvku a podpora WindowInsets pro zohlednění systémové navigace.
Dynamic Color automaticky vybírá barvy NavigationBar na základě tapety zařízení (Android 12+). Aktivní prvek je obarven primary, neaktivní — onSurfaceVariant. Ve výchozím nastavení se NavigationBar zvedá nad systémovou navigací pomocí Modifier.navigationBarsPadding(). Pro ruční řízení použijte parametr windowInsets.
NavigationBar v M3 automaticky animuje šířku aktivního prvku — ikona se posouví doleva a štítek se rozširuje. Animace trvá 300 ms a používí standardní easing Compose. Pokud je toto chování nežádoucí, nastavte štítek vždy viditelný pomocí alwaysShowLabel = true v NavigationBarItem.
Pro přepsání barev předejte NavigationBarColors v parametru colors. Ve výchozím nastavení se používá NavigationBarDefaults.colors(), ale můžete nastavit vlastní barvy: containerColor (pozadí panelu), contentColor (barva ikon), indicatorColor (pozadí aktivního prvku). Na rozdíl od M2, kde barvy byly nastavovány pomocí BottomNavigationDefaults, v M3 se používá jednotné rozhraní NavColors.
Pokud se v projektu používá BottomNavigation z M2 (androidx.compose.material), Google doporučuje migraci na M3 NavigationBar. Migrace zahrnuje výměnu importů, aktualizaci parametrů a přidání @OptIn(ExperimentalMaterial3Api::class). BottomNavigation z M2 je považován za zastaralý a nedostává nové funkce.
Stav NavigationBar ukládá index aktuálně vybraného prvku. V jednoduchých případech se používá remember { mutableIntStateOf(0) }. Pro produkční aplikace by však měl být index synchronizován s navigačním grafem, nikoli být lokálním stavem UI.
Hlavní přístupy k řízení stavu: NavController-based (doporučený) a ViewModel-based. V prvním případě je selectedItem vypočítán z aktuální trasy NavController. Ve druhém — ViewModel ukládá aktuální obrazovku a NavigationBarItem čte StateFlow z ViewModel. NavController-based je jednodušší, ViewModel-based poskytuje více kontroly při složité logice (autorizace, A/B testy).
Při použití saveState a restoreState NavController automaticky ukládá a obnovuje stav LazyColumn a dalších komponent uvnitř každé karty. Tím odpadá potřeba ViewModel pro ukládání pozice posouvání. Ukládání se však nevztahuje na vstupní pole — ta vyžadují SavedStateHandle nebo ViewModel.
Badge je kompaktní ukazatel oznámení zobrazovaný na ikoně NavigationBarItem. V Material3 je odznak implementován pomocí BadgedBox, který obaluje ikonu a umístí odznak do pravého horního rohu. Badge může obsahovat číslo (množství oznámení) nebo tečku (jednoduchý ukazatel přítomnosti).
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "Domů")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBox automaticky pozicuje Badge do pravého horního rohu dceřiného prvku. Pokud číslo přesahuje 99, Badge zobrazuje „99+“. Pro skrytí odznaku při nulové hodnotě použijte podmínku if (count > 0). Badge z Material3 má ve výchozím nastavení barvu error (červenou) s bílým textem. Přizpůsobení pomocí BadgeDefaults.colors(containerColor, contentColor).
V Material2 odznak neexistoval — musel být ručně kreslen pomocí Canvas nebo použít knihovny třetích stran. Material3 vyřešil tento problém nativní komponentou. Podle Android Developers (2025) je BadgedBox používán ve 40% aplikací s NavigationBar, z čehož 60% pro zobrazení nepřečtených zpráv.
Často kladené otázky
Material Design doporučuje 3 až 5 prvků. Méně než tři — neúčelné (lepší použít Tabs). Více než pět — zhoršuje čitelnost štítků a snižuje touch-target. Pokud je potřeba více než 5 sekcí, použijte Navigation Rail (tablety) nebo Drawer.
Určete seznam tras, kde je panel viditelný (obvykle kořenové trasy). V Scaffold bottomBar předejte NavigationBar pouze pokud je currentRoute v bottomNavRoutes. Alternativa — NavHost s odděleným grafem pro vnořené obrazovky bez bottomBar.
NavigationBar z Material3 podporuje Dynamic Color, BadgedBox, WindowInsets a animovanou šířku aktivního prvku. BottomNavigation z Material2 je staré API bez těchto funkcí. Google doporučuje používat NavigationBar pro nové projekty a migraci stávajících.
Problém je v chybějících saveState a restoreState v navigate(). Bez nich NavController neukládá stav předchozí karty. Přidejte popUpTo(startDestination) { saveState = true } a restoreState = true do NavOptions pro zachování LazyColumn, posouvání a vstupních polí.
Animace přepínání je řízena na úrovni NavHost, nikoli BottomNavigation. Přidejte composable(route, enterTransition, exitTransition) s vlastními animacemi. Pro BottomNavigation je typická fading animace (fadeIn + fadeOut) s dobou trvání 300ms.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také