Android Compose LazyGridState异常:拖动时存在超大待处理滚动值
问题:Compose LazyVerticalGrid在Android 8-10设备随机崩溃
使用Compose的LazyVerticalGrid时,仅在Android 8-10设备上出现随机崩溃,错误提示为IllegalStateException: entered drag with non-zero pending scroll: 1.116108E23,该异常仅通过生产崩溃日志发现,无法稳定复现。
完整错误日志
java.lang.IllegalStateException: entered drag with non-zero pending scroll: 1.116108E23 androidx.compose.foundation.lazy.LazyListState.onScroll$foundation_release(LazyListState.java:177) androidx.compose.foundation.lazy.LazyListState$scrollableState$1.invoke(LazyListState.java:177) androidx.compose.foundation.lazy.LazyListState$scrollableState$1.invoke(LazyListState.java:177) androidx.compose.foundation.gestures.DefaultScrollableState$scrollScope$1.scrollBy(DefaultScrollableState.java:16) androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke-MK-Hz9U(ScrollingLogic.java:84) androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke(ScrollingLogic.java:84) androidx.compose.foundation.AndroidEdgeEffectOverscrollEffect.applyToScroll-Rhakbz0(AndroidEdgeEffectOverscrollEffect.java:388) androidx.compose.foundation.gestures.ScrollingLogic.dispatchScroll-3eAAhYA(ScrollingLogic.java:45) androidx.compose.foundation.gestures.ScrollDraggableState.dragBy(ScrollDraggableState.java:9) androidx.compose.foundation.gestures.DraggableNode$abstractDragScope$1.dragBy-k-4lQ0M(DraggableNode.java:19) androidx.compose.foundation.gestures.DraggableNode.draggingBy(DraggableNode.java:2)
相关代码实现
LazyVerticalGrid代码
val filtersState by gameLibraryVM.filtersState.collectAsState() val gridState = rememberLazyGridState() LazyVerticalGrid( userScrollEnabled = filtersState != null, state = gridState, modifier = Modifier .fillMaxSize() .background(colorResource(R.color.yellow_FFFBE9)), columns = GridCells.Fixed(if (displayMode.value == DisplayMode.LIST) 1 else 2), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(12.dp), contentPadding = if (gameListPagingItems.loadState.append is LoadState.Loading) { PaddingValues().addBottom(28.dp) } else { PaddingValues().addBottom(46.dp) }, ) { // Content items... }
触发滚动的组件代码
// Scroll-to-top button ScrollToTopButton( modifier = Modifier .align(Alignment.BottomEnd), isVisible = gridState.firstVisibleItemIndex > 0, onClick = { coroutineScope.launch { gridState.animateScrollToItem(0) } }, ) // Filter toolbar reset action FilterToolBar( tags = selectedFilterState?.values.orEmpty().toList(), onReset = { coroutineScope.launch { gridState.animateScrollToItem(0) } gameLibraryVM.resetSelectedFilterState() } )
已尝试的解决措施
- 排查滚动操作中的竞态条件
- 检查嵌套滚动冲突
- 排查同时存在滚动和拖动操作的场景
- 在滚动操作前添加延迟
- 尝试捕获处理异常(但异常发生在框架层)
疑问
- 导致待处理滚动值(1.116108E23)异常超大的原因是什么?
- 如何正确处理并发滚动操作以避免该错误?
- 若底层问题未解决,是否有办法安全限制滚动值以防止崩溃?
环境信息
- Android Compose版本:1.4.0
- Kotlin版本:1.8.0
- 测试设备:Google Pixel 9 Pro XL(Android 14)、Google Pixel 5(Android 14)、Huawei Mate 20(Android 9)
解答
1. 异常超大滚动值的原因
这是Compose 1.4.x版本在Android 8-10(API 26-29)设备上的框架底层bug。问题出在AndroidEdgeEffectOverscrollEffect的滚动计算逻辑中,旧Android版本的EdgeEffect实现与Compose的滚动状态处理不兼容,导致数值计算时出现浮点溢出,生成了超大的pending scroll值。而LazyGridState的滚动校验逻辑要求开始拖动时pending scroll必须为0,触发了IllegalStateException。
2. 正确处理并发滚动操作的方案
- 统一滚动任务调度:用
Mutex确保同一时间只有一个滚动动画执行,避免并发调用animateScrollToItem:val scrollMutex = remember { Mutex() } // 调用滚动时 coroutineScope.launch { scrollMutex.withLock { gridState.animateScrollToItem(0) } } - 取消旧滚动任务:用
Job持有滚动协程,每次触发新滚动前先取消旧任务:var scrollJob by remember { mutableStateOf<Job?>(null) } // 滚动按钮点击逻辑 onClick = { scrollJob?.cancel() scrollJob = coroutineScope.launch { gridState.animateScrollToItem(0) } } - 同步数据刷新与滚动:在过滤重置时,等待滚动完成后再重置数据,避免数据变化干扰滚动状态:
onReset = { coroutineScope.launch { scrollMutex.withLock { gridState.animateScrollToItem(0) gridState.awaitScrollEnd() // 等待滚动完成 gameLibraryVM.resetSelectedFilterState() } } } - 升级Compose版本:优先升级到Compose 1.5.0及以上版本,官方在后续版本中修复了EdgeEffect和滚动状态校验的相关bug,这是最彻底的解决方案。
3. 临时限制滚动值的应急方案
- 自定义EdgeEffect禁用过度滚动:通过自定义FlingBehavior禁用EdgeEffect,避免异常数值生成:
val noEdgeEffectFling = ScrollDefaults.flingBehavior( edgeEffectFactory = object : EdgeEffectFactory { override fun createEdgeEffect( direction: Direction, isReverseDirection: Boolean ): EdgeEffect = object : EdgeEffect(LocalContext.current) { override fun onPull(delta: Float) = false override fun onPull(delta: Float, displacement: Float) = false override fun onRelease() = false override fun onAbsorb(velocity: Int) {} override fun draw(canvas: Canvas?): Boolean = false override fun isFinished(): Boolean = true } } ) // 应用到LazyVerticalGrid LazyVerticalGrid( flingBehavior = noEdgeEffectFling, // 其他参数... ) - 包装LazyGridState拦截异常滚动:自定义包装类拦截
scrollBy等方法,限制滚动值范围:class SafeLazyGridState(delegate: LazyGridState) : LazyGridState by delegate { override suspend fun scrollBy(pixels: Float) { // 限制滚动值在合理范围内,比如±屏幕高度的2倍 val maxScroll = LocalConfiguration.current.screenHeightDp.dp.toPx() * 2 val safePixels = pixels.coerceIn(-maxScroll, maxScroll) delegate.scrollBy(safePixels) } } // 使用时 val gridState = rememberLazyGridState() val safeGridState = remember { SafeLazyGridState(gridState) }
内容的提问来源于stack exchange,提问作者TaroCheng
相关产品推荐
相关产品推荐

