BottomNavigation: ce este, structură și configurare în Jetpack Compose

Autor: IT Sectr Publicat: 2026-06-29 Timp de citire: 8 min

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 — panoul de navigare inferior pentru 3–5 ecrane, implementat prin NavigationBar în Material3
  • NavigationBarItem — elementul panoului care conține pictogramă, etichetă și insignă pentru notificări
  • Legătura cu NavHost — selectarea elementului declanșează navigate() în NavController pentru comutarea grafului
  • Material3 adaugă Dynamic Color și luarea automată în calcul a navigării sistemului prin WindowInsets
  • Badge — insigna de notificări pe pictogramă, implementată prin BadgedBox cu număr

Ce este BottomNavigation în Jetpack Compose?

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.

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 }
            )
        }
    }
}

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).

ParametruTipDescriere
icon@Composable () -> UnitPictograma elementului panoului
label@Composable (() -> Unit)?Eticheta text sub pictogramă
selectedBooleanIndicatorul elementului selectat
onClick() -> UnitCallback la apăsare
badge@Composable (() -> Unit)?Insigna cu numărul de notificări

Legătura BottomNavigation cu NavHost

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.

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 */ }
}

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.

NavigationBar în Material3: Dynamic Color și adaptare

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.

Personalizarea culorilor NavigationBar

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.

Insignele de notificări și contoare pe elemente

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ță).

kotlin
@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

Câte elemente ar trebui să fie în BottomNavigation?

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.

Cum se ascunde BottomNavigation pe ecranele secundare?

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.

Care este diferența dintre NavigationBar (M3) și BottomNavigation (M2)?

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.

De ce la apăsarea unui element BottomNavigation ecranul se recrează?

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.

Cum se personalizează animația de comutare între file?

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

  • BottomNavigation (NavigationBar în M3) — panoul inferior pentru comutarea între 3–5 ecrane principale ale aplicației
  • NavigationBarItem — elementul panoului cu pictogramă, etichetă, insignă și stare de selecție
  • Integrarea cu NavController necesită popUpTo, launchSingleTop și restoreState pentru gestionarea corectă a stivei
  • Material3 NavigationBar suportă Dynamic Color, BadgedBox, WindowInsets și animația elementului activ
  • BadgedBox — componentă pentru insignele de notificări cu număr sau punct pe pictograma elementului
  • Starea de selecție se calculează din currentBackStackEntryAsState pentru sincronizarea cu NavController
  • Pentru ascunderea panoului pe ecrane secundare, utilizați randarea condiționată în bottomBar Scaffold pe baza listei de rute

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.

Discutați proiectul

Citiți și