这一节主要了解一下Compose中的ModalBottomSheetLayout,在Jetpack Compose开发中,ModalBottomSheetLayout是Material Design组件库中用于实现模态底部面板的核心组件,其核心作用是通过声明式API管理底部面板的显示、隐藏及交互逻辑。
API
ModalBottomSheetLayout:作为容器使用,管理底部sheet的状态和行为
sheetState:控制底部sheet的状态(展开、折叠、隐藏)
sheetShape:定义底部sheet的形状(通常使用圆角)
sheetElevation:控制底部sheet的阴影效果
sheetContent:底部sheet的内容区域
scrimColor:背景遮罩层的颜色
场景:
1 表单输入/选择,用户需要从多个选项中选择或填写简单表单(如添加备注)。
2 详情展示,点击列表项后,底部弹窗展示详细信息
3 操作菜单,提供一组操作按钮(如分享、删除、编辑)。
栗子:
app下gradle添加依赖
implementation "androidx.compose.material:material:1.4.3"
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dpimport kotlinx.coroutines.launch
import androidx.compose.material.ModalBottomSheetLayout@Composable
fun FormModalBottomSheet() {val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)val scope = rememberCoroutineScope()var name by remember { mutableStateOf("") }var email by remember { mutableStateOf("") }var submitted by remember { mutableStateOf(false) }ModalBottomSheetLayout(sheetState = sheetState,sheetContent = {Column(modifier = Modifier.fillMaxWidth().padding(16.dp).padding(bottom = 40.dp)) {Text(text = "请填写信息",style = MaterialTheme.typography.h5,modifier = Modifier.padding(bottom = 16.dp))OutlinedTextField(value = name,onValueChange = { name = it },label = { Text("姓名") },modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp))OutlinedTextField(value = email,onValueChange = { email = it },label = { Text("邮箱") },modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp))Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.End) {Button(onClick = { scope.launch { sheetState.hide() } },modifier = Modifier.padding(end = 8.dp)) {Text("取消")}Button(onClick = {submitted = truescope.launch { sheetState.hide() }}) {Text("提交")}}}}) {Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Button(onClick = { scope.launch { sheetState.show() } }) {Text("打开表单")}if (submitted && name.isNotEmpty() && email.isNotEmpty()) {Spacer(modifier = Modifier.height(16.dp))Card(elevation = 4.dp,modifier = Modifier.padding(16.dp)) {Column(modifier = Modifier.padding(16.dp)) {Text("提交成功", style = MaterialTheme.typography.h6)Spacer(modifier = Modifier.height(8.dp))Text("姓名: $name")Text("邮箱: $email")}}}}}
}
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CategoryModalBottomSheet() {val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)val scope = rememberCoroutineScope()val categories = listOf("水果", "蔬菜", "零食", "饮料")// 商品数据val products = mapOf("水果" to listOf("苹果", "香蕉", "橙子", "草莓", "葡萄", "西瓜", "芒果"),"蔬菜" to listOf("胡萝卜", "西红柿", "黄瓜", "土豆", "西兰花", "洋葱", "生菜"),"零食" to listOf("薯片", "巧克力", "饼干", "坚果", "糖果", "牛肉干", "果冻"),"饮料" to listOf("可乐", "雪碧", "果汁", "茶饮料", "咖啡", "牛奶", "矿泉水"))var selectedCategory by remember { mutableStateOf(categories.first()) }var selectedProduct by remember { mutableStateOf<String?>(null) }ModalBottomSheetLayout(sheetState = sheetState,sheetShape = MaterialTheme.shapes.large,sheetContent = {Column(modifier = Modifier.fillMaxWidth().padding(bottom = 30.dp)) {Box(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).wrapContentSize(Alignment.Center)) {Box(modifier = Modifier.width(32.dp).height(4.dp).clip(MaterialTheme.shapes.small).background(Color.Gray.copy(alpha = 0.3f)))}ScrollableTabRow(selectedTabIndex = categories.indexOf(selectedCategory),backgroundColor = Color.Transparent,edgePadding = 0.dp) {categories.forEachIndexed { index, category ->Tab(selected = selectedCategory == category,onClick = { selectedCategory = category },text = {Text(text = category,fontWeight = if (selectedCategory == category) FontWeight.Bold else FontWeight.Normal)},modifier = Modifier.padding(horizontal = 8.dp))}}// 商品列表LazyColumn(modifier = Modifier.fillMaxWidth().height(300.dp).padding(horizontal = 16.dp, vertical = 8.dp)) {items(products[selectedCategory] ?: emptyList()) { product ->ProductItem(product = product,isSelected = selectedProduct == product,onClick = {selectedProduct = productscope.launch { sheetState.hide() }})}}}}){Scaffold(topBar = {TopAppBar(title = { Text("商品选择器") },backgroundColor = MaterialTheme.colors.primary)},content = { padding ->Box(modifier = Modifier.fillMaxSize().padding(padding),contentAlignment = Alignment.Center) {Column(horizontalAlignment = Alignment.CenterHorizontally) {Button(onClick = { scope.launch { sheetState.show() } },modifier = Modifier.padding(16.dp)) {Text("选择商品")}if (selectedProduct != null) {Card(modifier = Modifier.padding(16.dp).fillMaxWidth().wrapContentHeight(),elevation = 4.dp) {Column(modifier = Modifier.padding(16.dp),horizontalAlignment = Alignment.CenterHorizontally) {Text(text = "已选择商品",fontSize = 16.sp,fontWeight = FontWeight.Bold,modifier = Modifier.padding(bottom = 8.dp))Text(text = selectedProduct!!,fontSize = 24.sp,fontWeight = FontWeight.Bold,color = MaterialTheme.colors.primary)Spacer(modifier = Modifier.height(16.dp))Button(onClick = { selectedProduct = null },colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray)) {Text("清除选择")}}}}}}})}
}@Composablefun ProductItem(product: String,isSelected: Boolean,onClick: () -> Unit) {Box(modifier = Modifier.fillMaxWidth().height(56.dp).clickable(onClick = onClick).padding(vertical = 8.dp),contentAlignment = Alignment.CenterStart) {Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxSize()) {Text(text = product,fontSize = 16.sp,modifier = Modifier.weight(1f))if (isSelected) {Icon(painter = painterResource(id = android.R.drawable.checkbox_on_background),contentDescription = "已选择",tint = MaterialTheme.colors.primary)}}Divider(color = Color.LightGray.copy(alpha = 0.5f),modifier = Modifier.fillMaxWidth().height(0.5.dp).align(Alignment.BottomCenter))}}
注意:
1 状态管理,使用rememberModalBottomSheetState()控制弹窗的展开/折叠状态。
2 避免弹窗内容过多,必要时分页或滚动。
3 性能优化,复杂内容使用LazyColumn或LazyRow 避免卡顿。避免在弹窗内加载大量数据,可预加载或分页。