解决偶现LazyColumn/LazyVerticalGrid不在顶部,向下滑动底部弹框就消失的情况
1. 背景说明
在使用Jetpack Compose开发Android应用时,我们经常会遇到底部弹框(BottomSheet)中包含列表的场景。最近在项目中使用LazyColumn/LazyVerticalGrid实现网格列表时,发现了一个偶现的问题:
- 底部弹框打开后,LazyGrid列表有时不在顶部位置
- 向下滑动列表时,底部弹框会意外消失
- 这个问题在使用
item(span = { GridItemSpan(4) })添加占满整行的头部或底部元素时更容易出现
问题代码示例
kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProblematicBottomSheet() {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val gridState = rememberLazyGridState()
var isOpen by remember { mutableStateOf(true) }
if (isOpen) {
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = { isOpen = false },
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
dragHandle = null
) {
Column(modifier = Modifier.heightIn(max = 600.dp)) {
// 标题栏
Box(
modifier = Modifier.fillMaxWidth().height(60.dp),
contentAlignment = Alignment.Center
) {
Text(text = "有问题的BottomSheet", fontSize = 18.sp, fontWeight = FontWeight.Bold)
}
// 直接使用LazyVerticalGrid,未处理滑动冲突
LazyVerticalGrid(
columns = GridCells.Fixed(4),
state = gridState,
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// 占满整行的头部元素,这是问题的关键触发点
item(span = { GridItemSpan(4) }) {
Text("Header")
}
// 列表内容
items(40) {
Box(
modifier = Modifier.size(80.dp)
.background(Color.LightGray, RoundedCornerShape(8.dp)),
contentAlignment = Alignment.Center
) {
Text(text = "Item $it", color = Color.Black)
}
}
// 占满整行的底部元素
item(span = { GridItemSpan(4) }) {
Text("Footer")
}
}
}
}
}
}
2. 问题分析与解决方案
问题原因
经过分析,这个问题的根本原因是Jetpack Compose Material3库在处理底部弹框(BottomSheet)与网格列表(LazyVerticalGrid)的交互时存在的手势处理问题:
ModalBottomSheet支持手势滑动关闭功能LazyVerticalGrid在使用GridItemSpan创建占满整行的元素时,可能导致列表初始位置计算不准确- 当列表不在顶部时,向下滑动的手势可能被错误地识别为关闭底部弹框的手势
解决方案
解决这个问题有两个关键部分:
- 核心修复 :升级到Compose Material3 1.5.0-alpha11或更高版本,这个版本是修复了底部弹框与滚动列表的手势冲突问题了的,compose-bom版本我用的是
2026.01.00。 - 体验优化 :使用
CompositionLocalProvider(LocalOverscrollFactory provides null)禁用过度滚动效果,让滑动到顶部/底部时不再出现"弹一下"的效果,使整体滑动体验更自然
版本依赖说明
确保在build.gradle.kts中使用正确的依赖版本:
kotlin
implementation("androidx.compose.material3:material3:1.5.0-alpha11")
implementation("androidx.compose:compose-bom:2026.01.00")
修复代码示例
kotlin
@Composable
fun Demo1Page(innerPadding: PaddingValues) {
Box(modifier = Modifier.padding(innerPadding)) {
var isOpenDialog by remember { mutableStateOf(false) }
Button(onClick = {
isOpenDialog = !isOpenDialog
}) {
Text("show")
}
ProblematicBottomSheet(isOpenDialog, {
isOpenDialog = false
})
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ProblematicBottomSheet(openDialog: Boolean, onDismissRequest: () -> Unit) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val gridState = rememberLazyGridState()
if (openDialog) {
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismissRequest,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
dragHandle = null
) {
CompositionLocalProvider(
LocalOverscrollFactory provides null // 关键:提供 null 来禁用效果
) {
Column(
modifier = Modifier.heightIn(max = 600.dp)
) {
// 标题栏
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "解决问题1的BottomSheet",
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
}
LazyVerticalGrid(
columns = GridCells.Fixed(4),
state = gridState,
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
item(span = { GridItemSpan(4) }) {
Text("Header")
}
// 列表内容
items(40) {
Box(
modifier = Modifier
.size(80.dp)
.background(Color.LightGray, RoundedCornerShape(8.dp)),
contentAlignment = Alignment.Center
) {
Text(text = "Item $it", color = Color.Black)
}
}
item(span = { GridItemSpan(4) }) {
Text("Footer")
}
}
}
}
}
}
}
核心修复点
kotlin
CompositionLocalProvider(
LocalOverscrollFactory provides null // 关键:提供null来禁用过度滚动效果
) {
// 包含LazyVerticalGrid的内容
}
3. 小结
通过结合版本升级和体验优化,我们成功解决了底部弹框中LazyGrid的问题:
- 核心修复:升级到Compose Material3 1.5.0-alpha11版本,从根本上解决了偶现LazyColumn/LazyVerticalGrid不在顶部以及向下滑动底部弹框消失的问题
- 体验优化:禁用过度滚动效果,让滑动到顶部/底部时不再出现"弹一下"的效果,使整体滑动体验更自然
虽然解决了列表滑动导致BottomSheet异常消失的问题,但是产品要求我们实现仿IOS的BottomSheet效果 : 滑动到顶部,再次滑动才关闭。那需要怎么做呢 ? 具体详见我的下篇文章 : Android Compose : 仿IOS风格BottomSheet关闭效果:滑动到顶部,再次滑动才关闭