RecyclerView更新功能暂停,1600项scrollBy失效问题求助
为什么RecyclerView更新数据后立即执行scrollBy会失效?
兄弟,我太懂你这种踩坑的憋屈感了!咱们来好好拆解下你遇到的问题:
问题核心原因
你现在的执行逻辑是「添加数据 → notifyDataSetChanged → 滚动操作」,但这里有个关键的时间差问题:
notifyDataSetChanged会触发RecyclerView的全量刷新,1600条数据的情况下,UI线程要完成大量的item测量、布局、绘制工作,这段时间里UI线程是卡顿冻结状态的。- 你把滚动操作和
notifyDataSetChanged放在同一个runOnUiThread里,它们会被依次加入UI线程的消息队列,但滚动操作是在RecyclerView还没完成所有item布局时就执行了——这时候列表的布局状态还没更新,滚动指令自然无法作用到已经就绪的列表上,看起来就像“失效”了。 - 至于按钮点击后滚动正常,是因为按钮操作是在RecyclerView完全刷新完成、UI线程恢复响应之后触发的,这时候列表已经布局完毕,scrollBy能正确生效。
哪怕你用Thread套runOnUiThread,本质上还是把所有任务丢到UI线程队列里,解决不了“滚动时机过早”的问题,而且全量刷新本身就会导致UI短暂卡顿,这是1600条数据全量更新的必然结果。
解决办法
我给你几个靠谱的方案,按推荐优先级排序:
1. 监听RecyclerView布局完成事件(最直接解决滚动时机问题)
等RecyclerView彻底完成所有item的布局后,再执行滚动操作,用addOnLayoutChangeListener就能实现:
// 先给RecyclerView加布局完成监听 mBinding.recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { // 记得移除监听,避免重复触发 mBinding.recyclerView.removeOnLayoutChangeListener(this); // 此时布局已完成,执行滚动 mBinding.recyclerView.scrollBy(0,((int)((51*movex)*destiny))); mBinding.horizontalScrollView.scrollBy(((int)((51*movey)*destiny)),0); } }); // 再执行数据刷新和隐藏loading adapter.notifyDataSetChanged(); mBinding.loading.setVisibility(View.GONE);
2. 用DiffUtil优化刷新性能(从根源减少卡顿)
1600条数据全量刷新太耗性能,用DiffUtil计算新旧数据的差异,只刷新变化的item,能大幅降低UI线程的负载,既减少卡顿,也能让滚动操作更大概率在布局就绪后执行:
首先创建DiffCallback类:
public class YourDataDiffCallback extends DiffUtil.Callback { private List<YourData> oldDataList; private List<YourData> newDataList; public YourDataDiffCallback(List<YourData> oldDataList, List<YourData> newDataList) { this.oldDataList = oldDataList; this.newDataList = newDataList; } @Override public int getOldListSize() { return oldDataList.size(); } @Override public int getNewListSize() { return newDataList.size(); } @Override public boolean areItemsTheSame(int oldItemPos, int newItemPos) { // 用数据的唯一标识判断是否为同一个item,比如id return oldDataList.get(oldItemPos).getId() == newDataList.get(newItemPos).getId(); } @Override public boolean areContentsTheSame(int oldItemPos, int newItemPos) { // 判断item内容是否完全一致,不一致才需要刷新 return oldDataList.get(oldItemPos).equals(newDataList.get(newItemPos)); } }
然后替换掉notifyDataSetChanged:
// 假设oldDataList是刷新前的数据集,newDataList是新数据 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new YourDataDiffCallback(oldDataList, newDataList)); diffResult.dispatchUpdatesTo(adapter); // 之后再配合布局监听执行滚动(保险起见)
3. 应急方案:postDelayed(不推荐,但临时可用)
如果只是临时测试,不想改太多代码,可以把滚动操作延迟一段时间,等刷新完成后执行,但这个方法的缺点是延迟时间不好把控(不同设备性能不同):
adapter.notifyDataSetChanged(); mBinding.loading.setVisibility(View.GONE); // 延迟执行滚动,时间根据实际情况调整 mBinding.recyclerView.postDelayed(new Runnable() { @Override public void run() { mBinding.recyclerView.scrollBy(0,((int)((51*movex)*destiny))); mBinding.horizontalScrollView.scrollBy(((int)((51*movey)*destiny)),0); } }, 300);
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

