如何在Android Material 3 Jetpack Compose中实现BottomSheet
Material 3 版本Jetpack Compose 实现BottomSheet正确方案
Material 3对BottomSheet能力做了场景拆分,早期1.2.0以下版本确实没有对应M2的BottomSheetScaffold,目前官方已经按使用场景提供了对应实现,不需要额外自定义复杂逻辑:
- 前置准备:将Material3依赖升级到
androidx.compose.material3:material3:1.2.0及以上正式版本,低版本缺失很多BottomSheet相关的官方适配,自定义实现很容易出现手势冲突、适配问题。
场景1:模态底部弹层(弹出时带半透明遮罩,阻断背后页面交互)
这类是最常用的场景,比如弹出操作菜单、选择面板、信息展示卡,直接使用官方提供的ModalBottomSheet组件即可。
基础实现代码:
// 控制弹层显示状态 val showSheet = rememberSaveable { mutableStateOf(false) } // 弹层拖拽状态,支持自定义初始展开状态、动画规格 val sheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden ) val coroutineScope = rememberCoroutineScope() // 页面主框架 Scaffold { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .fillMaxSize() .padding(16.dp) ) { Button(onClick = { showSheet.value = true }) { Text("打开底部弹层") } } } // 底部弹层 if (showSheet.value) { ModalBottomSheet( onDismissRequest = { showSheet.value = false }, sheetState = sheetState, // 可选配置:弹层形状、背景色、窗口Insets等 shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) ) { // 弹层内部内容,支持任意Compose组件 Text( text = "弹层标题", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(16.dp)) repeat(5) { index -> ListItem( headlineContent = { Text("选项 ${index + 1}") }, modifier = Modifier.clickable { coroutineScope.launch { sheetState.hide() }.invokeOnCompletion { if (!sheetState.isVisible) showSheet.value = false } } ) } // 适配系统导航栏,避免内容被遮挡 Spacer(modifier = Modifier.navigationBarsPadding()) } }
场景2:常驻底部面板(和页面内容同层级,无遮罩,支持拖拽半展开/全展开/收起)
这类就是M2版本BottomSheetScaffold覆盖的场景,比如地图页的底部信息栏、播放器的半透明控制面板,M3 1.2.0+版本已经重新提供了同名的BottomSheetScaffold组件,API和M2版本基本对齐,迁移成本极低。
基础实现代码:
val scaffoldState = rememberBottomSheetScaffoldState() val coroutineScope = rememberCoroutineScope() BottomSheetScaffold( scaffoldState = scaffoldState, sheetPeekHeight = 120.dp, // 收起状态下露出的面板高度 sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), sheetContent = { // 底部面板内容 Column(Modifier.fillMaxWidth()) { Text( text = "常驻底部面板", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(16.dp) ) // 面板内可直接放滚动组件,默认支持嵌套滚动,拖拽手势自动衔接 LazyColumn { items(20) { index -> ListItem(headlineContent = { Text("列表项 ${index + 1}") }) } item { Spacer(modifier = Modifier.navigationBarsPadding()) } } } } ) { innerPadding -> // 页面主内容区 Box( modifier = Modifier .padding(innerPadding) .fillMaxSize() ) { Button( onClick = { coroutineScope.launch { if (scaffoldState.bottomSheetState.isVisible) { scaffoldState.bottomSheetState.hide() } else { scaffoldState.bottomSheetState.expand() } } }, modifier = Modifier.align(Alignment.Center) ) { Text("切换底部面板状态") } } }
注意事项:
- 导入组件时请确认包名为
androidx.compose.material3,不要误导入M2包下的同名组件导致样式、行为异常- 不要在低版本M3库上自己实现BottomSheet拖拽逻辑,很容易出现嵌套滚动冲突、Insets适配错误、动画卡顿等问题,升级官方稳定版依赖是最优解
- 如果需要实现底部弹层强制不允许拖拽关闭,可在
rememberModalBottomSheetState中传入confirmValueChange参数拦截状态变更即可
内容的提问来源于stack exchange,提问作者Raheem
相关产品推荐
相关产品推荐

