BottomNavigation (NavigationBar în Material3) este o componentă Material Design care asigură navigarea între trei și cinci ecrane principale ale aplicației. În Jetpack Compose, panoul inferior este implementat prin NavigationBar sau BottomNavigation, unde fiecare element este reprezentat de o pictogramă și o etichetă text. Conform Google Material Design (2025), BottomNavigation este recomandat pentru iOS și Android ca model principal de navigare primară. Panoul se ajustează automat la înălțimea navigării sistemului (Gesture Navigation) prin WindowInsets. NavigationBar în Material3 suportă dynamic color, badge și margini adaptive.
Principalele puncte
BottomNavigation este o componentă Material Design pentru panoul de navigare inferior, care afișează de la trei la cinci elemente de meniu. În Jetpack Compose este implementat prin funcțiile composable BottomNavigation (Material2) și NavigationBar (Material3). Panoul este plasat în partea de jos a ecranului și asigură acces rapid la secțiunile cheie ale aplicației.
Sarcina principală a BottomNavigation este organizarea navigării primare între nivelurile de ecran. Fiecare element al panoului reprezintă un ecran separat (Home, Search, Profile). Apăsarea comută conținutul în zona principală a ecranului. BottomNavigation nu este destinat navigării imbricate — pentru aceasta se folosesc filele (Tabs) în partea de sus.
Conform Material Design Guidelines (2025), panoul inferior trebuie să conțină doar acele secțiuni care sunt accesibile din orice punct al aplicației. Ascunderea panoului pe ecranele secundare (de exemplu, la deschiderea unei pagini de detalii) este o practică standard. În Compose, vizibilitatea BottomNavigation este gestionată prin starea Scaffold sau prin randare condiționată pe baza rutei curente a NavController.
În Material3 se recomandă utilizarea NavigationBar în loc de Legacy BottomNavigation. NavigationBar include Dynamic Color, suport pentru WindowInsets și animație îmbunătățită la apăsare. Potrivit Google, NavigationBar este o componentă de perspectivă, iar BottomNavigation din M2 rămâne doar pentru compatibilitate inversă.
Fiecare element NavigationBar este implementat prin composable-ul NavigationBarItem. Parametrii elementului: icon (pictogramă), label (etichetă text), selected (stare de selecție) și onClick (callback). NavigationBarItem aplică automat culoarea selectată (primary) și animația de schimbare a dimensiunii pictogramei la activare.
@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 }
)
}
}
}
Pictogramele pentru NavigationBarItem trebuie să fie Filled pentru starea selectată și Outlined pentru cea neselectată. Material Icons oferă ambele seturi: Icons.Filled.Home și Icons.Outlined.Home. În mod implicit, NavigationBarItem afișează varianta filled pentru selected. Dacă este necesară o afișare diferită, transmiteți pictograma condiționat: if (selected) Filled.X else Outlined.X.
Lungimea etichetei text a elementului — nu mai mult de 12 caractere (alfabet latin) sau 8 caractere (chirilic). Etichetele lungi sunt trunchiate cu puncte de suspensie. Pentru etichete utilizați maxLines = 1 și ellipsis = TextTruncation.Ellipsis. Dacă eticheta este necesară întotdeauna, setați parametrul alwaysShowLabel = true (implicit true).
| Parametru | Tip | Descriere |
|---|---|---|
| icon | @Composable () -> Unit | Pictograma elementului panoului |
| label | @Composable (() -> Unit)? | Eticheta text sub pictogramă |
| selected | Boolean | Indicatorul elementului selectat |
| onClick | () -> Unit | Callback la apăsare |
| badge | @Composable (() -> Unit)? | Insigna cu numărul de notificări |
Actualizarea directă a selectedItem nu redirecționează navigarea — pentru comutarea ecranelor este necesară integrarea cu NavController. Modelul tipic: onClick în NavigationBarItem apelează navController.navigate(route), iar selectedItem este determinat din ruta curentă a NavController.
NavController stochează back stack-ul curent. Pentru a determina care element al panoului este selectat, comparați ruta curentă (navController.currentBackStackEntryAsState().value?.destination?.route) cu rutele elementelor. Dacă ruta se potrivește — elementul este considerat selectat. Aceasta elimină duplicarea stării.
@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 */ }
}
Setările cheie în navigate(): popUpTo(startDestination) previne acumularea back stack-ului la comutarea filelor, launchSingleTop exclude duplicarea rutei în stivă, iar restoreState restabilește starea vizitei anterioare a filei. Fără acești parametri, fiecare apăsare creează o nouă intrare în stivă, ceea ce duce la comportamentul incorect al butonului Înapoi.
Material3 (M3) oferă NavigationBar și NavigationBarItem ca înlocuitor pentru BottomNavigation din Material2. Principalele avantaje ale M3: Dynamic Color, înălțime adaptivă, animație ripple pe toată lățimea elementului și suport pentru WindowInsets pentru a lua în calcul navigarea sistemului.
Dynamic Color selectează automat culorile NavigationBar pe baza tapetului dispozitivului (Android 12+). Elementul activ este colorat în primary, cele inactive — în onSurfaceVariant. În mod implicit, NavigationBar se ridică deasupra navigării sistemului prin Modifier.navigationBarsPadding(). Pentru control manual, utilizați parametrul windowInsets.
NavigationBar în M3 animează automat lățimea elementului activ — pictograma se deplasează la stânga, iar eticheta se extinde. Animația durează 300 ms și folosește easing-ul standard Compose. Dacă acest comportament este nedorit, setați afișarea permanentă a etichetei prin alwaysShowLabel = true în NavigationBarItem.
Pentru a suprascrie culorile, transmiteți NavigationBarColors în parametrul colors. În mod implicit se folosește NavigationBarDefaults.colors(), dar puteți seta propriile culori: containerColor (fundalul panoului), contentColor (culoarea pictogramelor), indicatorColor (fundalul elementului activ). Spre deosebire de M2, unde culorile se setau prin BottomNavigationDefaults, în M3 se folosește interfața unificată NavColors.
Dacă în proiect se folosește BottomNavigation din M2 (androidx.compose.material), Google recomandă migrarea la M3 NavigationBar. Migrarea include înlocuirea importurilor, actualizarea parametrilor și adăugarea @OptIn(ExperimentalMaterial3Api::class). BottomNavigation din M2 este considerat învechit și nu primește funcționalități noi.
Starea NavigationBar stochează indexul elementului curent selectat. În cazuri simple se folosește remember { mutableIntStateOf(0) }. Cu toate acestea, pentru aplicații de producție, indexul trebuie sincronizat cu graful de navigare, nu să fie o stare locală UI.
Principalele abordări de gestionare a stării: NavController-based (recomandată) și ViewModel-based. În primul caz, selectedItem este calculat din ruta curentă a NavController. În al doilea — ViewModel stochează ecranul curent, iar NavigationBarItem citește StateFlow din ViewModel. NavController-based este mai simplu, ViewModel-based oferă mai mult control în logica complexă (autentificare, teste A/B).
La utilizarea saveState și restoreState, NavController salvează și restabilește automat starea LazyColumn și a altor componente din interiorul fiecărei file. Aceasta elimină necesitatea unui ViewModel pentru stocarea poziției de scroll. Cu toate acestea, salvarea nu se aplică câmpurilor de introducere — acestea necesită SavedStateHandle sau ViewModel.
Badge este un indicator compact de notificări afișat pe pictograma NavigationBarItem. În Material3, insigna este implementată prin BadgedBox, care înfășoară pictograma și plasează insigna în colțul din dreapta sus. Badge poate conține un număr (cantitatea de notificări) sau un punct (simplă indicație de prezență).
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "Acasă")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBox poziționează automat Badge în colțul din dreapta sus al elementului copil. Dacă numărul depășește 99, Badge afișează „99+“. Pentru ascunderea insignei la valoarea zero, utilizați condiția if (count > 0). Badge din Material3 are în mod implicit culoarea error (roșu) cu text alb. Personalizare prin BadgeDefaults.colors(containerColor, contentColor).
În Material2, insigna nu exista — trebuia desenată manual prin Canvas sau prin biblioteci terțe. Material3 a rezolvat această problemă cu o componentă nativă. Potrivit Android Developers (2025), BadgedBox este utilizat în 40% din aplicațiile cu NavigationBar, dintre care 60% — pentru afișarea mesajelor necitite.
Întrebări frecvente
Material Design recomandă de la 3 la 5 elemente. Mai puțin de trei — inadecvat (mai bine să folosiți Tabs). Mai mult de cinci — degradează lizibilitatea etichetelor și reduce touch-target-ul. Dacă sunt necesare mai mult de 5 secțiuni, utilizați Navigation Rail (tablete) sau Drawer.
Definiți lista rutelor unde panoul este vizibil (de obicei rutele rădăcină). În Scaffold bottomBar, transmiteți NavigationBar numai dacă currentRoute in bottomNavRoutes. Alternativă — NavHost cu un graf separat pentru ecrane imbricate fără bottomBar.
NavigationBar din Material3 suportă Dynamic Color, BadgedBox, WindowInsets și lățime animată a elementului activ. BottomNavigation din Material2 este un API vechi fără aceste funcții. Google recomandă utilizarea NavigationBar pentru proiecte noi și migrarea celor existente.
Problema constă în lipsa saveState și restoreState în navigate(). Fără ele, NavController nu salvează starea filei anterioare. Adăugați popUpTo(startDestination) { saveState = true } și restoreState = true în NavOptions pentru a păstra LazyColumn, scroll-ul și câmpurile de intrare.
Animația de comutare este controlată la nivelul NavHost, nu al BottomNavigation. Adăugați composable(route, enterTransition, exitTransition) cu animații personalizate. Pentru BottomNavigation, animația tipică este fading (fadeIn + fadeOut) cu durata de 300ms.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și