You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 04:52:53