Jetpack Compose学习(16)——ModalBottomSheet(底部弹窗)

原文地址: 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" }

基本使用

效果:

代码:

kotlin 复制代码
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")
                }

            }
        }
    }
}

这里需要注意的是:

  1. 展示弹窗,实际和Dialog类似,也是控制一个Boolean数值变化从而弹出
  2. 关闭弹窗的代码得用上面的写法,否则就是没有下滑效果!!(之前就是在这踩坑了,虽然没人关注这点哈哈)

PS:后续代码为了保证重点和观感,会有所精简

ModalBottomSheet默认是有半屏和全屏模式的,但上面例子由于我们的内容元素不多,高度不是大,我们改造下,加个LazyColumn再看下效果

效果:

代码:

kotlin 复制代码
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属性来实现,如下代码

kotlin 复制代码
ModalBottomSheet(
	onDismissRequest = { openBottomSheet = false },
	sheetState = bottomSheetState,
	dragHandle = {} //设置为空
) {
	
}

效果如下:

顶头区自定义按钮

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

效果:

代码:

kotlin 复制代码
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
						}
					}
			})
		}
	}
) 

禁止展示半屏

如果不想要展示半屏的效果,可以通过下面这样设置

kotlin 复制代码
val bottomSheetState = rememberModalBottomSheetState(true)

意思是,如果你的内容满全屏了,则直接跳过半屏模式,直接展示全屏,效果如下图展示

那我内容元素总高度不满全屏高度,又该如何呢?

那更简单,直接将你的内容容器用Modifier设置为全屏即可

kotlin 复制代码
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")
		}
	}
}

参考