BottomNavigation: co to jest, struktura i konfiguracja w Jetpack Compose

Autor: IT Sectr Opublikowano: 2026-06-29 Czas czytania: 8 min

BottomNavigation (NavigationBar w Material3) to komponent Material Design zapewniający nawigację między trzema a pięcioma głównymi ekranami aplikacji. W Jetpack Compose dolny panel jest implementowany przez NavigationBar lub BottomNavigation, gdzie każdy element jest reprezentowany przez ikonę i etykietę tekstową. Według Google Material Design (2025), BottomNavigation jest zalecany dla iOS i Android jako podstawowy wzorzec nawigacji podstawowej. Panel automatycznie dostosowuje się do wysokości nawigacji systemowej (Gesture Navigation) poprzez WindowInsets. NavigationBar w Material3 obsługuje dynamic color, badge i adaptacyjne odstępy.

Najważniejsze

  • BottomNavigation — panel dolnej nawigacji dla 3–5 ekranów, zaimplementowany przez NavigationBar w Material3
  • NavigationBarItem — element panelu zawierający ikonę, etykietę i badge dla powiadomień
  • Połączenie z NavHost — wybór elementu uruchamia navigate() w NavController do przełączania grafu
  • Material3 dodaje Dynamic Color i automatyczne uwzględnianie nawigacji systemowej przez WindowInsets
  • Badge — znacznik powiadomień na ikonie, implementowany przez BadgedBox z liczbą

Czym jest BottomNavigation w Jetpack Compose?

BottomNavigation to komponent Material Design dla dolnego panelu nawigacji, wyświetlający od trzech do pięciu pozycji menu. W Jetpack Compose jest zaimplementowany przez funkcje composable BottomNavigation (Material2) i NavigationBar (Material3). Panel umieszczany jest w dolnej części ekranu i zapewnia szybki dostęp do kluczowych sekcji aplikacji.

Głównym zadaniem BottomNavigation jest organizacja nawigacji podstawowej między poziomami ekranów. Każdy element panelu reprezentuje osobny ekran (Home, Search, Profile). Naciśnięcie przełącza zawartość w głównym obszarze ekranu. BottomNavigation nie jest przeznaczony do nawigacji zagnieżdżonej — do tego służą zakładki (Tabs) w górnej części.

Według Material Design Guidelines (2025), dolny panel powinien zawierać tylko te sekcje, które są dostępne z każdego miejsca w aplikacji. Ukrywanie panelu na ekranach podrzędnych (np. przy otwieraniu szczegółowej strony) to standardowa praktyka. W Compose widoczność BottomNavigation jest zarządzana przez stan Scaffold lub warunkowe renderowanie na podstawie bieżącej trasy NavController.

W Material3 zaleca się używanie NavigationBar zamiast Legacy BottomNavigation. NavigationBar zawiera Dynamic Color, obsługę WindowInsets i ulepszoną animację naciśnięcia. Według Google, NavigationBar jest perspektywicznym komponentem, a BottomNavigation z M2 pozostaje tylko dla wstecznej kompatybilności.

Każdy element NavigationBar jest implementowany przez composable NavigationBarItem. Parametry elementu: icon (ikona), label (etykieta tekstowa), selected (stan wyboru) i onClick (callback). NavigationBarItem automatycznie stosuje wybrany kolor (primary) i animację zmiany rozmiaru ikony przy aktywacji.

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

Ikony dla NavigationBarItem powinny być Filled dla stanu wybranego i Outlined dla niewybranego. Material Icons udostępniają oba zestawy: Icons.Filled.Home i Icons.Outlined.Home. Domyślnie NavigationBarItem wyświetla wariant filled dla selected. Jeśli potrzebne jest różne wyświetlanie, przekazuj ikonę warunkowo: if (selected) Filled.X else Outlined.X.

Długość etykiety tekstowej elementu — nie więcej niż 12 znaków (alfabet łaciński) lub 8 znaków (cyrylica). Długie etykiety są przycinane z wielokropkiem. Dla etykiet używaj maxLines = 1 i ellipsis = TextTruncation.Ellipsis. Jeśli etykieta ma być zawsze widoczna, ustaw parametr alwaysShowLabel = true (domyślnie true).

