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 — організація первинної навігації між screen-рівнями. Кожен елемент панелі представляє окремий екран (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 відновлює стан попереднього відвідування вкладки. Без цих параметрів кожне натискання створює новий entry в стеку, що призводить до некоректної поведінки кнопки Back.

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) }. Однак для production-застосунків індекс повинен синхронізуватися з навігаційним графом, а не бути локальним станом 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-таргет. Якщо потрібно більше 5 розділів, використовуйте Navigation Rail (планшети) або Drawer.

Як сховати BottomNavigation на дочірніх екранах?

Визначте список маршрутів, де панель видна (зазвичай кореневі). В Scaffold bottomBar передавайте NavigationBar тільки якщо currentRoute in 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
  • Для приховування панелі на дочірніх екранах використовуйте умовний рендеринг в bottomBar Scaffold за списком маршрутів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також