记录 RecyclerView 滚动位置并恢复是一个很常见的需求,通常需要精准恢复到上次的位置。
预计会用到 RecyclerView 相关的三个知识点:
监听 RecyclerView 滚动状态
监听 RecyclerView 完成绘制
滚动 RecyclerView 到指定的位置
思路:
在「RecyclerView 完成绘制」时,记录首个元素的偏移量作为基础偏移量;此步非必须流程,根据自己实际情况看是否需要,有些情况此基础偏移量为0,即不存在基础偏移量的问题;
在「监听 RecyclerView 滚动状态」里,滚动结束时,记录最左侧的元素坐标和偏移量;
再次打开当前页面时,检查是否存在偏移量信息的记录,有则进行位置恢复,即「滚动 RecyclerView 到指定的位置」。
条件:
1 2 3 4 5 6 7 8 LinearLayoutManager linearLayoutManager = new LinearLayoutManager (mContext);linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); int rvBaseOffset; int rvPosition; int rvOffset;
1. 前置知识 监听 RecyclerView 滚动状态 调用 recyclerView.addOnScrollListener(onScrollListener);
来设置 RecyclerView 的滚动监听器。
自定义一个类来继承 RecyclerView.OnScrollListener
并覆写 onScrollStateChanged()
方法,在其中处理关键状态的监听。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private class MOnScrollListener extends RecyclerView .OnScrollListener { @Override public void onScrollStateChanged (@NonNull RecyclerView recyclerView, int newState) { super .onScrollStateChanged(recyclerView, newState); boolean hasStarted = newState == RecyclerView.SCROLL_STATE_DRAGGING; boolean hasEnded = newState == RecyclerView.SCROLL_STATE_IDLE; if (hasEnded && linearLayoutManager != null ) { View leftView = linearLayoutManager.getChildAt(0 ); if (leftView == null ) { return ; } rvOffset = leftView.getLeft(); rvPosition = linearLayoutManager.getPosition(leftView); SPManager.getInstance().setRvOffset(rvOffset); SPManager.getInstance().setRvPosition(rvPosition); } } }
监听 RecyclerView 完成绘制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 recyclerView.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver .OnGlobalLayoutListener() { @Override public void onGlobalLayout () { View leftView = linearLayoutManager.getChildAt(0 ); if (leftView == null ) { return ; } rvBaseOffset = leftView.getLeft(); int lastPosition = linearLayoutManager.getPosition(leftView); if (lastPosition == 0 ) { SPManager.getInstance().setRvBaseOffset(rvBaseOffset); } recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this ); } });
滚动 RecyclerView 到指定的位置 具有类似功能的 API 有:
RecyclerView.scrollToPosition(int position)
RecyclerView.smoothScrollToPosition(int position)
RecyclerView.scrollBy(int x, int y)
LinearLayoutManager.scrollToPositionWithOffset(int position, int offset)
注意不同 API 是不同的类的方法,另外还有使用有滚动动画的区别等。
这里使用 LinearLayoutManager.scrollToPositionWithOffset(int position, int offset)
,它可以精准的定位到上次的位置,也不需要展示滚动动画。
2. 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 LinearLayoutManager linearLayoutManager = new LinearLayoutManager (getContext());linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); recyclerView.setLayoutManager(linearLayoutManager); onScrollListener = new MOnScrollListener (); recyclerView.addOnScrollListener(onScrollListener); int lastPositionHistory = SPManager.getInstance().getRvPosition();if (lastPositionHistory == -1 ) { recyclerView.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver .OnGlobalLayoutListener() { @Override public void onGlobalLayout () { View leftView = linearLayoutManager.getChildAt(0 ); if (leftView == null ) { return ; } rvBaseOffset = leftView.getLeft(); int lastPosition = linearLayoutManager.getPosition(leftView); if (lastPosition == 0 ) { SPManager.getInstance().setRvBaseOffset(rvBaseOffset); } recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this ); } }); } if (linearLayoutManager != null && recyclerView != null ) { int lastPosition = SPManager.getInstance().getRvPositiont(); if (lastPosition >= 0 && linearLayoutManager != null ) { int lastOffset = SPManager.getInstance().getRvOffset(); int baseOffset = SPManager.getInstance().getRvBaseOffset(); linearLayoutManager.scrollToPositionWithOffset(lastPosition, lastOffset - baseOffset); } }
3. 回顾总结 本次记录的是实际使用中的情况,基础偏移量的值不为 0 可能不是普遍现象,没看到过相关记录,特记录下来,避免后人踩坑吧。
参考链接:
如果有什么建议或者问题可以随时联系我,共同探讨学习: