Android Compose : 解决列表滑动导致BottomSheet异常消失的问题

解决偶现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)的交互时存在的手势处理问题:

  1. ModalBottomSheet支持手势滑动关闭功能
  2. LazyVerticalGrid在使用GridItemSpan创建占满整行的元素时,可能导致列表初始位置计算不准确
  3. 当列表不在顶部时,向下滑动的手势可能被错误地识别为关闭底部弹框的手势

解决方案

解决这个问题有两个关键部分:

  1. 核心修复 :升级到Compose Material3 1.5.0-alpha11或更高版本,这个版本是修复了底部弹框与滚动列表的手势冲突问题了的,compose-bom版本我用的是2026.01.00。
  2. 体验优化 :使用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的问题:

  1. 核心修复:升级到Compose Material3 1.5.0-alpha11版本,从根本上解决了偶现LazyColumn/LazyVerticalGrid不在顶部以及向下滑动底部弹框消失的问题
  2. 体验优化:禁用过度滚动效果,让滑动到顶部/底部时不再出现"弹一下"的效果,使整体滑动体验更自然

虽然解决了列表滑动导致BottomSheet异常消失的问题,但是产品要求我们实现仿IOS的BottomSheet效果 : 滑动到顶部,再次滑动才关闭。那需要怎么做呢 ? 具体详见我的下篇文章 : Android Compose : 仿IOS风格BottomSheet关闭效果:滑动到顶部,再次滑动才关闭

相关推荐
木头左2 小时前
【架构设计】教育类 App 服务端化改造:AI 上移、题库分层与个人题库归属
android·架构设计·系统设计·后端开发·数据库设计
墨狂之逸才3 小时前
USB 的 HOST 和 DEVICE 到底有什么区别?ADB 调试应该选哪个?
android
奶油话梅糖4 小时前
苹果的内核跑上了安卓手机:2,631 Star 的 HakiOS 让 XNU 在 Adreno GPU 上画出 118 帧
android·macos·智能手机
恋猫de小郭4 小时前
Flutter THLC 的 WebView 滚动终于从 70 FPS 提升到 120 FPS
android·前端·flutter
好的佩奇15 小时前
Dart 之扩展类型
android·flutter·dart
alexhilton17 小时前
2027 Android 开发者路线图:学习、面试与 AI
android·kotlin·android jetpack
灿飞屋19 小时前
Kotlin与Java虚拟机三:控制流
android·java·开发语言·后端·python·kotlin·安卓
疯华整猫20 小时前
我用 Trae 把 Android 内存账重算了一遍
android·性能优化·源码
TimeFine1 天前
AI 编程工作流的设计:从人工判断到 AI裁判
android·ai编程
阿杰m1 天前
Android源码第四章:Android 系统启动——App 的第一行代码从哪来?
android