You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 导致待处理滚动值(1.116108E23)异常超大的原因是什么?
  2. 如何正确处理并发滚动操作以避免该错误?
  3. 若底层问题未解决,是否有办法安全限制滚动值以防止崩溃?

环境信息

  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:15:17