Jetpack Compose自定义BottomNavigationAppBar的两个问题求助
问题1:贝塞尔曲线不对称且被截断
问题分析
当前IndentPath的核心问题:
- 起始点
start.x = -90f超出rect左边界,导致曲线左侧被clip截断 - 控制点坐标完全不对称,且依赖固定
maxX/maxY映射,无法适配动态rect尺寸,引发曲线变形 - 曲线终点与控制点逻辑混乱,未以indent中心为对称基准
解决方案
重构IndentPath,以indent中心为对称点,用相对比例定义曲线,确保对称且不超出边界:
class IndentPath(private val rect: Rect) { // 以indent中心为原点的相对坐标映射(x范围-1到1,y范围0到1) private fun translateRelative(x: Float, y: Float): PointF { val centerX = rect.centerX() val topY = rect.top return PointF( centerX + x * rect.width / 2, topY + y * rect.height ) } fun createPath(): Path { val path = Path() // 起点/终点:indent左右端点(与bar底部对齐) val start = translateRelative(-1f, 0f) val end = translateRelative(1f, 0f) // 曲线最低点(钟形底部) val bottom = translateRelative(0f, 1f) // 对称控制点:保证左右曲线完全镜像 val leftControl1 = translateRelative(-0.8f, 0.2f) val leftControl2 = translateRelative(-0.2f, 0.9f) val rightControl1 = translateRelative(0.2f, 0.9f) val rightControl2 = translateRelative(0.8f, 0.2f) path.moveTo(start.x, start.y) path.cubicTo(leftControl1.x, leftControl1.y, leftControl2.x, leftControl2.y, bottom.x, bottom.y) path.cubicTo(rightControl1.x, rightControl1.y, rightControl2.x, rightControl2.y, end.x, end.y) return path } }
额外注意:确保StraightIndent的indentWidth与导航项宽度匹配,避免曲线范围超出项边界。
问题2:切换图标时的图标/Logo切换动画
实现思路
利用Jetpack Compose的AnimatedContent结合缩放、透明度动画,实现新旧图标的过渡效果,通过prevSelectedIndex记录上一选中项触发跨项动画。
解决方案
修改ColorButton组件,添加图标切换动画:
@Composable fun ColorButton( modifier: Modifier = Modifier, prevSelectedIndex: Int, selectedIndex: Int, index: Int, onClick: () -> Unit, icon: @Composable () -> Unit, selectedIcon: @Composable () -> Unit, // 新增选中状态图标 contentDescription: String, animationType: AnimationType, background: Color ) { val isSelected = selectedIndex == index Box( modifier = modifier .clickable(onClick = onClick) .padding(16.dp), contentAlignment = Alignment.Center ) { AnimatedContent( targetState = isSelected, transitionSpec = { // 切换动画:选中时新图标放大淡入,旧图标缩小淡出;取消选中则反向 if (targetState) { scaleIn(spring(stiffness = Spring.StiffnessMedium)) + fadeIn() with scaleOut(targetScale = 0.7f) + fadeOut() } else { scaleIn(initialScale = 0.7f) + fadeIn() with scaleOut(targetScale = 1.2f) + fadeOut() }.using(SizeTransform(clip = false)) } ) { selected -> if (selected) selectedIcon() else icon() } } }
使用示例
在MyCustomAnimatedBottomNavBar中传入选中状态图标:
ColorButton( // ...其他参数 icon = { Icon(Icons.Default.Home, contentDescription = contentDescription) }, selectedIcon = { Icon(Icons.Default.HomeFilled, contentDescription = contentDescription) } )
若需图标到Logo的变形动画,可使用AnimatedVectorDrawable配合rememberAnimatedVectorDrawable实现矢量图过渡。
内容的提问来源于stack exchange,提问作者Josef M
相关产品推荐
相关产品推荐