ParametrTypOpis
icon@Composable () -> UnitIkona elementu panelu
label@Composable (() -> Unit)?Etykieta tekstowa pod ikoną
selectedBooleanFlaga wybranego elementu
onClick() -> UnitCallback przy naciśnięciu
badge@Composable (() -> Unit)?Badge z liczbą powiadomień

Połączenie BottomNavigation z NavHost

Bezpośrednia aktualizacja selectedItem nie przekierowuje nawigacji — do przełączania ekranów potrzebna jest integracja z NavController. Typowy wzorzec: onClick w NavigationBarItem wywołuje navController.navigate(route), a selectedItem jest określany na podstawie bieżącej trasy NavController.

NavController przechowuje bieżący back stack. Aby określić, który element panelu jest wybrany, porównaj bieżącą trasę (navController.currentBackStackEntryAsState().value?.destination?.route) z trasami elementów. Jeśli trasa się zgadza — element jest uważany za wybrany. Eliminuje to dublowanie stanu.

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

Kluczowe ustawienia w navigate(): popUpTo(startDestination) zapobiega gromadzeniu się back stack przy przełączaniu zakładek, launchSingleTop eliminuje dublowanie trasy w stosie, a restoreState przywraca stan poprzedniej wizyty w zakładce. Bez tych parametrów każde naciśnięcie tworzy nowy wpis w stosie, co prowadzi do nieprawidłowego działania przycisku Wstecz.

NavigationBar w Material3: Dynamic Color i adaptacja

Material3 (M3) udostępnia NavigationBar i NavigationBarItem jako zamiennik BottomNavigation z Material2. Główne zalety M3: Dynamic Color, adaptacyjna wysokość, animacja ripple na całej szerokości elementu i obsługa WindowInsets do uwzględnienia nawigacji systemowej.

Dynamic Color automatycznie dobiera kolory NavigationBar na podstawie tapety urządzenia (Android 12+). Aktywny element jest kolorowany na primary, nieaktywne — na onSurfaceVariant. Domyślnie NavigationBar unosi się nad nawigacją systemową przez Modifier.navigationBarsPadding(). Do ręcznego sterowania użyj parametru windowInsets.

NavigationBar w M3 automatycznie animuje szerokość aktywnego elementu — ikona przesuwa się w lewo, a etykieta rozszerza się. Animacja trwa 300 ms i używa standardowego easingu Compose. Jeśli takie zachowanie jest niepożądane, ustaw zawsze widoczną etykietę przez alwaysShowLabel = true w NavigationBarItem.

Dostosowywanie kolorów NavigationBar

Aby zmienić kolory, przekaż NavigationBarColors w parametrze colors. Domyślnie używane jest NavigationBarDefaults.colors(), ale można ustawić własne: containerColor (tło panelu), contentColor (kolor ikon), indicatorColor (tło aktywnego elementu). W przeciwieństwie do M2, gdzie kolory były ustawiane przez BottomNavigationDefaults, w M3 używany jest jednolity interfejs NavColors.

Jeśli w projekcie używany jest BottomNavigation z M2 (androidx.compose.material), Google zaleca migrację do M3 NavigationBar. Migracja obejmuje wymianę importów, aktualizację parametrów i dodanie @OptIn(ExperimentalMaterial3Api::class). BottomNavigation z M2 jest uważany za przestarzały i nie otrzymuje nowych funkcji.

Stan NavigationBar przechowuje indeks aktualnie wybranego elementu. W prostych przypadkach używany jest remember { mutableIntStateOf(0) }. Jednak w aplikacjach produkcyjnych indeks powinien być synchronizowany z grafem nawigacji, a nie być lokalnym stanem UI.

Podstawowe podejścia do zarządzania stanem: NavController-based (zalecane) i ViewModel-based. W pierwszym przypadku selectedItem jest obliczany z bieżącej trasy NavController. W drugim — ViewModel przechowuje bieżący ekran, a NavigationBarItem odczytuje StateFlow z ViewModel. NavController-based jest prostsze, ViewModel-based daje więcej kontroli przy złożonej logice (autoryzacja, testy A/B).

Przy użyciu saveState i restoreState NavController automatycznie zapisuje i przywraca stan LazyColumn i innych komponentów wewnątrz każdej zakładki. Eliminuje to potrzebę stosowania ViewModel do przechowywania pozycji scrolla. Jednak zapisywanie nie ma zastosowania dla pól wejściowych — wymagają one SavedStateHandle lub ViewModel.

