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

如何用Jetpack Compose实现类似iOS CircleBar的Android BottomNavigationMenu?

实现带动态FloatingActionButton的BottomNavigationMenu及类似CircleBar的导航栏(Jetpack Compose)

一、带动态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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 12:47:43