RecycleView浅谈

RecyclerView 简称 RV, 是作为 ListView 和 GridView 的加强版出现的,目的是在有限的屏幕之上展示大量的内容,因此 RecyclerView 的复用机制的实现是它的一个核心部分

常规使用方式如下:

RecycleView.setLayoutManager();
RecycleView.setAdapter();
RecycleView.setItemAnimator();
RecycleView.addItemDecoration();

setLayoutManager(): 必选, 设置RecycleView的布局管理器,决定RecycleView显示风格,默认的有线性布局管理器LinearLayoutManager, 网格布局管理器GridLayoutManager, 瀑布流布局管理器 StaggeredGridLayoutManager

setAdapter: 必选, 设置RecycleView 的数据适配器, 当数据发生改变时, 以通知者的身份,通知RV数据改变接受刷新数据。

setItemAnimator 非必选项 设置RecycleView的item装饰器, 经常用来设置Item的分割线。

setItemAnimator: 非必选项 设置RV中的Item动画。

绘制流程

RV 的 onMeasure 方法如下:
在这里插入图片描述

  1. 表示在XML布局文件中, RecycleView的宽高设置为matchParent或者具体大小,那么直接将 skipMeasure 置为 true,并调用 mLayout(传入的 LayoutManager)的 onMeasure 方法测量自身的宽高即可。
  2. 表示在XML布局中,RV的狂傲设置为Wrap_content, 则会执行下面的dispatchLayoutStep2(), 其实就是测量RecycleView的子View大小, 最终确定RecycleView的实际宽高。

onLayout

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

调用dispatchLayout

 void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

调用dispatchLayoutStep2重新布局子View

在这里插入图片描述如果在onMeasure中那样执行dispatchLayoutStep2方法去测量子View, 则会在onLayout阶段重新执行。

private void dispatchLayoutStep2() {
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }

核心逻辑是调用了mLayout的onLayoutChildren方法。 这个方法是LayoutManager中的一个空方法,主要作用是测量 RV 内的子 View 大小,并确定它们所在的位置。LinearLayoutManager、GridLayoutManager,以及 StaggeredLayoutManager 都分别复写了这个方法,并实现了不同方式的布局。

以 LinearLayoutManager 的实现为例,展开分析,实现如下 :

在这里插入图片描述

  1. 在onLayoutChildren在调用fill方法, 完成子View的测量布局工作。
  2. 在fill中通过while循环判断是否还有剩余足够空间来绘制一个完整的子View。
  3. layoutChunk 方法中是子 View 测量布局的真正实现,每次执行完之后需要重新计算 remainingSpace。

layoutChunk 是一个非常核心的方法,这个方法执行一次就填充一个 ItemView 到 RV,部分代码如下

在这里插入图片描述
图中 1 处从缓存(Recycler)中取出子 ItemView,然后调用 addView 或者 addDisappearingView 将子 ItemView 添加到 RV 中。

图中 2 处测量被添加的 RV 中的子 ItemView 的宽高。

图中 3 处根据所设置的 Decoration、Margins 等所有选项确定子 ItemView 的显示位置。

onDraw
这个方法很简单,如果有添加 ItemDecoration,则循环调用所有的 Decoration 的 onDraw 方法,将其显示。至于所有的子 ItemView 则是通过 Android 渲染机制递归的调用子 ItemView 的 draw 方法显示到屏幕上。

缓存复用原理 Recycler

public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        int mViewCacheMax = DEFAULT_CACHE_SIZE;

        RecycledViewPool mRecyclerPool;

        private ViewCacheExtension mViewCacheExtension;

Recycler 的缓存机制就是通过上图中的这些数据容器来实现的,实际上 Recycler 的缓存也是分级处理的,根据访问优先级从上到下可以分为 4 级,如下:

1级缓存 mAttachedScrap
2级缓存 mChangedScrap
3级缓存mCachedViews
4级缓存mRecyclerPool

1级缓存 mAttachedScrap&mChangedScrap

通过下拉刷新列表中的内容,当刷新被触发时,只需要在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来。

2级缓存 mChangedScrap
它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder

2级缓存 ViewCacheExtension
开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制。只是一般情况下我们不会自己实现也不建议自己去添加缓存逻辑,因为这个类的使用门槛较高,需要开发人员对 RV 的源码非常熟悉

4级缓存 RecycledViewPool
RecycledViewPool 同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。这就同最早的 ListView 中的使用 ViewHolder 复用 convertView 的道理是一致的,因此 RV 也算是将 ListView 的优点完美的继承过来。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值