如何在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
相关产品推荐
相关产品推荐