Badge powiadomień i liczniki na elementach

Badge to kompaktowy wskaźnik powiadomień wyświetlany na ikonie NavigationBarItem. W Material3 badge jest implementowany przez BadgedBox, który otacza ikonę i umieszcza badge w prawym górnym rogu. Badge może zawierać liczbę (ilość powiadomień) lub kropkę (proste wskazanie na obecność).

kotlin
@Composable
fun HomeItem(unreadCount: Int) {
    NavigationBarItem(
        icon = {
            BadgedBox(badge = {
                if (unreadCount > 0) {
                    Badge { Text(unreadCount.toString()) }
                }
            }) {
                Icon(Icons.Filled.Home, contentDescription = "Strona główna")
            }
        },
        label = { Text("Home") },
        selected = false,
        onClick = { }
    )
}

BadgedBox automatycznie pozycjonuje Badge w prawym górnym rogu elementu podrzędnego. Jeśli liczba przekracza 99, Badge wyświetla „99+“. Aby ukryć badge przy zerowej wartości, użyj warunku if (count > 0). Badge z Material3 domyślnie ma kolor error (czerwony) z białym tekstem. Dostosowanie przez BadgeDefaults.colors(containerColor, contentColor).

W Material2 badge nie istniał — trzeba było rysować go ręcznie przez Canvas lub używać bibliotek zewnętrznych. Material3 rozwiązał ten problem natywnym komponentem. Według Android Developers (2025), BadgedBox jest używany w 40% aplikacji z NavigationBar, z czego 60% — do wyświetlania nieprzeczytanych wiadomości.

Często zadawane pytania

Ile elementów powinno być w BottomNavigation?

Material Design zaleca od 3 do 5 elementów. Mniej niż trzy — niecelowe (lepiej użyć Tabs). Więcej niż pięć — pogarsza czytelność etykiet i zmniejsza touch-arget. Jeśli potrzeba więcej niż 5 sekcji, użyj Navigation Rail (tablety) lub Drawer.

Jak ukryć BottomNavigation na ekranach podrzędnych?

Określ listę tras, na których panel jest widoczny (zwykle główne). W Scaffold bottomBar przekazuj NavigationBar tylko jeśli currentRoute in bottomNavRoutes. Alternatywa — NavHost z osobnym grafem dla ekranów zagnieżdżonych bez bottomBar.

Jaka jest różnica między NavigationBar (M3) a BottomNavigation (M2)?

NavigationBar z Material3 obsługuje Dynamic Color, BadgedBox, WindowInsets i animowaną szerokość aktywnego elementu. BottomNavigation z Material2 to stare API bez tych funkcji. Google zaleca używanie NavigationBar dla nowych projektów i migrację istniejących.

Dlaczego po naciśnięciu elementu BottomNavigation ekran jest odtwarzany?

Problem polega na braku saveState i restoreState w navigate(). Bez nich NavController nie zapisuje stanu poprzedniej zakładki. Dodaj popUpTo(startDestination) { saveState = true } i restoreState = true w NavOptions, aby zachować LazyColumn, scroll i pola wejściowe.

Jak dostosować animację przełączania między zakładkami?

Animacja przełączania jest kontrolowana na poziomie NavHost, a nie BottomNavigation. Dodaj composable(route, enterTransition, exitTransition) z niestandardowymi animacjami. Dla BottomNavigation typowa jest animacja fadingowa (fadeIn + fadeOut) o czasie trwania 300ms.

Podsumowanie

  • BottomNavigation (NavigationBar w M3) — dolny panel do przełączania między 3–5 głównymi ekranami aplikacji
  • NavigationBarItem — element panelu z ikoną, etykietą, badge i stanem wyboru
  • Integracja z NavController wymaga popUpTo, launchSingleTop i restoreState do prawidłowego zarządzania stosem
  • Material3 NavigationBar obsługuje Dynamic Color, BadgedBox, WindowInsets i animację aktywnego elementu
  • BadgedBox — komponent do badge powiadomień z liczbą lub kropką na ikonie elementu
  • Stan wyboru jest obliczany z currentBackStackEntryAsState do synchronizacji z NavController
  • Aby ukryć panel na ekranach podrzędnych, użyj warunkowego renderowania w bottomBar Scaffold na podstawie listy tras

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również