BottomNavigation:什么是它,结构和Jetpack Compose中的设置

作者: IT Sectr 发布日期: 2026-06-29 阅读时间: 8 分钟

BottomNavigation(Material3中的NavigationBar)是一个Material Design组件,用于在应用程序的三到五个主屏幕之间提供导航。在Jetpack Compose中,底部面板通过NavigationBarBottomNavigation实现,其中每个元素由一个图标和一个文本标签表示。根据Google Material Design(2025),BottomNavigation被推荐用于iOS和Android作为主要导航模式。面板通过WindowInsets自动适应系统导航(手势导航)的高度。Material3中的NavigationBar支持动态颜色、徽章和自适应边距。

主要内容

  • BottomNavigation — 通过Material3中的NavigationBar实现的3-5个屏幕的底部导航面板
  • NavigationBarItem — 包含图标、标签和通知徽章的面板元素
  • 与NavHost的连接 — 选择元素会触发NavController中的navigate()来切换图形
  • Material3 添加了动态颜色和通过WindowInsets自动考虑系统导航
  • Badge — 图标上的通知徽章,通过BadgedBox和数字实现

Jetpack Compose中的BottomNavigation是什么?

BottomNavigation是一个底部导航面板的Material Design组件,显示三到五个菜单项。在Jetpack Compose中,通过BottomNavigation(Material2)和NavigationBar(Material3)的可组合函数实现。面板位于屏幕底部,提供对应用程序关键部分的快速访问。

BottomNavigation的主要任务是在屏幕级别之间组织主要导航。面板的每个元素代表一个单独的屏幕(Home、Search、Profile)。点击会在屏幕主区域切换内容。BottomNavigation不适用于嵌套导航——这需要使用顶部的选项卡(Tabs)。

根据Material Design指南(2025),底部面板应仅包含从应用程序任何点都可访问的部分。在子屏幕(例如打开详细页面时)隐藏面板是标准做法。在Compose中,BottomNavigation的可见性通过Scaffold状态或基于当前NavController路由的条件渲染来管理。

在Material3中,建议使用NavigationBar而不是旧版BottomNavigation。NavigationBar包含动态颜色、WindowInsets支持和改进的点击动画。根据Google的说法,NavigationBar是一个前瞻性组件,而M2中的BottomNavigation仅用于向后兼容。

每个NavigationBar元素通过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显示selected的filled变体。如果需要不同的显示,有条件地传递图标: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集成。典型模式:NavigationBarItem中的onClick调用navController.navigate(route),而selectedItem由NavController的当前路由确定。

NavController存储当前返回栈。要确定哪个面板元素被选中,将当前路由(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)防止在切换选项卡时返回栈积累,launchSingleTop排除路由在栈中的重复,而restoreState恢复先前选项卡访问的状态。没有这些参数,每次点击都会在栈中创建一个新条目,导致返回按钮行为不正确。

Material3中的NavigationBar:动态颜色和自适应

Material3(M3)提供NavigationBarNavigationBarItem作为Material2中BottomNavigation的替代品。M3的主要优点:动态颜色、自适应高度、元素全宽涟漪动画以及用于考虑系统导航的WindowInsets支持。

动态颜色根据设备壁纸自动选择NavigationBar的颜色(Android 12+)。活动元素以primary着色,非活动元素以onSurfaceVariant着色。默认情况下,NavigationBar通过Modifier.navigationBarsPadding()提升到系统导航之上。如需手动控制,使用windowInsets参数。

M3中的NavigationBar自动动画化活动元素的宽度——图标向左移动,标签扩展。动画持续300毫秒,并使用标准Compose缓动。如果此行为不可取,通过NavigationBarItem中的alwaysShowLabel = true设置始终显示标签。

自定义NavigationBar颜色

要覆盖颜色,在colors参数中传递NavigationBarColors。默认使用NavigationBarDefaults.colors(),但您可以设置自己的颜色:containerColor(面板背景)、contentColor(图标颜色)、indicatorColor(活动元素背景)。与M2中通过BottomNavigationDefaults设置颜色不同,M3中使用统一的NavColors接口。

如果项目中使用了M2(androidx.compose.material)中的BottomNavigation,Google建议迁移到M3 NavigationBar。迁移包括替换导入、更新参数和添加@OptIn(ExperimentalMaterial3Api::class)。M2中的BottomNavigation被视为已弃用,不会获得新功能。

NavigationBar状态存储当前选定元素的索引。在简单情况下使用remember { mutableIntStateOf(0) }。然而,对于生产应用程序,索引应与导航图同步,而不仅仅是本地UI状态。

状态管理的主要方法:基于NavController(推荐)和基于ViewModel。在前一种情况下,selectedItem从NavController的当前路由计算。在后一种情况下——ViewModel存储当前屏幕,NavigationBarItem从ViewModel读取StateFlow。基于NavController更简单,基于ViewModel在复杂逻辑(授权、A/B测试)中提供更多控制。

使用saveState和restoreState时,NavController自动保存和恢复每个选项卡内LazyColumn和其他组件的状态。这消除了使用ViewModel存储滚动位置的需要。但是,保存不适用于输入字段——它们需要SavedStateHandle或ViewModel。

元素上的通知徽章和计数器

Badge是一个紧凑的通知指示器,显示在NavigationBarItem的图标上。在Material3中,徽章通过BadgedBox实现,它包裹图标并将徽章放置在右上角。徽章可以包含数字(通知数量)或点(简单的存在指示)。

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)条件。Material3中的Badge默认具有error颜色(红色)和白色文本。通过BadgeDefaults.colors(containerColor, contentColor)自定义。

在Material2中,徽章不存在——必须通过Canvas手动绘制或使用第三方库。Material3通过原生组件解决了这个问题。根据Android Developers(2025),BadgedBox被用于40%使用NavigationBar的应用程序,其中60%用于显示未读消息。

常见问题

BottomNavigation应该有多少个元素?

Material Design建议3到5个元素。少于三个——不必要(最好使用Tabs)。多于五个——会降低标签的可读性并减少触摸目标。如果需要超过5个部分,使用Navigation Rail(平板电脑)或Drawer。

如何在子屏幕上隐藏BottomNavigation?

确定面板可见的路由列表(通常是根路由)。在Scaffold bottomBar中,仅当currentRoute在bottomNavRoutes中时才传递NavigationBar。替代方案——使用带有单独图形的NavHost进行嵌套屏幕,无需bottomBar。

NavigationBar(M3)和BottomNavigation(M2)之间有什么区别?

Material3中的NavigationBar支持动态颜色、BadgedBox、WindowInsets和活动元素的动画宽度。Material2中的BottomNavigation是没有这些功能的旧API。Google建议在新项目中使用NavigationBar并迁移现有项目。

为什么点击BottomNavigation元素时屏幕会重新创建?

问题在于navigate()中缺少saveStaterestoreState。没有它们,NavController不会保存先前选项卡的状态。在NavOptions中添加popUpTo(startDestination) { saveState = true }和restoreState = true以保留LazyColumn、滚动和输入字段。

如何自定义选项卡之间的切换动画?

切换动画在NavHost级别控制,而不是BottomNavigation。添加带有自定义动画的composable(route, enterTransition, exitTransition)。对于BottomNavigation,典型的动画是淡入淡出(fadeIn + fadeOut),持续300ms。

总结

  • BottomNavigation(M3中的NavigationBar)— 用于在应用程序的3-5个主屏幕之间切换的底部面板
  • NavigationBarItem — 带有图标、标签、徽章和选择状态的面板元素
  • 与NavController集成需要popUpTo、launchSingleTop和restoreState以正确管理栈
  • Material3 NavigationBar支持动态颜色、BadgedBox、WindowInsets和活动元素动画
  • BadgedBox — 用于元素图标上带数字或点的通知徽章的组件
  • 选择状态从currentBackStackEntryAsState计算以与NavController同步
  • 要在子屏幕上隐藏面板,在Scaffold bottomBar中根据路由列表使用条件渲染

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读