记录 RecyclerView 滚动位置并恢复是一个很常见的需求,通常需要精准恢复到上次的位置。
预计会用到 RecyclerView 相关的三个知识点:
思路:
条件:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); // 水平,本文以此为例
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); //或 垂直
//以下3个变量的值需要持久化存储到 SharedPreferences 类似的地方
int rvBaseOffset; //初始状态时position=0元素的的基础偏移量 Offset
int rvPosition; //最左边首个可见元素的 position
int rvOffset; //最左边首个可见元素的偏移量 Offset
调用 recyclerView.addOnScrollListener(onScrollListener);
来设置 RecyclerView 的滚动监听器。
自定义一个类来继承 RecyclerView.OnScrollListener
并覆写 onScrollStateChanged()
方法,在其中处理关键状态的监听。
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; //此时,RecyclerView 滚动开始
boolean hasEnded = newState == RecyclerView.SCROLL_STATE_IDLE; //此时,RecyclerView 滚动结束
if (hasEnded && linearLayoutManager != null) {
View leftView = linearLayoutManager.getChildAt(0); //获取可视的第一个view
if (leftView == null) {
return;
}
rvOffset = leftView.getLeft(); //获取该view的左边的偏移量,垂直布局时获取 getTop()
rvPosition = linearLayoutManager.getPosition(leftView);
SPManager.getInstance().setRvOffset(rvOffset);
SPManager.getInstance().setRvPosition(rvPosition);
}
}
}
recyclerView.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
View leftView = linearLayoutManager.getChildAt(0); //获取可视的第一个view
if (leftView == null) {
return;
}
rvBaseOffset = leftView.getLeft();
int lastPosition = linearLayoutManager.getPosition(leftView);
if (lastPosition == 0) {
SPManager.getInstance().setRvBaseOffset(rvBaseOffset);
}
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
具有类似功能的 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)
,它可以精准的定位到上次的位置,也不需要展示滚动动画。
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); //获取可视的第一个view
if (leftView == null) {
return;
}
rvBaseOffset = leftView.getLeft();
int lastPosition = linearLayoutManager.getPosition(leftView);
if (lastPosition == 0) {
SPManager.getInstance().setRvBaseOffset(rvBaseOffset);
}
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
//在合适的时机,比如 onResume() 或者获取完数据后进行位置恢复
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);
}
}
本次记录的是实际使用中的情况,基础偏移量的值不为 0 可能不是普遍现象,没看到过相关记录,特记录下来,避免后人踩坑吧。
参考链接: