BottomNavigation (NavigationBar در Material3) — کامپوننتی از Material Design است که ناوبری بین سه تا پنج صفحه اصلی برنامه را فراهم میکند. در Jetpack Compose پنل پایین از طریق NavigationBar یا BottomNavigation پیادهسازی میشود، جایی که هر عنصر با یک آیکون و برچسب متنی نمایش داده میشود. طبق Google Material Design (2025)، BottomNavigation برای iOS و Android به عنوان الگوی اصلی ناوبری اولیه توصیه میشود. پنل به طور خودکار از طریق WindowInsets با ارتفاع ناوبری سیستم (Gesture Navigation) تطبیق مییابد. NavigationBar در Material3 از dynamic color، badge و فاصلههای تطبیقی پشتیبانی میکند.
نکات اصلی
BottomNavigation — کامپوننت Material Design برای پنل ناوبری پایین است که سه تا پنج آیتم منو را نمایش میدهد. در Jetpack Compose از طریق توابع composable BottomNavigation (Material2) و NavigationBar (Material3) پیادهسازی میشود. پنل در پایین صفحه قرار میگیرد و دسترسی سریع به بخشهای کلیدی برنامه را فراهم میکند.
وظیفه اصلی BottomNavigation سازماندهی ناوبری اولیه بین سطوح صفحه است. هر عنصر پنل یک صفحه جداگانه (Home, Search, Profile) را نمایش میدهد. فشار دادن محتوا را در ناحیه اصلی صفحه تغییر میدهد. BottomNavigation برای ناوبری تودرتو طراحی نشده است — برای این کار از تبها (Tabs) در قسمت بالا استفاده میشود.
طبق Material Design Guidelines (2025)، پنل پایین باید فقط شامل بخشهایی باشد که از هر نقطه برنامه قابل دسترسی هستند. مخفی کردن پنل در صفحات زیرمجموعه (مثلاً هنگام باز کردن صفحه جزئیات) یک روش استاندارد است. در Compose، visibility BottomNavigation از طریق وضعیت Scaffold یا رندر شرطی بر اساس مسیر فعلی NavController مدیریت میشود.
در Material3 توصیه میشود به جای Legacy BottomNavigation از NavigationBar استفاده کنید. NavigationBar شامل Dynamic Color، پشتیبانی از WindowInsets و انیمیشن بهبود یافته فشار است. به گفته Google، NavigationBar یک کامپوننت آیندهنگر است و BottomNavigation از M2 فقط برای سازگاری معکوس باقی مانده است.
هر عنصر NavigationBar از طریق composable NavigationBarItem پیادهسازی میشود. پارامترهای عنصر: icon (آیکون)، label (برچسب متنی)، selected (وضعیت انتخاب) و onClick (فراخوان). NavigationBarItem به طور خودکار رنگ انتخابشده (primary) و انیمیشن تغییر اندازه آیکون هنگام فعالسازی را اعمال میکند.
@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.
طول برچسب متنی عنصر — بیش از ۱۲ کاراکتر (لاتین) یا ۸ کاراکتر (سیریلیک) نباشد. برچسبهای بلند با سه نقطه بریده میشوند. برای برچسبها از maxLines = 1 و ellipsis = TextTruncation.Ellipsis استفاده کنید. اگر برچسب همیشه لازم است، پارامتر alwaysShowLabel = true (پیشفرض true) را تنظیم کنید.
| پارامتر | نوع | توضیحات |
|---|---|---|
| icon | @Composable () -> Unit | آیکون عنصر پنل |
| label | @Composable (() -> Unit)? | برچسب متنی زیر آیکون |
| selected | Boolean | پرچم عنصر انتخابشده |
| onClick | () -> Unit | فراخوان هنگام کلیک |
| badge | @Composable (() -> Unit)? | نشان با تعداد اعلانها |
بهروزرسانی مستقیم selectedItem ناوبری را هدایت نمیکند — برای تغییر صفحهها به یکپارچگی با NavController نیاز است. الگوی معمول: onClick در NavigationBarItem navController.navigate(route) را فراخوانی میکند و selectedItem از مسیر فعلی NavController تعیین میشود.
NavController back stack فعلی را ذخیره میکند. برای تعیین اینکه کدام عنصر پنل انتخاب شده است، مسیر فعلی (navController.currentBackStackEntryAsState().value?.destination?.route) را با مسیرهای عناصر مقایسه کنید. اگر مسیر مطابقت داشت — عنصر انتخابشده در نظر گرفته میشود. این کار از تکرار وضعیت جلوگیری میکند.
@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 وضعیت بازدید قبلی از تب را بازیابی میکند. بدون این پارامترها، هر کلیک یک ورودی جدید در پشته ایجاد میکند که منجر به رفتار نادرست دکمه بازگشت میشود.
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 میلیثانیه طول میکشد و از easing استاندارد Compose استفاده میکند. اگر این رفتار نامطلوب است، برچسب را همیشه از طریق alwaysShowLabel = true در NavigationBarItem نمایش دهید.
برای تغییر رنگها، NavigationBarColors را در پارامتر colors ارسال کنید. به طور پیشفرض از NavigationBarDefaults.colors() استفاده میشود، اما میتوانید رنگهای خود را تنظیم کنید: containerColor (زمینه پنل)، contentColor (رنگ آیکونها)، indicatorColor (زمینه عنصر فعال). برخلاف M2 که رنگها از طریق BottomNavigationDefaults تنظیم میشدند، در M3 از رابط یکپارچه NavColors استفاده میشود.
اگر در پروژه از BottomNavigation از M2 (androidx.compose.material) استفاده میشود، Google مهاجرت به M3 NavigationBar را توصیه میکند. مهاجرت شامل جایگزینی importها، بهروزرسانی پارامترها و افزودن @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 badge از طریق BadgedBox پیادهسازی میشود که آیکون را احاطه کرده و badge را در گوشه بالا سمت راست قرار میدهد. Badge میتواند شامل عدد (تعداد اعلانها) یا نقطه (نشاندهنده ساده وجود) باشد.
@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 را در گوشه بالا سمت راست عنصر فرزند قرار میدهد. اگر عدد از ۹۹ بیشتر شود، Badge “۹۹+” را نمایش میدهد. برای مخفی کردن badge در مقدار صفر از شرط if (count > 0) استفاده کنید. Badge از Material3 به طور پیشفرض رنگ error (قرمز) با متن سفید دارد. سفارشیسازی از طریق BadgeDefaults.colors(containerColor, contentColor).
در Material2 badge وجود نداشت — باید آن را به صورت دستی از طریق Canvas یا با استفاده از کتابخانههای شخص ثالث رسم میکردید. Material3 این مشکل را با یک کامپوننت بومی حل کرد. طبق Android Developers (2025)، BadgedBox در ۴۰٪ برنامههای دارای NavigationBar استفاده میشود که از این تعداد ۶۰٪ برای نمایش پیامهای خواندهنشده است.
سوالات متداول
Material Design از ۳ تا ۵ عنصر را توصیه میکند. کمتر از سه — غیرمنطقی است (بهتر است از Tabs استفاده کنید). بیشتر از پنج — خوانایی برچسبها را کاهش میدهد و touch-target را کم میکند. اگر بیش از ۵ بخش نیاز است، از Navigation Rail (تبلتها) یا Drawer استفاده کنید.
لیست مسیرهایی که پنل در آنها قابل مشاهده است را مشخص کنید (معمولاً مسیرهای ریشه). در Scaffold bottomBar، NavigationBar را فقط در صورتی ارسال کنید که currentRoute in bottomNavRoutes باشد. جایگزین — NavHost با گراف جداگانه برای صفحات تودرتو بدون bottomBar.
NavigationBar از Material3 از Dynamic Color، BadgedBox، WindowInsets و عرض متحرک عنصر فعال پشتیبانی میکند. BottomNavigation از Material2 یک API قدیمی بدون این ویژگیها است. Google استفاده از NavigationBar را برای پروژههای جدید و مهاجرت پروژههای موجود توصیه میکند.
مشکل عدم وجود saveState و restoreState در navigate() است. بدون آنها NavController وضعیت تب قبلی را ذخیره نمیکند. برای حفظ LazyColumn، اسکرول و فیلدهای ورودی، popUpTo(startDestination) { saveState = true } و restoreState = true را در NavOptions اضافه کنید.
انیمیشن جابهجایی در سطح NavHost کنترل میشود، نه BottomNavigation. composable(route, enterTransition, exitTransition) را با انیمیشنهای سفارشی اضافه کنید. برای BottomNavigation انیمیشن fading (fadeIn + fadeOut) با مدت ۳۰۰ میلیثانیه معمول است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید