BottomNavigation: какво е това, структура и настройка в Jetpack Compose

Автор: IT Sectr Публикувано: 2026-06-29 Време за четене: 8 мин

BottomNavigation (NavigationBar в Material3) е компонент Material Design, който осигурява навигация между три до пет основни екрана на приложението. В Jetpack Compose долният панел се имплементира чрез NavigationBar или BottomNavigation, където всеки елемент е представен от икона и текстов етикет. Според Google Material Design (2025), BottomNavigation се препоръчва за iOS и Android като основен модел за първична навигация. Панелът автоматично се адаптира към височината на системната навигация (Gesture Navigation) чрез WindowInsets. NavigationBar в Material3 поддържа dynamic color, badge и адаптивни отстояния.

Основни точки

  • BottomNavigation — панел за долна навигация за 3–5 екрана, имплементиран чрез NavigationBar в Material3
  • NavigationBarItem — елемент на панела, съдържащ икона, етикет и значка за известия
  • Връзка с NavHost — изборът на елемент задейства navigate() в NavController за превключване на графа
  • Material3 добавя Dynamic Color и автоматично отчитане на системната навигация чрез WindowInsets
  • Badge — значка за известия върху иконата, имплементирана чрез BadgedBox с брой

Какво е BottomNavigation в Jetpack Compose?

BottomNavigation е компонент Material Design за долен навигационен панел, който показва от три до пет елемента от менюто. В Jetpack Compose се имплементира чрез composable функциите BottomNavigation (Material2) и NavigationBar (Material3). Панелът се поставя в долната част на екрана и осигурява бърз достъп до ключови секции на приложението.

Основната задача на BottomNavigation е организирането на първична навигация между нивата на екраните. Всеки елемент от панела представлява отделен екран (Home, Search, Profile). Натискането превключва съдържанието в основната област на екрана. BottomNavigation не е предназначен за вложена навигация — за това се използват раздели (Tabs) в горната част.

Според Material Design Guidelines (2025), долният панел трябва да съдържа само онези секции, които са достъпни от всяка точка на приложението. Скриването на панела на подчинени екрани (например при отваряне на страница с детайли) е стандартна практика. В Compose видимостта на BottomNavigation се управлява чрез състоянието на Scaffold или условно рендиране на базата на текущия маршрут на NavController.

В Material3 се препоръчва използването на NavigationBar вместо Legacy BottomNavigation. NavigationBar включва Dynamic Color, поддръжка на WindowInsets и подобрена анимация при натискане. Според Google, NavigationBar е перспективен компонент, а BottomNavigation от M2 остава само за обратна съвместимост.

Всеки елемент на NavigationBar се имплементира чрез composable NavigationBarItem. Параметри на елемента: icon (икона), label (текстов етикет), selected (състояние на избор) и onClick (обратно извикване). NavigationBarItem автоматично прилага избрания цвят (primary) и анимация за промяна на размера на иконата при активиране.

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

Иконите за NavigationBarItem трябва да бъдат Filled за избрано състояние и Outlined за неизбрано състояние. Material Icons предоставят и двата набора: Icons.Filled.Home и Icons.Outlined.Home. По подразбиране NavigationBarItem показва filled варианта за selected. Ако е необходимо различно показване, предавайте иконата условно: if (selected) Filled.X else Outlined.X.

Дължината на текстовия етикет на елемента — не повече от 12 знака (латиница) или 8 знака (кирилица). Дългите етикети се съкращават с многоточие. За етикети използвайте maxLines = 1 и ellipsis = TextTruncation.Ellipsis. Ако етикетът е винаги необходим, задайте параметъра alwaysShowLabel = true (по подразбиране true).

ПараметърТипОписание
icon@Composable () -> UnitИкона на елемента от панела
label@Composable (() -> Unit)?Текстов етикет под иконата
selectedBooleanФлаг на избрания елемент
onClick() -> UnitОбратно извикване при натискане
badge@Composable (() -> Unit)?Значка с брой известия

Свързване на BottomNavigation с NavHost

Директното актуализиране на selectedItem не пренасочва навигацията — за превключване на екрани е необходима интеграция с NavController. Типичният модел: onClick в NavigationBarItem извиква navController.navigate(route), а selectedItem се определя от текущия маршрут на NavController.

NavController съхранява текущия back stack. За да определите кой елемент от панела е избран, сравнете текущия маршрут (navController.currentBackStackEntryAsState().value?.destination?.route) с маршрутите на елементите. Ако маршрутът съвпада — елементът се счита за избран. Това елиминира дублирането на състояние.

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

Ключови настройки в navigate(): popUpTo(startDestination) предотвратява натрупването на back stack при превключване на раздели, launchSingleTop изключва дублирането на маршрута в стека, а restoreState възстановява състоянието на предишното посещение на раздела. Без тези параметри всяко натискане създава нов запис в стека, което води до неправилно поведение на бутона Назад.

NavigationBar в Material3: Dynamic Color и адаптация

Material3 (M3) предоставя NavigationBar и NavigationBarItem като замяна на BottomNavigation от Material2. Основни предимства на M3: Dynamic Color, адаптивна височина, ripple анимация по цялата ширина на елемента и поддръжка на WindowInsets за отчитане на системната навигация.

Dynamic Color автоматично избира цветовете на NavigationBar на базата на тапета на устройството (Android 12+). Активният елемент се оцветява в primary, неактивните — в onSurfaceVariant. По подразбиране NavigationBar се повдига над системната навигация чрез Modifier.navigationBarsPadding(). За ръчно управление използвайте параметъра windowInsets.

NavigationBar в M3 автоматично анимира ширината на активния елемент — иконата се премества наляво, а етикетът се разширява. Анимацията продължава 300 ms и използва стандартния easing на Compose. Ако това поведение е нежелателно, задайте етикетът винаги да се показва чрез alwaysShowLabel = true в NavigationBarItem.

Персонализиране на цветовете на NavigationBar

За презаписване на цветовете предайте NavigationBarColors в параметъра colors. По подразбиране се използва NavigationBarDefaults.colors(), но можете да зададете свои цветове: containerColor (фон на панела), contentColor (цвят на иконите), indicatorColor (фон на активния елемент). За разлика от M2, където цветовете се задаваха чрез BottomNavigationDefaults, в M3 се използва унифициран интерфейс NavColors.

Ако в проекта се използва BottomNavigation от M2 (androidx.compose.material), Google препоръчва миграция към M3 NavigationBar. Миграцията включва подмяна на импорти, актуализиране на параметри и добавяне на @OptIn(ExperimentalMaterial3Api::class). BottomNavigation от M2 се счита за остарял и не получава нови функции.

Състоянието на NavigationBar съхранява индекса на текущо избрания елемент. В прости случаи се използва remember { mutableIntStateOf(0) }. Въпреки това, за производствени приложения индексът трябва да бъде синхронизиран с навигационния граф, а не да бъде локално състояние на UI.

Основни подходи за управление на състоянието: NavController-based (препоръчва се) и ViewModel-based. В първия случай selectedItem се изчислява от текущия маршрут на NavController. Във втория — ViewModel съхранява текущия екран, а NavigationBarItem чете StateFlow от ViewModel. NavController-based е по-прост, ViewModel-based дава повече контрол при сложна логика (оторизация, A/B тестове).

При използване на saveState и restoreState, NavController автоматично запазва и възстановява състоянието на LazyColumn и други компоненти във всеки раздел. Това елиминира необходимостта от ViewModel за съхраняване на позицията на скрол. Въпреки това, запазването не се прилага за полета за въвеждане — те изискват SavedStateHandle или ViewModel.

Значки за известия и броячи върху елементи

Badge е компактен индикатор за известия, който се показва върху иконата на NavigationBarItem. В Material3 значката се имплементира чрез BadgedBox, който обгръща иконата и поставя значката в горния десен ъгъл. Badge може да съдържа число (брой известия) или точка (просто указание за наличие).

kotlin
@Composable
fun HomeItem(unreadCount: Int) {
    NavigationBarItem(
        icon = {
            BadgedBox(badge = {
                if (unreadCount > 0) {
                    Badge { Text(unreadCount.toString()) }
                }
            }) {
                Icon(Icons.Filled.Home, contentDescription = "Начало")
            }
        },
        label = { Text("Home") },
        selected = false,
        onClick = { }
    )
}

BadgedBox автоматично позиционира Badge в горния десен ъгъл на дъщерния елемент. Ако числото надвишава 99, Badge показва „99+“. За скриване на значката при нулева стойност използвайте условието if (count > 0). Badge от Material3 по подразбиране има цвят error (червен) с бял текст. Персонализиране чрез BadgeDefaults.colors(containerColor, contentColor).

В Material2 значката не съществуваше — трябваше да се рисува ръчно чрез Canvas или да се използват библиотеки на трети страни. Material3 реши този проблем с нативен компонент. Според Android Developers (2025), BadgedBox се използва в 40% от приложенията с NavigationBar, от които 60% — за показване на непрочетени съобщения.

Често задавани въпроси

Колко елемента трябва да има в BottomNavigation?

Material Design препоръчва от 3 до 5 елемента. По-малко от три — нецелесъобразно (по-добре да използвате Tabs). Повече от пет — влошава четимостта на етикетите и намалява touch-target. Ако са необходими повече от 5 секции, използвайте Navigation Rail (таблети) или Drawer.

Как да скрия BottomNavigation на подчинени екрани?

Определете списък с маршрути, където панелът е видим (обикновено коренови маршрути). В Scaffold bottomBar предавайте NavigationBar само ако currentRoute е в bottomNavRoutes. Алтернатива — NavHost с отделен граф за вложени екрани без bottomBar.

Каква е разликата между NavigationBar (M3) и BottomNavigation (M2)?

NavigationBar от Material3 поддържа Dynamic Color, BadgedBox, WindowInsets и анимирана ширина на активния елемент. BottomNavigation от Material2 е стар API без тези функции. Google препоръчва използването на NavigationBar за нови проекти и мигриране на съществуващите.

Защо при натискане на елемент от BottomNavigation екранът се пресъздава?

Проблемът е в липсата на saveState и restoreState в navigate(). Без тях NavController не запазва състоянието на предишния раздел. Добавете popUpTo(startDestination) { saveState = true } и restoreState = true в NavOptions за запазване на LazyColumn, скрол и полета за въвеждане.

Как да персонализирам анимацията за превключване между раздели?

Анимацията за превключване се контролира на ниво NavHost, а не на BottomNavigation. Добавете composable(route, enterTransition, exitTransition) с персонализирани анимации. За BottomNavigation е типична fading анимация (fadeIn + fadeOut) с продължителност 300ms.

Резюме

  • BottomNavigation (NavigationBar в M3) — долен панел за превключване между 3–5 основни екрана на приложението
  • NavigationBarItem — елемент на панела с икона, етикет, значка и състояние на избор
  • Интеграция с NavController изисква popUpTo, launchSingleTop и restoreState за правилно управление на стека
  • Material3 NavigationBar поддържа Dynamic Color, BadgedBox, WindowInsets и анимация на активния елемент
  • BadgedBox — компонент за значки за известия с число или точка върху иконата на елемента
  • Състояние на избор се изчислява от currentBackStackEntryAsState за синхронизация с NavController
  • За скриване на панела на подчинени екрани използвайте условно рендиране в Scaffold bottomBar на базата на списък с маршрути

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също