原文地址: Jetpack Compose学习(16)——ModalBottomSheet(底部弹窗)-Stars-One的杂货小窝
接手新公司项目里,有代码用到了这个弹窗,由于需要重构架构和进行相关统一组件封装,顺手学习下这个组件,发现还是踩了些坑(怪我以Compose里的Dialog来用了哈哈)
介绍
这个组件是属于M3里的组件,需要引入androidx.compose.material3这个依赖
不过新版本的Android Studio创建项目都是直接通过Bom引入了,是都会带有了这个依赖了(这里就不过多提及加入依赖了)
PS:主要是现在新版本Android Studio,新创建的项目,依赖分了几个文件,贴的话会很麻烦,见谅哈哈
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
基本使用
效果:

代码:
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun ModelSheetDemoPage(modifier: Modifier = Modifier) { val context = LocalContext.current Column(modifier = Modifier.statusBarsPadding()) { var openBottomSheet by rememberSaveable { mutableStateOf(false) } val scope = rememberCoroutineScope() val bottomSheetState = rememberModalBottomSheetState() // App content Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Button( onClick = { openBottomSheet = !openBottomSheet }, modifier = Modifier.align(Alignment.CenterHorizontally) ) { Text(text = "Show Bottom Sheet") } } // Sheet content if (openBottomSheet) { ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, ) { Text("内容数据") Button( // Note: If you provide logic outside of onDismissRequest to remove the sheet, // you must additionally handle intended state cleanup, if any. onClick = { scope .launch { bottomSheetState.hide() } .invokeOnCompletion { if (!bottomSheetState.isVisible) { openBottomSheet = false } } } ) { Text("Hide Bottom Sheet") } } } } }
这里需要注意的是:
- 展示弹窗,实际和Dialog类似,也是控制一个Boolean数值变化从而弹出
- 关闭弹窗的代码得用上面的写法,否则就是没有下滑效果!!(之前就是在这踩坑了,虽然没人关注这点哈哈)
PS:后续代码为了保证重点和观感,会有所精简
ModalBottomSheet默认是有半屏和全屏模式的,但上面例子由于我们的内容元素不多,高度不是大,我们改造下,加个LazyColumn再看下效果
效果:

代码:
ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, ) { //.... LazyColumn { items(20) { Row(modifier= Modifier.fillMaxWidth().height(48.dp).padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { Text("hello ${it}") } } } }
自定义样式
去掉小横条
想要实现自定义的下拉样式,然后,不希望要这个小横条(如下图所示),要如何实现呢?

可以通过dragHandle属性来实现,如下代码
ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, dragHandle = {} //设置为空 ) { }
效果如下:

顶头区自定义按钮
这个dragHandle实际就是顶头那篇区域,如果想要自定义一个取消和确定,也可以实现,如下效果和代码
效果:

代码:
ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, dragHandle = { Row(modifier= Modifier.fillMaxWidth().padding(horizontal = 24.dp)) { Text("取消",modifier= Modifier.clickable{ scope .launch { bottomSheetState.hide() } .invokeOnCompletion { if (!bottomSheetState.isVisible) { openBottomSheet = false } } }) Spacer(modifier= Modifier.weight(1f)) Text("确定",modifier= Modifier.clickable{ scope .launch { bottomSheetState.hide() } .invokeOnCompletion { if (!bottomSheetState.isVisible) { openBottomSheet = false } } }) } } )
禁止展示半屏
如果不想要展示半屏的效果,可以通过下面这样设置
val bottomSheetState = rememberModalBottomSheetState(true)
意思是,如果你的内容满全屏了,则直接跳过半屏模式,直接展示全屏,效果如下图展示

那我内容元素总高度不满全屏高度,又该如何呢?
那更简单,直接将你的内容容器用Modifier设置为全屏即可
ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, ) { //直接填充满屏即可! Column(modifier= Modifier.fillMaxSize()) { Text("内容数据") Button( // Note: If you provide logic outside of onDismissRequest to remove the sheet, // you must additionally handle intended state cleanup, if any. onClick = { scope .launch { bottomSheetState.hide() } .invokeOnCompletion { if (!bottomSheetState.isVisible) { openBottomSheet = false } } } ) { Text("Hide Bottom Sheet") } } }