如何在Jetpack Compose中实现带动画的弧形及指定样式底部导航栏?
Jetpack Compose 弧形底部导航栏实现(含切换动画)
一、实现目标样式的弧形底部导航
针对你提供的样式(中间带凸起弧形的底部导航),我们可以通过自定义Canvas绘制背景+导航项组合的方式实现:
1. 定义导航项数据类
先封装导航栏所需的基础信息:
data class BottomNavItem( val title: String, val icon: ImageVector, val route: String )
2. 绘制弧形背景
用Canvas绘制带凸起的底部背景,凸起位置跟随选中项:
@Composable fun CurvedBottomNavBackground(selectedIndex: Int, itemCount: Int, screenWidth: Dp) { val density = LocalDensity.current val curveHeight = 32.dp // 弧形凸起高度,可自定义 val itemWidth = screenWidth / itemCount Canvas(modifier = Modifier.fillMaxWidth().height(72.dp)) { val path = Path().apply { moveTo(0f, 0f) // 绘制到弧形左侧起点 lineTo((selectedIndex * itemWidth).toPx(), 0f) // 用二次贝塞尔曲线绘制凸起弧形 quadraticBezierTo( (selectedIndex * itemWidth + itemWidth / 2).toPx(), -curveHeight.toPx(density), ((selectedIndex + 1) * itemWidth).toPx(), 0f ) // 绘制剩余边界并闭合路径 lineTo(size.width, 0f) lineTo(size.width, size.height) lineTo(0f, size.height) close() } drawPath(path, color = MaterialTheme.colorScheme.primaryContainer) } }
3. 实现单个导航项
包含图标和可选的标题,选中时切换样式:
@Composable fun BottomNavItem( item: BottomNavItem, isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = item.icon, contentDescription = item.title, tint = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp) ) if (isSelected) { Text( text = item.title, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary ) } } }
4. 组装完整导航栏
将背景和导航项组合到一起:
@Composable fun CurvedBottomNavigation( items: List<BottomNavItem>, selectedIndex: Int, onItemSelected: (Int) -> Unit ) { val screenWidth = LocalConfiguration.current.screenWidthDp.dp Box(modifier = Modifier.fillMaxWidth()) { // 弧形背景 CurvedBottomNavBackground(selectedIndex, items.size, screenWidth) // 横向排列导航项 Row( modifier = Modifier .fillMaxWidth() .padding(top = 16.dp, bottom = 16.dp), horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically ) { items.forEachIndexed { index, item -> BottomNavItem( item = item, isSelected = index == selectedIndex, onClick = { onItemSelected(index) } ) } } } }
二、添加切换动画效果
通过Compose的动画API,让弧形移动和导航项缩放更平滑:
1. 动画化选中状态
修改导航栏组件,添加动画驱动的选中索引和缩放效果:
@Composable fun AnimatedCurvedBottomNavigation( items: List<BottomNavItem>, selectedIndex: Int, onItemSelected: (Int) -> Unit ) { val screenWidth = LocalConfiguration.current.screenWidthDp.dp // 弧形移动动画 val animatedSelectedIndex by animateIntAsState( targetValue = selectedIndex, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) Box(modifier = Modifier.fillMaxWidth()) { CurvedBottomNavBackground(animatedSelectedIndex, items.size, screenWidth) Row( modifier = Modifier .fillMaxWidth() .padding(top = 16.dp, bottom = 16.dp), horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically ) { items.forEachIndexed { index, item -> // 导航项缩放动画 val scale by animateFloatAsState( targetValue = if (index == selectedIndex) 1.1f else 1f, animationSpec = tween(durationMillis = 300) ) BottomNavItem( item = item, isSelected = index == selectedIndex, onClick = { onItemSelected(index) }, modifier = Modifier.scale(scale) ) } } } }
2. 使用示例
在Scaffold中集成导航栏:
@Composable fun MainScreen() { val navItems = listOf( BottomNavItem("首页", Icons.Default.Home, "home"), BottomNavItem("发现", Icons.Default.Explore, "explore"), BottomNavItem("发布", Icons.Default.AddCircle, "post"), BottomNavItem("消息", Icons.Default.Message, "message"), BottomNavItem("我的", Icons.Default.Person, "profile") ) var selectedIndex by remember { mutableStateOf(0) } Scaffold( bottomBar = { AnimatedCurvedBottomNavigation( items = navItems, selectedIndex = selectedIndex, onItemSelected = { selectedIndex = it } ) } ) { padding -> Box(modifier = Modifier.padding(padding).fillMaxSize(), contentAlignment = Alignment.Center) { Text(text = "当前页面:${navItems[selectedIndex].title}") } } }
你可以根据需求调整curveHeight、动画时长、颜色值等参数,适配自己的UI风格。
内容的提问来源于stack exchange,提问作者Danil
相关产品推荐
相关产品推荐

