BottomNavigation (NavigationBar di Material3) adalah komponen Material Design yang menyediakan navigasi antara tiga hingga lima layar utama aplikasi. Di Jetpack Compose, panel bawah diimplementasikan melalui NavigationBar atau BottomNavigation, di mana setiap elemen diwakili oleh ikon dan label teks. Menurut Google Material Design (2025), BottomNavigation direkomendasikan untuk iOS dan Android sebagai pola navigasi utama. Panel secara otomatis menyesuaikan dengan tinggi navigasi sistem (Gesture Navigation) melalui WindowInsets. NavigationBar di Material3 mendukung dynamic color, badge, dan jarak adaptif.
Poin utama
BottomNavigation adalah komponen Material Design untuk panel navigasi bawah yang menampilkan tiga hingga lima item menu. Di Jetpack Compose, ini diimplementasikan melalui fungsi composable BottomNavigation (Material2) dan NavigationBar (Material3). Panel ditempatkan di bagian bawah layar dan menyediakan akses cepat ke bagian-bagian utama aplikasi.
Tugas utama BottomNavigation adalah mengatur navigasi primer antara tingkat layar. Setiap elemen panel mewakili layar terpisah (Home, Search, Profile). Tekanan mengalihkan konten di area utama layar. BottomNavigation tidak ditujukan untuk navigasi bersarang — untuk itu digunakan tab (Tabs) di bagian atas.
Menurut Material Design Guidelines (2025), panel bawah hanya boleh berisi bagian-bagian yang dapat diakses dari titik mana pun dalam aplikasi. Menyembunyikan panel di layar anak (misalnya, saat membuka halaman detail) adalah praktik standar. Di Compose, visibilitas BottomNavigation dikelola melalui status Scaffold atau rendering bersyarat berdasarkan rute NavController saat ini.
Di Material3, dianjurkan menggunakan NavigationBar daripada Legacy BottomNavigation. NavigationBar mencakup Dynamic Color, dukungan WindowInsets, dan animasi tekanan yang lebih baik. Menurut Google, NavigationBar adalah komponen prospektif, sedangkan BottomNavigation dari M2 tetap hanya untuk kompatibilitas mundur.
Setiap elemen NavigationBar diimplementasikan melalui composable NavigationBarItem. Parameter elemen: icon (ikon), label (label teks), selected (status pemilihan) dan onClick (panggilan balik). NavigationBarItem secara otomatis menerapkan warna yang dipilih (primary) dan animasi perubahan ukuran ikon saat aktivasi.
@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 }
)
}
}
}
Ikon untuk NavigationBarItem harus Filled untuk status dipilih dan Outlined untuk status tidak dipilih. Material Icons menyediakan kedua set: Icons.Filled.Home dan Icons.Outlined.Home. Secara default, NavigationBarItem menampilkan varian filled untuk selected. Jika tampilan berbeda diperlukan, berikan ikon secara bersyarat: if (selected) Filled.X else Outlined.X.
Panjang label teks elemen — tidak lebih dari 12 karakter (alfabet Latin) atau 8 karakter (Sirilik). Label panjang dipotong dengan elipsis. Untuk label, gunakan maxLines = 1 dan ellipsis = TextTruncation.Ellipsis. Jika label selalu diperlukan, atur parameter alwaysShowLabel = true (default true).
| Parameter | Tipe | Deskripsi |
|---|---|---|
| icon | @Composable () -> Unit | Ikon elemen panel |
| label | @Composable (() -> Unit)? | Label teks di bawah ikon |
| selected | Boolean | Bendera elemen yang dipilih |
| onClick | () -> Unit | Panggilan balik saat ditekan |
| badge | @Composable (() -> Unit)? | Lencana dengan jumlah notifikasi |
Pembaruan langsung selectedItem tidak mengarahkan navigasi — untuk mengganti layar diperlukan integrasi dengan NavController. Pola tipikal: onClick di NavigationBarItem memanggil navController.navigate(route), dan selectedItem ditentukan dari rute NavController saat ini.
NavController menyimpan back stack saat ini. Untuk menentukan elemen panel mana yang dipilih, bandingkan rute saat ini (navController.currentBackStackEntryAsState().value?.destination?.route) dengan rute elemen. Jika rute cocok — elemen dianggap dipilih. Ini menghilangkan duplikasi status.
@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 */ }
}
Pengaturan kunci dalam navigate(): popUpTo(startDestination) mencegah penumpukan back stack saat mengganti tab, launchSingleTop mengecualikan duplikasi rute dalam tumpukan, dan restoreState mengembalikan status kunjungan sebelumnya ke tab. Tanpa parameter ini, setiap tekanan membuat entri baru dalam tumpukan, yang menyebabkan perilaku tombol Kembali yang tidak benar.
Material3 (M3) menyediakan NavigationBar dan NavigationBarItem sebagai pengganti BottomNavigation dari Material2. Keunggulan utama M3: Dynamic Color, tinggi adaptif, animasi ripple selebar elemen, dan dukungan WindowInsets untuk memperhitungkan navigasi sistem.
Dynamic Color secara otomatis memilih warna NavigationBar berdasarkan wallpaper perangkat (Android 12+). Elemen aktif diwarnai primary, yang tidak aktif — onSurfaceVariant. Secara default, NavigationBar naik di atas navigasi sistem melalui Modifier.navigationBarsPadding(). Untuk kontrol manual, gunakan parameter windowInsets.
NavigationBar di M3 secara otomatis menganimasikan lebar elemen aktif — ikon bergeser ke kiri dan label meluas. Animasi berlangsung 300 ms dan menggunakan easing standar Compose. Jika perilaku ini tidak diinginkan, atur label selalu terlihat melalui alwaysShowLabel = true di NavigationBarItem.
Untuk mengganti warna, berikan NavigationBarColors dalam parameter colors. Secara default, NavigationBarDefaults.colors() digunakan, tetapi Anda dapat mengatur warna sendiri: containerColor (latar panel), contentColor (warna ikon), indicatorColor (latar elemen aktif). Berbeda dengan M2, di mana warna diatur melalui BottomNavigationDefaults, di M3 digunakan antarmuka seragam NavColors.
Jika dalam proyek digunakan BottomNavigation dari M2 (androidx.compose.material), Google merekomendasikan migrasi ke M3 NavigationBar. Migrasi mencakup penggantian impor, pembaruan parameter, dan penambahan @OptIn(ExperimentalMaterial3Api::class). BottomNavigation dari M2 dianggap usang dan tidak menerima fitur baru.
Status NavigationBar menyimpan indeks elemen yang sedang dipilih. Dalam kasus sederhana, remember { mutableIntStateOf(0) } digunakan. Namun, untuk aplikasi produksi, indeks harus disinkronkan dengan grafik navigasi, bukan menjadi status UI lokal.
Pendekatan utama untuk manajemen status: NavController-based (direkomendasikan) dan ViewModel-based. Dalam kasus pertama, selectedItem dihitung dari rute NavController saat ini. Dalam kasus kedua — ViewModel menyimpan layar saat ini, dan NavigationBarItem membaca StateFlow dari ViewModel. NavController-based lebih sederhana, ViewModel-based memberikan lebih banyak kontrol dalam logika kompleks (otorisasi, tes A/B).
Saat menggunakan saveState dan restoreState, NavController secara otomatis menyimpan dan mengembalikan status LazyColumn dan komponen lain di dalam setiap tab. Ini menghilangkan kebutuhan ViewModel untuk menyimpan posisi scroll. Namun, penyimpanan tidak berlaku untuk bidang input — mereka memerlukan SavedStateHandle atau ViewModel.
Badge adalah indikator notifikasi kompak yang ditampilkan pada ikon NavigationBarItem. Di Material3, lencana diimplementasikan melalui BadgedBox, yang membungkus ikon dan menempatkan lencana di sudut kanan atas. Badge dapat berisi angka (jumlah notifikasi) atau titik (indikasi keberadaan sederhana).
@Composable
fun HomeItem(unreadCount: Int) {
NavigationBarItem(
icon = {
BadgedBox(badge = {
if (unreadCount > 0) {
Badge { Text(unreadCount.toString()) }
}
}) {
Icon(Icons.Filled.Home, contentDescription = "Beranda")
}
},
label = { Text("Home") },
selected = false,
onClick = { }
)
}
BadgedBox secara otomatis memposisikan Badge di sudut kanan atas elemen anak. Jika angka melebihi 99, Badge menampilkan “99+”. Untuk menyembunyikan lencana saat nilai nol, gunakan kondisi if (count > 0). Badge dari Material3 secara default memiliki warna error (merah) dengan teks putih. Kustomisasi melalui BadgeDefaults.colors(containerColor, contentColor).
Di Material2, lencana tidak ada — harus digambar manual melalui Canvas atau menggunakan pustaka pihak ketiga. Material3 memecahkan masalah ini dengan komponen asli. Menurut Android Developers (2025), BadgedBox digunakan dalam 40% aplikasi dengan NavigationBar, di mana 60% digunakan untuk menampilkan pesan yang belum dibaca.
Pertanyaan yang Sering Diajukan
Material Design merekomendasikan 3 hingga 5 elemen. Kurang dari tiga — tidak berguna (lebih baik menggunakan Tabs). Lebih dari lima — mengurangi keterbacaan label dan menurunkan touch-target. Jika diperlukan lebih dari 5 bagian, gunakan Navigation Rail (tablet) atau Drawer.
Tentukan daftar rute di mana panel terlihat (biasanya rute root). Di Scaffold bottomBar, berikan NavigationBar hanya jika currentRoute in bottomNavRoutes. Alternatif — NavHost dengan grafik terpisah untuk layar bersarang tanpa bottomBar.
NavigationBar dari Material3 mendukung Dynamic Color, BadgedBox, WindowInsets, dan lebar animasi elemen aktif. BottomNavigation dari Material2 adalah API lama tanpa fitur ini. Google merekomendasikan menggunakan NavigationBar untuk proyek baru dan migrasi proyek yang ada.
Masalahnya adalah tidak adanya saveState dan restoreState di navigate(). Tanpa mereka, NavController tidak menyimpan status tab sebelumnya. Tambahkan popUpTo(startDestination) { saveState = true } dan restoreState = true di NavOptions untuk mempertahankan LazyColumn, scroll, dan bidang input.
Animasi perpindahan dikendalikan di tingkat NavHost, bukan BottomNavigation. Tambahkan composable(route, enterTransition, exitTransition) dengan animasi kustom. Untuk BottomNavigation, animasi fading (fadeIn + fadeOut) dengan durasi 300ms adalah tipikal.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga