RecyclerView 计算布局或滚动时调用方法的错误分析

在使用 RecyclerView 或 ViewPager2 时,你可能会遇到这个错误:"Cannot call this method while RecyclerView is computing a layout or scrolling"。这是 Android 系统为了防止 UI 异常而设计的保护机制。

错误原因

当 RecyclerView 正在执行以下操作时,调用某些敏感方法会触发此错误:

  1. 布局计算中:RecyclerView 正在测量和布局其子项(如滚动、数据更新后)。
  2. 滚动过程中:RecyclerView 正在执行平滑滚动或快速滚动。

这些敏感方法包括:

  • scrollToPosition()/scrollToPositionWithOffset()
  • smoothScrollToPosition()
  • setAdapter()/notifyDataSetChanged()
  • 修改 LayoutParams 或直接操作子视图

解决方案

以下是几种解决此问题的方法:

1. 延迟执行敏感操作

使用 post() 方法将操作放入消息队列,等待 RecyclerView 完成当前操作后执行:

// Kotlin
recyclerView.post {
    // 执行敏感操作,如滚动或更新数据
    layoutManager.scrollToPositionWithOffset(position, offset)
}

// Java
recyclerView.post(new Runnable() {
    @Override
    public void run() {
        // 执行敏感操作
        layoutManager.scrollToPositionWithOffset(position, offset);
    }
});
2. 使用滚动状态监听器

在滚动完成后执行操作:

// Kotlin
recyclerView.registerOnScrollListener(object : RecyclerView.OnScrollListener() {
    override void onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            // 滚动已停止,可以执行敏感操作
            layoutManager.scrollToPositionWithOffset(position, offset)
        }
    }
})

// Java
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            // 滚动已停止,可以执行敏感操作
            layoutManager.scrollToPositionWithOffset(position, offset);
        }
    }
});
3. 检查 RecyclerView 状态

在执行操作前检查 RecyclerView 是否处于安全状态:

// Kotlin
fun safeScrollToPosition(position: Int, offset: Int) {
    if (recyclerView.isComputingLayout() || recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) {
        recyclerView.post {
            layoutManager.scrollToPositionWithOffset(position, offset)
        }
    } else {
        layoutManager.scrollToPositionWithOffset(position, offset)
    }
}

// Java
private void safeScrollToPosition(int position, int offset) {
    if (recyclerView.isComputingLayout() || recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                layoutManager.scrollToPositionWithOffset(position, offset);
            }
        });
    } else {
        layoutManager.scrollToPositionWithOffset(position, offset);
    }
}
4. 使用 ViewPager2 的 setCurrentItem() 替代

对于 ViewPager2,优先使用其提供的安全方法:

java

// Kotlin
viewPager2.setCurrentItem(position, smoothScroll = true)

// Java
viewPager2.setCurrentItem(position, true);

最佳实践

  1. 避免在滚动过程中更新数据:尽量在滚动停止后再进行数据更新或位置调整。
  2. 批量操作:如果需要执行多个敏感操作,考虑将它们集中在一个 post() 中执行。
  3. 减少布局计算频率:优化 RecyclerView 的适配器和布局,减少不必要的重绘。

转自:RecyclerView 错误及解决方案 - 豆包

更多推荐