如何在Jetpack Compose底部导航栏选中图标上方添加圆点并自定义颜色
解决方案
一、添加选中项顶部外部圆点(无图片实现)
要实现导航栏外部的顶部圆点,可通过Box布局嵌套实现:
- 给整个BottomBar外层套一个Box,让导航栏和圆点处于同一层级;
- 单独封装导航项组件,选中时显示顶部圆点,通过
offset将圆点定位到导航栏外部。
二、图标自定义颜色优化
通过统一的状态管理切换图标tint,同时支持传入自定义颜色参数,实现更灵活的颜色控制。
重构后的完整代码
@Composable fun BottomBar(modifier: Modifier = Modifier) { var navNum by remember { mutableStateOf(0) } // 统一管理导航项数据 val navItems = listOf( NavItem(R.drawable.home_filled, R.drawable.home_light, "home", 0), NavItem(R.drawable.calendar_filled, R.drawable.calendar_light, "calendar", 1), NavItem(R.drawable.message_filled, R.drawable.message_light, "message", 2), NavItem(R.drawable.user_filled, R.drawable.user_light, "user", 3) ) Box(modifier = modifier.fillMaxWidth()) { // 底部导航栏主体 Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(topStart = 30.dp, topEnd = 30.dp)) .background(CardColor) .padding(vertical = 15.dp, horizontal = 15.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Row(verticalAlignment = Alignment.CenterVertically) { navItems.take(2).forEach { item -> BottomNavItem( isSelected = navNum == item.index, selectedIcon = item.selectedIcon, unselectedIcon = item.unselectedIcon, contentDescription = item.desc, onClick = { navNum = item.index } ) if (item.index != 1) Spacer(modifier = Modifier.width(8.dp)) } } Row(verticalAlignment = Alignment.CenterVertically) { navItems.takeLast(2).forEach { item -> BottomNavItem( isSelected = navNum == item.index, selectedIcon = item.selectedIcon, unselectedIcon = item.unselectedIcon, contentDescription = item.desc, onClick = { navNum = item.index } ) if (item.index != 3) Spacer(modifier = Modifier.width(8.dp)) } } } // 顶部选中圆点 Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 15.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top ) { Row(verticalAlignment = Alignment.CenterVertically) { navItems.take(2).forEachIndexed { index, item -> if (navNum == item.index) { Box( modifier = Modifier .size(8.dp) .background(SecondaryColor, CircleShape) .offset(y = (-20).dp) // 上移至导航栏外部 ) } if (index != 1) Spacer(modifier = Modifier.width(33.dp)) // 匹配图标间距 } } Row(verticalAlignment = Alignment.CenterVertically) { navItems.takeLast(2).forEachIndexed { index, item -> if (navNum == item.index) { Box( modifier = Modifier .size(8.dp) .background(SecondaryColor, CircleShape) .offset(y = (-20).dp) ) } if (index != 1) Spacer(modifier = Modifier.width(33.dp)) } } } } } // 导航项数据类,统一管理图标、索引等信息 data class NavItem( val selectedIcon: Int, val unselectedIcon: Int, val desc: String, val index: Int ) @Composable fun BottomNavItem( isSelected: Boolean, selectedIcon: Int, unselectedIcon: Int, contentDescription: String, onClick: () -> Unit, selectedColor: Color = SecondaryColor, unselectedColor: Color = ThinTextColor ) { IconButton(onClick = onClick) { Icon( painter = painterResource( id = if (isSelected) selectedIcon else unselectedIcon ), contentDescription = contentDescription, tint = if (isSelected) selectedColor else unselectedColor, modifier = Modifier.size(25.dp) ) } }
关键说明
- 顶部圆点实现:
- 用独立Row布局放置圆点,通过
offset(y = (-20).dp)将圆点上移到导航栏背景外; - 借助
Box+CircleShape绘制圆形,无需图片资源,颜色直接复用现有配色。
- 用独立Row布局放置圆点,通过
- 图标颜色自定义:
BottomNavItem组件新增selectedColor和unselectedColor参数,可直接传入自定义颜色;- 利用
tint属性动态切换颜色,若使用矢量图标,甚至可只用一套图标资源通过tint切换状态。
- 代码优化:
- 提取
NavItem数据类统一管理导航信息,消除重复的if-else逻辑; - 封装
BottomNavItem组件,简化主布局代码,提升可维护性。
- 提取
内容的提问来源于stack exchange,提问作者Baba Yaga
相关产品推荐
相关产品推荐

