Jetpack Compose实现带固定激活项的BottomSheet列表
解决方案:动态适配高度的BottomSheetScaffold实现
核心思路是通过状态变量动态绑定sheetPeekHeight,结合全局位置监听获取固定内容的实际高度,同时计算底部弹窗的展开高度以适配列表内容,具体实现如下:
关键步骤
- 用
mutableStateOf保存动态的peek高度,状态变化会自动触发BottomSheetScaffold重组 - 通过
onGloballyPositioned监听固定内容的布局位置,获取其实际高度并更新状态 - 计算弹窗展开高度:若列表总高度小于屏幕剩余高度,展开高度为列表总高;否则设为屏幕可用高度,列表内部滚动
- 基于内容自适应处理列表项高度,适配系统字体缩放
完整代码示例
@Composable fun AdaptiveBottomSheet() { // 保存动态的peek高度,初始值设为0,后续会更新 val sheetPeekHeight = remember { mutableStateOf(0.dp) } // 模拟列表数据 val listItems = remember { List(20) { "Item $it" } } // 激活项状态(示例:默认激活第0项) val activeItemIndex = remember { mutableStateOf(0) } // 获取屏幕可用高度(减去状态栏、导航栏等系统UI高度) val windowSize = rememberWindowSize() val availableScreenHeight = windowSize.height - LocalWindowInsets.current.systemBars.bottom.dp BottomSheetScaffold( sheetPeekHeight = sheetPeekHeight.value, // 计算展开高度:取列表总高度和屏幕可用高度的较小值 sheetMaxHeight = with(LocalDensity.current) { val itemHeight = 48.sp.toDp() // 列表项高度随字体缩放 val totalListHeight = itemHeight * listItems.size + sheetPeekHeight.value min(totalListHeight, availableScreenHeight) }, sheetContent = { Column { // 固定显示的内容(alwaysVisibleSheetContent) Box( modifier = Modifier .fillMaxWidth() .background(Color.LightGray) .onGloballyPositioned { coordinates -> // 获取固定内容的实际高度,转换为dp后更新状态 val height = with(LocalDensity.current) { coordinates.size.height.toDp() } sheetPeekHeight.value = height } ) { if (activeItemIndex.value != -1) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Text( text = "激活项: ${listItems[activeItemIndex.value]}", modifier = Modifier.weight(1f).padding(16.dp) ) CircularProgressIndicator( modifier = Modifier.size(24.dp).padding(end = 16.dp), strokeWidth = 2.dp ) } } else { Text( text = "无激活项", modifier = Modifier.fillMaxWidth().padding(16.dp), textAlign = TextAlign.Center ) } } // 列表内容 LazyColumn( modifier = Modifier.fillMaxWidth() ) { items(listItems) { item -> val isActive = listItems.indexOf(item) == activeItemIndex.value Text( text = item, modifier = Modifier .fillMaxWidth() .height(48.sp.toDp()) // 高度随字体缩放 .padding(horizontal = 16.dp) .align(Alignment.CenterVertically) .clickable { activeItemIndex.value = listItems.indexOf(item) }, verticalAlignment = Alignment.CenterVertically ) } } } }, content = { // 主内容区域 Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { activeItemIndex.value = -1 }) { Text("清除激活项") } } } ) } // 辅助函数:获取窗口尺寸 @Composable fun rememberWindowSize(): WindowSize { val configuration = LocalConfiguration.current return remember(configuration) { WindowSize( width = configuration.screenWidthDp.dp, height = configuration.screenHeightDp.dp ) } } data class WindowSize(val width: Dp, val height: Dp)
关键点解释
- 动态peekHeight更新:通过
onGloballyPositioned监听固定内容的布局完成事件,获取其真实高度后更新sheetPeekHeight的状态变量,触发BottomSheetScaffold重组,从而自适应固定内容的高度(包括系统字体缩放后的变化)。 - 自适应展开高度:计算列表总高度(列表项高度×数量 + 固定内容高度),与屏幕可用高度取最小值,确保弹窗不会超出屏幕,超出时列表内部可滚动。
- 列表项高度适配:使用
48.sp.toDp()设置列表项高度,sp单位会随系统字体缩放自动调整,保证高度适配字体设置。 - 激活项状态处理:通过
activeItemIndex状态控制固定内容的显示(文本/带进度指示器的激活项),点击列表项可切换激活状态。
内容的提问来源于stack exchange,提问作者BobDidley
相关产品推荐
相关产品推荐

