能否在Jetpack Compose中实现三态模态底部表单?
自定义Compose三状态模态底部表单实现方案
直接基于Compose的swipeable modifier实现三状态(Peek/半展开/全展开)底部表单,无需嵌套XML,同时支持底层地图交互:
核心思路
通过定义三个滑动锚点对应不同状态,结合swipeable处理手势切换,仅在表单可见区域响应滑动,确保底层地图可正常交互。
1. 定义状态枚举
enum class BottomSheetState { Closed, Peek, HalfExpanded, Expanded }
2. 计算各状态偏移量
根据屏幕尺寸定义不同状态的位置:
val screenHeight = LocalConfiguration.current.screenHeightDp.dp val statusBarHeight = WindowInsets.statusBars.getTop(LocalDensity.current).dp // 自定义各状态高度 val peekHeight = 60.dp val halfExpandedHeight = screenHeight / 2 val expandedHeight = screenHeight - statusBarHeight // 锚点映射:y偏移量 -> 对应状态 val anchors = mapOf( screenHeight to BottomSheetState.Closed, // 完全隐藏在屏幕下方 0.dp to BottomSheetState.Peek, // 仅显示peek高度 -(halfExpandedHeight - peekHeight) to BottomSheetState.HalfExpanded, // 半展开 -(expandedHeight - peekHeight) to BottomSheetState.Expanded // 全展开(顶部贴合状态栏) )
3. 构建可滑动表单
使用rememberSwipeableState管理状态,结合offset实现位置动画:
val sheetState = rememberSwipeableState(initialValue = BottomSheetState.HalfExpanded) val isSheetVisible = sheetState.currentValue != BottomSheetState.Closed AnimatedVisibility(visible = isSheetVisible) { Box( modifier = Modifier .fillMaxWidth() .offset(y = sheetState.offset.value) ) { Box( modifier = Modifier .fillMaxWidth() .height(expandedHeight) .background(MaterialTheme.colorScheme.surface) .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) .swipeable( state = sheetState, anchors = anchors, orientation = Orientation.Vertical, // 禁用阻力,让滑动切换更顺滑 resistance = null ) ) { Column(modifier = Modifier.fillMaxSize()) { // 顶部拖动指示器 Box( modifier = Modifier .align(Alignment.CenterHorizontally) .padding(16.dp) .size(40.dp, 4.dp) .background( MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f), RoundedCornerShape(2.dp) ) ) // 表单内容区域(示例用LazyColumn) val scrollState = rememberLazyListState() val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // 解决列表滚动与表单滑动的冲突:列表顶置时,滑动触发表单状态切换 val delta = available.y return if (delta > 0 && scrollState.firstVisibleItemIndex == 0 && scrollState.firstVisibleItemScrollOffset == 0) { Offset(0f, sheetState.performDrag(delta)) } else { Offset.Zero } } } } LazyColumn( modifier = Modifier .weight(1f) .nestedScroll(nestedScrollConnection), state = scrollState ) { items(20) { index -> Text( "表单条目 $index", modifier = Modifier .fillMaxWidth() .padding(16.dp) ) } } } } } }
关键细节说明
- 底层地图交互:仅表单可见区域(Peek状态下的60dp高度、半展开/全展开的全部区域)会响应滑动手势,其他区域的点击/触摸会直接透传到底层地图组件。
- 状态切换逻辑:
- 初始为半展开状态,上滑触发全展开,下滑触发Peek状态
- Peek状态下继续下滑,表单完全关闭
- 全展开状态下滑,返回半展开状态
- 自定义调整:可通过修改
peekHeight、halfExpandedHeight参数,灵活调整各状态的显示高度。
内容的提问来源于stack exchange,提问作者Scoobidy-Bop
相关产品推荐
相关产品推荐

