RecyclerView源码解析

基础

RecyclerView相对于以前的ListView来说,更加灵活。其所拆分出来的各个类的分工更加明确,很好地体现了我们经常所说的职责单一原则。我们这里先对其中使用到的类进行一下讲解

  • LayoutManager:RecyclerView的布局管理者,主要负责对于RecyclerView子View的测量和布局工作。
  • RecyclerView.Recycler:缓存的核心类。RecyclerView强大的缓存能力都是基于这个类来实现的。是缓存的核心工具类。
  • Adapter:Adapter的基类。负责将ViewHolder中的数据和RecyclerView中的控件进行绑定处理。
  • ViewHolder:视图和元数据类。它持有了要显示的数据信息,包括位置、View、ViewType等。

源码

无论是View还是ViewGroup的子类,都是通过 onMeasure() 来实现测量工作的,那么我们对于RecyclerView的源码解析就把onMeasure当作我们的切入点

自身测量
    //RecyclerView.java
    protected void onMeasure(int widthSpec, int heightSpec) {
        //dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3肯定会执行,但是会根据具体的情况来区分是在onMeasure还是onLayout中执行。
        if (mLayout == null) {//LayoutManager为空,那么就使用默认的测量策略
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.mAutoMeasure) {
            //有LayoutManager,开启了自动测量
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            //步骤1  调用LayoutManager的onMeasure方法来进行测量工作
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            //如果width和height都已经是精确值,那么就不用再根据内容进行测量,后面步骤不再处理
            if (skipMeasure || mAdapter == null) {
                return;
            }
            //如果测量过程后的宽或者高都没有精确,那么就需要根据child来进行布局,从而来确定其宽和高。
            // 当前的布局状态是start
            if (mState.mLayoutStep == State.STEP_START) {
                //布局的第一部 主要进行一些初始化的工作
                dispatchLayoutStep1();
            }
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //执行布局第二步。先确认子View的大小与布局
            dispatchLayoutStep2();
            // 布局过程结束,根据Children中的边界信息计算并设置RecyclerView长宽的测量值
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            //检查是否需要再此测量。如果RecyclerView仍然有非精确的宽和高,或者这里还有至少一个Child还有非精确的宽和高,我们就需要再次测量。
            // 比如父子尺寸属性互相依赖的情况,要改变参数重新进行一次
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            //有LayoutManager,没有开启自动测量。一般系统的三个LayoutManager都是自动测量,
            // 如果是我们自定义的LayoutManager的话,可以通过setAutoMeasureEnabled关闭自动测量功能
            //RecyclerView已经设置了固定的Size,直接使用固定值即可
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            //如果在测量过程中数据发生变化,需要先对数据进行处理
            ...
            // 处理完新更新的数据,然后执行自定义测量操作。
            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            eatRequestLayout();
            //没有设置固定的宽高,则需要进行测量
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            resumeRequestLayout(false);
            mState.mInPreLayout = false;
        }
    }

在这个方法里面,根据不同的情况进行了不同的处理。

  1. 没有设置LayoutManager的情况下,直接使用默认的测量方法。
  2. 当设置了Layoutmanager而且开启了自动测量功能。
  3. 设置了LayoutManager但是没有开启自动测量。

我们先从最简单的来分析。

第一种:没有设置LayoutManager。

因为RecyclerView的所有的测量和布局工作都是交给LayoutManager来处理的,如果没有设置的话,只能使用默认的测量方案了。

第三种:有LayoutManager,而且关闭了自动测量功能。

关闭测量的情况下不需要考虑子View的大小和布局。直接按照正常的流程来进行测量即可。如果直接已经设置了固定的宽高,那么直接使用固定值即可。如果没有设置固定宽高,那么就按照正常的控件一样,根据父级的要求与自身的属性进行测量。

第二种:有LayoutManager,开启了自动测量。

这种情况是最复杂的,需要根据子View的布局来调整自身的大小。需要知道子View的大小和布局。所以RecyclerView将布局的过程提前到这里来进行了。

我们简化一下代码再看

//RecyclerView.java
if (mLayout.mAutoMeasure) {
            //调用LayoutManager的onMeasure方法来进行测量工作
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            //如果width和height都已经是精确值,那么就不用再根据内容进行测量,后面步骤不再处理
            if (skipMeasure || mAdapter == null) {
                return;
            }
            if (mState.mLayoutStep == State.STEP_START) {
                //布局的第一部 主要进行一些初始化的工作
                dispatchLayoutStep1();
            }
            ...
            //开启了自动测量,需要先确认子View的大小与布局
            dispatchLayoutStep2();
            ...
            //再根据子View的情况决定自身的大小
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            if (mLayout.shouldMeasureTwice()) {
                ...
                //如果有父子尺寸属性互相依赖的情况,要改变参数重新进行一次
                dispatchLayoutStep2();
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        }

对于RecyclerView的测量和绘制工作,是需要 dispatchLayoutStep1 ,dispatchLayoutStep2 , dispatchLayoutStep3 这三步来执行的,step1里是进行预布局,主要跟记录数据更新时需要进行的动画所需的信息有关,step2就是实际循环执行了子View的测量布局的一步,而step3主要是用来实际执行动画。而且通过 mLayoutStep记录了当前执行到了哪一步。在开启自动测量的情况下如果没有设置固定宽高,那么会执行setp1和step2。在step2执行完后就可以调用setMeasuredDimensionFromChildren 方法,根据子类的测量布局结果来设置自身的大小。

我们先不进行分析step1,step2和step3的具体功能。直接把 onLayout 的代码也贴出来,看一下这3步是如何保证都能够执行的。

 //RecyclerView.java
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        dispatchLayout();
    }

    void dispatchLayout() {
        if (mAdapter == null) {//没有设置adapter,返回
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {//没有设置LayoutManager,返回
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        //在onMeasure阶段,如果宽高是固定的,那么mLayoutStep == State.STEP_START 而且dispatchLayoutStep1和dispatchLayoutStep2不会调用
        //所以这里就会调用一下
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {
            //在onMeasure阶段,如果执行了dispatchLayoutStep1,但是没有执行dispatchLayoutStep2,就会执行dispatchLayoutStep2
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        //最终调用dispatchLayoutStep3
        dispatchLayoutStep3();
    }

可以看到,其实在 onLayout 阶段会根据 onMeasure 阶段3个步骤执行到了哪个,然后会在 onLayout 中把剩下的步骤执行。

OK,到现在整个流程通了,在这3个步骤中,step2就是执行了子View的测量布局的一步,也是最重要的一环,所以我们将关注的重点放在这个函数。

//RecyclerView.java
    private void dispatchLayoutStep2() {
        //禁止布局请求
        eatRequestLayout();
        ...
        mState.mInPreLayout = false;
        //调用LayoutManager的layoutChildren方法来布局
        mLayout.onLayoutChildren(mRecycler, mState);
        ...
        resumeRequestLayout(false);
    }

这里调用LayoutManager的 onLayoutChildren 方法,将对于子View的测量和布局工作交给了LayoutManager。而且我们在自定义LayoutManager的时候也必须要重写这个方法来描述我们的布局错略。这里我们分析最经常使用的 LinearLayoutManager(后面简称LLM) 。我们这里只研究垂直方向布局的情况。

//LinearLayoutManager.java
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state

在方法的开始位置,直接就扔给了我们一段说明文档,告诉了我们LinearLayoutManager 中的布局策略。简单翻译一下:

  1. 通过子控件和其他的变量信息。找到一个锚点和锚点项的位置。
  2. 从锚点的位置开始,往上,填
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值