前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android必知必会 - RecyclerView 恢复上次滚动位置

Android必知必会 - RecyclerView 恢复上次滚动位置

作者头像
他叫自己MR.张
发布2020-05-28 15:25:55
2.1K0
发布2020-05-28 15:25:55
举报
文章被收录于专栏:Android必知必会

记录 RecyclerView 滚动位置并恢复是一个很常见的需求,通常需要精准恢复到上次的位置。

预计会用到 RecyclerView 相关的三个知识点:

  1. 监听 RecyclerView 滚动状态
  2. 监听 RecyclerView 完成绘制
  3. 滚动 RecyclerView 到指定的位置

思路:

  • 在「RecyclerView 完成绘制」时,记录首个元素的偏移量作为基础偏移量;此步非必须流程,根据自己实际情况看是否需要,有些情况此基础偏移量为0,即不存在基础偏移量的问题;
  • 在「监听 RecyclerView 滚动状态」里,滚动结束时,记录最左侧的元素坐标和偏移量;
  • 再次打开当前页面时,检查是否存在偏移量信息的记录,有则进行位置恢复,即「滚动 RecyclerView 到指定的位置」。

条件:

代码语言:javascript
复制
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

1. 前置知识

监听 RecyclerView 滚动状态

调用 recyclerView.addOnScrollListener(onScrollListener); 来设置 RecyclerView 的滚动监听器。

自定义一个类来继承 RecyclerView.OnScrollListener 并覆写 onScrollStateChanged() 方法,在其中处理关键状态的监听。

代码语言:javascript
复制
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 完成绘制

代码语言:javascript
复制
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);
  }
});

滚动 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. 代码实现

代码语言:javascript
复制
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);
  }
}

3. 回顾总结

本次记录的是实际使用中的情况,基础偏移量的值不为 0 可能不是普遍现象,没看到过相关记录,特记录下来,避免后人踩坑吧。

参考链接:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/04/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前置知识
    • 监听 RecyclerView 滚动状态
      • 监听 RecyclerView 完成绘制
        • 滚动 RecyclerView 到指定的位置
        • 2. 代码实现
        • 3. 回顾总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档