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

如何在Jetpack Compose中制作如图所示的自定义底部导航栏形状?

在Jetpack Compose中实现自定义形状的底部导航栏

要实现自定义形状的底部导航栏,核心是通过Shape接口创建自定义轮廓,再结合BottomNavigation和悬浮按钮完成布局。以下是完整实现步骤:

1. 创建自定义形状类

根据设计需求定义底部导航栏的轮廓,这里以中间带悬浮按钮凸起的常见样式为例:

import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.density

class BottomNavShape(
    private val cornerRadius: Dp,
    private val fabOffset: Dp,
    private val fabSize: Dp
) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val cornerRadiusPx = with(density) { cornerRadius.toPx() }
        val fabOffsetPx = with(density) { fabOffset.toPx() }
        val fabSizePx = with(density) { fabSize.toPx() }

        val path = Path().apply {
            // 左上角圆角
            moveTo(cornerRadiusPx, 0f)
            arcTo(
                Rect(0f, 0f, cornerRadiusPx * 2, cornerRadiusPx * 2),
                180f,
                90f,
                false
            )
            // 左边缘到中间凸起左侧
            lineTo((size.width / 2) - fabOffsetPx - fabSizePx / 2, 0f)
            // 绘制中间凸起的左半圆弧
            arcTo(
                Rect(
                    (size.width / 2) - fabOffsetPx - fabSizePx / 2,
                    -fabSizePx / 2,
                    (size.width / 2) - fabOffsetPx + fabSizePx / 2,
                    fabSizePx / 2
                ),
                180f,
                -180f,
                false
            )
            // 中间凸起右侧到右边缘
            lineTo(size.width - cornerRadiusPx, 0f)
            // 右上角圆角
            arcTo(
                Rect(
                    size.width - cornerRadiusPx * 2,
                    0f,
                    size.width,
                    cornerRadiusPx * 2
                ),
                270f,
                90f,
                false
            )
            // 右边缘到底部
            lineTo(size.width, size.height)
            // 右下角圆角
            arcTo(
                Rect(
                    size.width - cornerRadiusPx * 2,
                    size.height - cornerRadiusPx * 2,
                    size.width,
                    size.height
                ),
                0f,
                90f,
                false
            )
            // 底部到左下角
            lineTo(cornerRadiusPx, size.height)
            // 左下角圆角
            arcTo(
                Rect(0f, size.height - cornerRadiusPx * 2, cornerRadiusPx * 2, size.height),
                90f,
                90f,
                false
            )
            close()
        }
        return Outline.Generic(path)
    }
}

2. 实现自定义底部导航栏组件

将自定义形状应用到BottomNavigation,并添加中间悬浮按钮:

import androidx.compose.foundation.layout.*
import androidx.compose.floatingactionbutton.FloatingActionButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp

// 导航项数据类
data class NavItem(
    val title: String,
    val icon: ImageVector,
    val route: String
)

@Composable
fun CustomBottomNavigation(
    navItems: List<NavItem>,
    currentRoute: String,
    onNavItemClicked: (NavItem) -> Unit,
    modifier: Modifier = Modifier
) {
    val fabSize = 64.dp
    val fabOffset = 16.dp
    val cornerRadius = 16.dp

    BottomNavigation(
        modifier = modifier.clip(BottomNavShape(cornerRadius, fabOffset, fabSize)),
        backgroundColor = MaterialTheme.colorScheme.surface,
        contentColor = MaterialTheme.colorScheme.onSurface,
        elevation = 8.dp
    ) {
        navItems.forEachIndexed { index, item ->
            val isSelected = currentRoute == item.route
            // 为中间悬浮按钮预留位置
            if (index == navItems.size / 2) {
                Spacer(
                    modifier = Modifier
                        .size(fabSize)
                        .offset(y = -fabOffset)
                )
            } else {
                BottomNavigationItem(
                    selected = isSelected,
                    onClick = { onNavItemClicked(item) },
                    icon = {
                        Icon(
                            imageVector = item.icon,
                            contentDescription = item.title,
                            modifier = Modifier.size(24.dp)
                        )
                    },
                    label = { Text(item.title) },
                    selectedContentColor = MaterialTheme.colorScheme.primary,
                    unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant
                )
            }
        }
        // 中间悬浮按钮
        Box(
            modifier = Modifier
                .fillMaxSize()
                .wrapContentSize(Alignment.Center)
                .offset(y = -fabOffset)
        ) {
            FloatingActionButton(
                onClick = {
                    navItems.getOrNull(navItems.size / 2)?.let(onNavItemClicked)
                },
                containerColor = MaterialTheme.colorScheme.primary,
                contentColor = MaterialTheme.colorScheme.onPrimary,
                modifier = Modifier.size(fabSize)
            ) {
                navItems.getOrNull(navItems.size / 2)?.let {
                    Icon(
                        imageVector = it.icon,
                        contentDescription = it.title,
                        modifier = Modifier.size(28.dp)
                    )
                }
            }
        }
    }
}

3. 在页面中使用自定义底部导航栏

结合Scaffold和NavHost完成整体页面布局:

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    val navItems = listOf(
        NavItem("首页", Icons.Default.Home, "home"),
        NavItem("探索", Icons.Default.Explore, "explore"),
        NavItem("发布", Icons.Default.AddCircle, "post"),
        NavItem("消息", Icons.Default.Message, "message"),
        NavItem("我的", Icons.Default.Person, "profile")
    )

    Scaffold(
        bottomBar = {
            CustomBottomNavigation(
                navItems = navItems,
                currentRoute = navController.currentBackStackEntry?.destination?.route ?: "home",
                onNavItemClicked = { navController.navigate(it.route) }
            )
        }
    ) { paddingValues ->
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.padding(paddingValues)
        ) {
            composable("home") { HomeScreen() }
            composable("explore") { ExploreScreen() }
            composable("post") { PostScreen() }
            composable("message") { MessageScreen() }
            composable("profile") { ProfileScreen() }
        }
    }
}

// 示例页面组件(按需替换)
@Composable
fun HomeScreen() { Text("首页") }
@Composable
fun ExploreScreen() { Text("探索") }
@Composable
fun PostScreen() { Text("发布") }
@Composable
fun MessageScreen() { Text("消息") }
@Composable
fun ProfileScreen() { Text("我的") }

自定义调整说明

  • 如果你的设计图不是中间凸起样式,只需修改BottomNavShape中的Path绘制逻辑,比如调整圆角位置、添加切角等。
  • 可通过修改fabSize、fabOffset、cornerRadius参数来匹配设计尺寸。
  • 悬浮按钮和导航项的颜色、样式可通过MaterialTheme.colorScheme自定义。

内容的提问来源于stack exchange,提问作者TriangleN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:09:21