如何用Jetpack Compose实现类似iOS CircleBar的Android BottomNavigationMenu?
一、带动态FloatingActionButton的BottomNavigation
核心思路
用Compose的BottomNavigation配合FloatingActionButton,通过状态控制FAB的显示/隐藏、位置偏移,实现与底部导航的联动效果。
实现代码示例
@Composable fun DynamicFABBottomNavigation( selectedTab: Int, onTabSelected: (Int) -> Unit, fabClick: () -> Unit ) { // 自定义FAB显示逻辑,示例为选中中间tab时显示 val showFab = selectedTab == 2 // FAB平滑弹出/隐藏的偏移动画 val fabOffsetY by animateDpAsState( targetValue = if (showFab) 0.dp else (-50).dp, animationSpec = tween(300) ) Box(modifier = Modifier.fillMaxSize()) { Scaffold( bottomBar = { BottomNavigation(backgroundColor = Color.White) { listOf("首页", "消息", "发布", "我的").forEachIndexed { index, title -> BottomNavigationItem( selected = selectedTab == index, onClick = { onTabSelected(index) }, icon = { Icon( imageVector = when(index) { 0 -> Icons.Default.Home 1 -> Icons.Default.Message 2 -> Icons.Default.Add else -> Icons.Default.Person }, contentDescription = title ) }, label = { Text(title) }, selectedContentColor = Color.Blue, unselectedContentColor = Color.Gray ) } } } ) { paddingValues -> // 页面内容区域,自动适配导航栏高度避免遮挡 Box(modifier = Modifier.padding(paddingValues)) { // 填充你的页面内容 } } // 动态FAB if (showFab) { FloatingActionButton( onClick = fabClick, modifier = Modifier .align(Alignment.BottomCenter) .offset(y = fabOffsetY) .padding(bottom = 16.dp), containerColor = Color.Blue ) { Icon(Icons.Default.Add, contentDescription = "发布") } } } }
关键说明
- 通过
selectedTab状态灵活控制FAB的显示条件,可根据业务需求调整 - 用
animateDpAsState实现FAB的平滑过渡动画,提升交互体验 Scaffold的bottomBar参数自动处理导航栏与页面内容的间距,无需手动计算
二、实现类似CircleBar的BottomNavigation
CircleBar的核心特征是中间导航项为圆形凸起样式,选中时放大并显示圆形背景,交互时有平滑动画,以下是Compose的实现方案:
核心思路
自定义BottomNavigationItem的布局,利用Compose动画API实现选中状态的样式变化,包括背景缩放、图标放大、颜色过渡。
实现代码示例
@Composable fun CircleBarStyleBottomNavigation( selectedTab: Int, onTabSelected: (Int) -> Unit ) { BottomNavigation( backgroundColor = Color.White, modifier = Modifier.height(60.dp) ) { val navItems = listOf( Pair(Icons.Default.Home, "首页"), Pair(Icons.Default.Search, "搜索"), Pair(Icons.Default.Add, "发布"), Pair(Icons.Default.Favorite, "收藏"), Pair(Icons.Default.Person, "我的") ) navItems.forEachIndexed { index, (icon, title) -> val isSelected = selectedTab == index // 选中时的缩放动画 val scale by animateFloatAsState( targetValue = if (isSelected && index == 2) 1.2f else 1f, animationSpec = spring(stiffness = Spring.StiffnessMedium) ) // 选中时的背景颜色过渡动画 val bgColor by animateColorAsState( targetValue = if (isSelected && index == 2) Color.Blue else Color.Transparent, animationSpec = tween(300) ) BottomNavigationItem( selected = isSelected, onClick = { onTabSelected(index) }, icon = { Box( modifier = Modifier .scale(scale) .background(bgColor, shape = CircleShape) .padding(if (isSelected && index == 2) 12.dp else 0.dp), contentAlignment = Alignment.Center ) { Icon( imageVector = icon, contentDescription = title, tint = if (isSelected) Color.White else Color.Gray, modifier = Modifier.size(24.dp) ) } }, label = { Text( title, color = if (isSelected) Color.Blue else Color.Gray, fontSize = 12.sp ) }, selectedContentColor = Color.Blue, unselectedContentColor = Color.Gray, // 中间项向上凸起 modifier = if (index == 2) Modifier.padding(top = (-16).dp) else Modifier ) } } }
关键说明
- 针对中间导航项(index=2)通过
Modifier.padding(top = (-16).dp)实现向上凸起的视觉效果 - 用
animateFloatAsState实现选中时的图标缩放,animateColorAsState实现圆形背景的颜色过渡 - 圆形背景通过
background(bgColor, shape = CircleShape)配合padding调整凸起范围
使用示例
在你的Compose页面中直接调用组件即可:
@Composable fun MainScreen() { var selectedTab by remember { mutableStateOf(0) } // 带动态FAB的导航栏 DynamicFABBottomNavigation( selectedTab = selectedTab, onTabSelected = { selectedTab = it }, fabClick = { /* 处理发布逻辑 */ } ) // 或者使用CircleBar风格的导航栏 // CircleBarStyleBottomNavigation( // selectedTab = selectedTab, // onTabSelected = { selectedTab = it } // ) }
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

