RecyclerView 源码分析(一)

来源:互联网 发布:网络安全产品分类 编辑:程序博客网 时间:2024/06/06 02:30

    • 构造函数
      • mChildHelper
      • mAdapterHelper
    • LayoutManager
      • LinearLayoutManager
      • setLayoutManager
    • ItemDecoration
      • DividerItemDecoration
      • addItemDecoration
    • Adapter
    • onMeasure
    • onLayout
      • dispatchLayoutStep1
      • dispatchLayoutStep2
    • draw
    • 总结

在工作上,越来越多的使用 RecyclerView 来代替 ListViewGridView,更有甚者,我发现有人想用 RecyclerView 来替代 ViewPager,但是前提是要解决预加载的问题。然而,我并不只想从表面去使用它,我需要知根知底,这样才能以不变应万变。

本文分析的版本为

compile 'com.android.support:recyclerview-v7:26.1.0'

本篇文章只分析 RecyclerView 首次加载并显示的过程。而动画原理,回收机制留到后面文章分析。所以分析源码的时候,就有侧重点,这样才好分析。

在看这篇文章前,需要对 RecyclerView 的使用有一个基本的认识,使用 RecyclerView 基本三要素如下:

  1. 设置 LayoutManager,用来 measurelayoutRecycler 获取到的 View。其实 LayoutManager 还有负责 RecyclerView 的测量,这在后面的分析中将会看到。
  2. 设置适配器 Adapter,也就是 RecyclerView.Adapter,用来将数据转化为 RecyclerView 可用的 View
  3. 设置 ItemDecoration,这个是可选的,一般用于绘制分割线,系统默认为 LinearLayoutManager 配置了一个 DividerItemDecoration

构造函数

说了这么多,那么从哪里开始分析起呢?RecyclerView 首次是从 XML 加载,当然先看构造函数。

    public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        if (attrs != null) {            TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);            // 如果不设置clipToPadding,默认就是为 true            mClipToPadding = a.getBoolean(0, true);            a.recycle();        } else {            mClipToPadding = true;        }        // 可根据控件大小自动伸缩        setScrollContainer(true);        setFocusableInTouchMode(true);        final ViewConfiguration vc = ViewConfiguration.get(context);        mTouchSlop = vc.getScaledTouchSlop();        mScaledHorizontalScrollFactor =                ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);        mScaledVerticalScrollFactor =                ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();        setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);        mItemAnimator.setListener(mItemAnimatorListener);        // 给 mAdapterHelper 赋值        initAdapterManager();        // 给 mChildHelper 赋值        initChildrenHelper();        // If not explicitly specified this view is important for accessibility.        if (ViewCompat.getImportantForAccessibility(this)                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {            ViewCompat.setImportantForAccessibility(this,                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);        }        mAccessibilityManager = (AccessibilityManager) getContext()                .getSystemService(Context.ACCESSIBILITY_SERVICE);        setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));        // Create the layoutManager if specified.        boolean nestedScrollingEnabled = true;        if (attrs != null) {            int defStyleRes = 0;            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,                    defStyle, defStyleRes);            String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);            int descendantFocusability = a.getInt(                    R.styleable.RecyclerView_android_descendantFocusability, -1);            if (descendantFocusability == -1) {                setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);            }            // 可以设置 fastScrollEnabled 属性,但是同时要设置下面四个属性            // TODO: 有没有高级的用法?            mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false);            if (mEnableFastScroller) {                StateListDrawable verticalThumbDrawable = (StateListDrawable) a                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable);                Drawable verticalTrackDrawable = a                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable);                StateListDrawable horizontalThumbDrawable = (StateListDrawable) a                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable);                Drawable horizontalTrackDrawable = a                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable);                initFastScroller(verticalThumbDrawable, verticalTrackDrawable,                        horizontalThumbDrawable, horizontalTrackDrawable);            }            a.recycle();            // 需要在属性中声明layoutManager才能创建 LayoutManager 对象            createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);            if (Build.VERSION.SDK_INT >= 21) {                a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,                        defStyle, defStyleRes);                // nestedScrollingEnabled 默认为 true                nestedScrollingEnabled = a.getBoolean(0, true);                a.recycle();            }        } else {            setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);        }        // Re-set whether nested scrolling is enabled so that it is set on all API levels        setNestedScrollingEnabled(nestedScrollingEnabled);    }

构造函数通常都是初始化一些属性的,当然这里也不例外。基于本文分析的侧重点,我列出几个重要的变量的初始化:

  1. initAdapterManager() 初始化了 mAdapterHelper 变量
  2. initChildrenHelper() 初始化了 mChildHelper 变量

这2个变量在后面的分析大量被使用,所以,我在后面列出了这2个变量初始化的源码,以供查询参考。

另外,RecyclerView 一直被人诟病的没有 FastScroller 的属性,这个版本也有了。不过要同时设置5个属性! 你没看错,是5个属性,具体如何使用,请参考 https://stackoverflow.com/questions/45370246/how-to-use-fastscrollenabled-in-recyclerview。 不过,我试了下,功能好像还是不完全,例如 ListViewAdapter 如果实现了 SectionIndexer 接口, ListViewFastScroller 就可以通过滚动显示一个索引, 具体效果,参见 7.0 版本的 Launcher 的效果。

还有一点需要提一下,就是可以在 XML 中,为 RecyclerView 设置 android:layoutManager 属性,然后系统根据这个名字,通过反射创建 LayoutManager 对象。不过,我们通常都是动态去设置 LayoutManager,所以这里就不去分析 XML 设置 LayoutManager 的情况。

mChildHelper

    private void initChildrenHelper() {        mChildHelper = new ChildHelper(new ChildHelper.Callback() {            @Override            public int getChildCount() {                return RecyclerView.this.getChildCount();            }            @Override            public void addView(View child, int index) {                if (VERBOSE_TRACING) {                    TraceCompat.beginSection("RV addView");                }                RecyclerView.this.addView(child, index);                if (VERBOSE_TRACING) {                    TraceCompat.endSection();                }                dispatchChildAttached(child);            }            @Override            public int indexOfChild(View view) {                return RecyclerView.this.indexOfChild(view);            }            @Override            public void removeViewAt(int index) {                final View child = RecyclerView.this.getChildAt(index);                if (child != null) {                    dispatchChildDetached(child);                    // Clear any android.view.animation.Animation that may prevent the item from                    // detaching when being removed. If a child is re-added before the                    // lazy detach occurs, it will receive invalid attach/detach sequencing.                    child.clearAnimation();                }                if (VERBOSE_TRACING) {                    TraceCompat.beginSection("RV removeViewAt");                }                RecyclerView.this.removeViewAt(index);                if (VERBOSE_TRACING) {                    TraceCompat.endSection();                }            }            @Override            public View getChildAt(int offset) {                return RecyclerView.this.getChildAt(offset);            }            @Override            public void removeAllViews() {                final int count = getChildCount();                for (int i = 0; i < count; i++) {                    View child = getChildAt(i);                    dispatchChildDetached(child);                    // Clear any android.view.animation.Animation that may prevent the item from                    // detaching when being removed. If a child is re-added before the                    // lazy detach occurs, it will receive invalid attach/detach sequencing.                    child.clearAnimation();                }                RecyclerView.this.removeAllViews();            }            @Override            public ViewHolder getChildViewHolder(View view) {                return getChildViewHolderInt(view);            }            @Override            public void attachViewToParent(View child, int index,                    ViewGroup.LayoutParams layoutParams) {                final ViewHolder vh = getChildViewHolderInt(child);                if (vh != null) {                    if (!vh.isTmpDetached() && !vh.shouldIgnore()) {                        throw new IllegalArgumentException("Called attach on a child which is not"                                + " detached: " + vh + exceptionLabel());                    }                    if (DEBUG) {                        Log.d(TAG, "reAttach " + vh);                    }                    vh.clearTmpDetachFlag();                }                RecyclerView.this.attachViewToParent(child, index, layoutParams);            }            @Override            public void detachViewFromParent(int offset) {                final View view = getChildAt(offset);                if (view != null) {                    final ViewHolder vh = getChildViewHolderInt(view);                    if (vh != null) {                        if (vh.isTmpDetached() && !vh.shouldIgnore()) {                            throw new IllegalArgumentException("called detach on an already"                                    + " detached child " + vh + exceptionLabel());                        }                        if (DEBUG) {                            Log.d(TAG, "tmpDetach " + vh);                        }                        vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);                    }                }                RecyclerView.this.detachViewFromParent(offset);            }            @Override            public void onEnteredHiddenState(View child) {                final ViewHolder vh = getChildViewHolderInt(child);                if (vh != null) {                    vh.onEnteredHiddenState(RecyclerView.this);                }            }            @Override            public void onLeftHiddenState(View child) {                final ViewHolder vh = getChildViewHolderInt(child);                if (vh != null) {                    vh.onLeftHiddenState(RecyclerView.this);                }            }        });    }

mAdapterHelper

    void initAdapterManager() {        mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {            @Override            public ViewHolder findViewHolder(int position) {                final ViewHolder vh = findViewHolderForPosition(position, true);                if (vh == null) {                    return null;                }                // ensure it is not hidden because for adapter helper, the only thing matter is that                // LM thinks view is a child.                if (mChildHelper.isHidden(vh.itemView)) {                    if (DEBUG) {                        Log.d(TAG, "assuming view holder cannot be find because it is hidden");                    }                    return null;                }                return vh;            }            @Override            public void offsetPositionsForRemovingInvisible(int start, int count) {                offsetPositionRecordsForRemove(start, count, true);                mItemsAddedOrRemoved = true;                mState.mDeletedInvisibleItemCountSincePreviousLayout += count;            }            @Override            public void offsetPositionsForRemovingLaidOutOrNewView(                    int positionStart, int itemCount) {                offsetPositionRecordsForRemove(positionStart, itemCount, false);                mItemsAddedOrRemoved = true;            }            @Override            public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {                viewRangeUpdate(positionStart, itemCount, payload);                mItemsChanged = true;            }            @Override            public void onDispatchFirstPass(AdapterHelper.UpdateOp op) {                dispatchUpdate(op);            }            void dispatchUpdate(AdapterHelper.UpdateOp op) {                switch (op.cmd) {                    case AdapterHelper.UpdateOp.ADD:                        mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);                        break;                    case AdapterHelper.UpdateOp.REMOVE:                        mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);                        break;                    case AdapterHelper.UpdateOp.UPDATE:                        mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,                                op.payload);                        break;                    case AdapterHelper.UpdateOp.MOVE:                        mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);                        break;                }            }            @Override            public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {                dispatchUpdate(op);            }            @Override            public void offsetPositionsForAdd(int positionStart, int itemCount) {                offsetPositionRecordsForInsert(positionStart, itemCount);                mItemsAddedOrRemoved = true;            }            @Override            public void offsetPositionsForMove(int from, int to) {                offsetPositionRecordsForMove(from, to);                // should we create mItemsMoved ?                mItemsAddedOrRemoved = true;            }        });    }

LayoutManager

RecyclerView 在经过 onFinishInflate() 方法后,就完成了加载,还要经历 onAttachedToWindow() 方法添加到 Window 中。这样 Activity 才能获取到 RecyclerView 对象,并对它做一些设置。首先为 RecyclerView 设置 LayoutManager ,在 Activity 中,通常进行如下设置

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

那么,我们就以这一段代码开始分析。

LinearLayoutManager

首先看看如何创建 LinearLayoutManager 对象

    public LinearLayoutManager(Context context) {        this(context, VERTICAL, false);    }    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {        setOrientation(orientation);        setReverseLayout(reverseLayout);        setAutoMeasureEnabled(true);    }

如果只用一个参数的构造方法,其实调用了三个参数的构造方法,也就是默认创建了垂直方向的 LinearLayoutManager

因此,如果你想要创建水平方向的 LinearLayoutManager,你就需要调用三个参数的构造方法,把第二个参数设置为 LinearLayoutManager.HORIZONTAL

最后一个参数 reverseLayout 的意思自己试一下就直到了。

最终,这个构造函数只是初始化了三个变量

  1. mOrientationVERTICAL
  2. mReverseLayoutfalse
  3. mAutoMeasuretrue

setLayoutManager

创建了 LayoutManager 后,现在看看如何给 RecyclerView 设置

    public void setLayoutManager(LayoutManager layout) {        // ... 省略无数行        mLayout = layout;        if (layout != null) {            if (layout.mRecyclerView != null) {                throw new IllegalArgumentException("LayoutManager " + layout                        + " is already attached to a RecyclerView:"                        + layout.mRecyclerView.exceptionLabel());            }            mLayout.setRecyclerView(this);            if (mIsAttached) {                mLayout.dispatchAttachedToWindow(this);            }        }        mRecycler.updateViewCacheSize();        requestLayout();    }

由于才刚刚设置 LayoutManager,因此我省略的不相干的无数行代码。

首先看下 mLayout.setRecyclerView(this)

        void setRecyclerView(RecyclerView recyclerView) {            if (recyclerView == null) {                mRecyclerView = null;                mChildHelper = null;                mWidth = 0;                mHeight = 0;            } else {                mRecyclerView = recyclerView;                mChildHelper = recyclerView.mChildHelper;                mWidth = recyclerView.getWidth();                mHeight = recyclerView.getHeight();            }            mWidthMode = MeasureSpec.EXACTLY;            mHeightMode = MeasureSpec.EXACTLY;        }

很简单,这里为 LayoutManager 对象初始化了 mRecyclerViewmChildHelper, mWidth, mHeight, mWidthMode, mHeightMode 成员变量。

再回到 setLayoutManager() 方法的第14行,mIsAttached 变量在 onAttachedToWindow() 方法中被设置为 true,所以看第15行 mLayout.dispatchAttachedToWindow(this)

        // LayoutManager.java        void dispatchAttachedToWindow(RecyclerView view) {            mIsAttachedToWindow = true;            onAttachedToWindow(view);        }

这里就只是为 LayoutManager 对象设置 mIsAttachedToWindowtrue,而 onAttachedToWindow() 是个空方法。

setLayoutManager() 最后调用了 requestLayout() 方法进行重新布局。

ItemDecoration

addItemDecoration(), setLayoutManager(), setAdapter() 的调用顺序其实没有关系,我这里把它提前,平常开发中,会选择添加分割线,代码如下

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL))

按照这个代码进行分析。

DividerItemDecoration

首先是创建 ItemDecoration 对象,这里创建的是 DividerItemDecoration 对象,看构造方法

    private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };    public DividerItemDecoration(Context context, int orientation) {        final TypedArray a = context.obtainStyledAttributes(ATTRS);        mDivider = a.getDrawable(0);        if (mDivider == null) {            Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "                    + "DividerItemDecoration. Please set that attribute all call setDrawable()");        }        a.recycle();        setOrientation(orientation);    }

老规矩,初始化变量 mDividermOrientation。 其中 mDivider 是系统提供的 drawableListView 的分隔线就是使用的这个 drawable

从这段代码的 Log 信息中可以看到,可以通过调用 setDrawable() 方法来设置自定义的分割线。

addItemDecoration

创建了 ItemDecoration 对象,现在就是设置 ItemDecoration 了。

    public void addItemDecoration(ItemDecoration decor) {        addItemDecoration(decor, -1);    }    public void addItemDecoration(ItemDecoration decor, int index) {        if (mLayout != null) {            mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"                    + " layout");        }        if (mItemDecorations.isEmpty()) {            setWillNotDraw(false);        }        // 1. 保存 decor 到 mItemDecorations(ArrayList<ItemDecoration>类型)        if (index < 0) {            mItemDecorations.add(decor);        } else {            mItemDecorations.add(index, decor);        }        // 2. 标记 RecyclerView 的所有 children 和 ViewHolder.itemView 的 LayoutParams.mInsetsDirty 为 true        markItemDecorInsetsDirty();        // 3. 请求重新布局        requestLayout();    }    

例子中调用的是一个参数的构造方法,默认调用 了两个参数的构造方法,index 设置为了 -1

纵观整个方法,分了三步,我已经用注释标明了。

最主要的就是第一步,由于 index-1 调用的就是 mItemDecorations.add(decor)

mItemDecorations 是一个 ArrayList< ItemDecoration > 类型的变量,也就是说,如果我们添加了多个 ItemDecoration,依次往 ArrayList 中添加。 而如果我们想给自己的 ItemDecoration 定义个顺序,那么就用两个参数的构造方法。

可见顺序很重要,因为影响了绘制的顺序,这个后面可以看到。

最后一步调用了 requestLayout() 来请求重新布局。

Adapter

Android 没有像 ListView 一样,提供写好的一些 Adapter,需要自己继承 RecyclerView.Adapter。这里就不演示如何写 Adapter了,只分析过程。

    public void setAdapter(Adapter adapter) {        // bail out if layout is frozen        setLayoutFrozen(false);        setAdapterInternal(adapter, false, true);        requestLayout();    }   

setAdapter() 方法在这里做的工作并不多,给变量 mAdapter 赋值 ,为 mAdapter 设置数据监听器。

到这里为止,好像并没有把加数加载进去。确实没有,因为我们需要再次刷新布局,这也就是为什么 setLayoutManager(), setAdapter(), addItemDecoration() 方法最后都会调用 requestLayout() 的原因。那么现在就进入了 measure, layout, draw 过程。

onMeasure()

    @Override    protected void onMeasure(int widthSpec, int heightSpec) {        if (mLayout == null) {            defaultOnMeasure(widthSpec, heightSpec);            return;        }        // 在 LinearLayoutManager 的构造函数中,默认设置 mAutoMeasure 为 true        if (mLayout.mAutoMeasure) {            final int widthMode = MeasureSpec.getMode(widthSpec);            final int heightMode = MeasureSpec.getMode(heightSpec);            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY                    && heightMode == MeasureSpec.EXACTLY;            // 实际也是调用 RecyclerView 的 defaultOnMeasure() 来进行测量,并保存测量的宽高            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);            if (skipMeasure || mAdapter == null) {                return;            }            // ...省略几千行        }         // ...省略一万行    }

measure 过程比较简单,代码已经注释了。 这里我只考虑了一类情形的测量,就是为 RecyclerView 设置准确的值,如 300dp 或者 MATCH_PARENT,这样就省略后面一大堆的步骤,~.~!

可以看到,如果 LayoutManager 不为 null,会调用 LayoutManageronMeasure() 方法为 RecyclerView 进行 measure 的过程。这就是前面提到的,LayoutManager 的作用包括了为 RecyclerView 进行测量。

LinearLayoutManger 并没有复写 onMeasure() 方法,所以调用了 LayoutManageronMeasure() 方法,代码如下

        // LayoutManager 的 onMeasure() 方法        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);        }   // RecyclerView 的 defaultOnMeasure() 方法   void defaultOnMeasure(int widthSpec, int heightSpec) {        // calling LayoutManager here is not pretty but that API is already public and it is better        // than creating another method since this is internal.        final int width = LayoutManager.chooseSize(widthSpec,                getPaddingLeft() + getPaddingRight(),                ViewCompat.getMinimumWidth(this));        final int height = LayoutManager.chooseSize(heightSpec,                getPaddingTop() + getPaddingBottom(),                ViewCompat.getMinimumHeight(this));        setMeasuredDimension(width, height);    }

可以看到最终测量的策略采用的是 LayoutManager 的一个静态方法 chooseSize(),代码如下

       public static int chooseSize(int spec, int desired, int min) {            final int mode = View.MeasureSpec.getMode(spec);            final int size = View.MeasureSpec.getSize(spec);            switch (mode) {                case View.MeasureSpec.EXACTLY:                    return size;                case View.MeasureSpec.AT_MOST:                    return Math.min(size, Math.max(desired, min));                case View.MeasureSpec.UNSPECIFIED:                default:                    return Math.max(desired, min);            }        }

从这里可以看出,如果给 RecyclerView 的宽/高设置了 WRAP_CONTENT 属性,那就不能正常显示的,值为 Math.min(size, Math.max(desired, min))

而如果设置为了 MATCH_PAREN 或者类似 300dp 这样的准确值,就可以得到精确的宽高值。

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;    }    void dispatchLayout() {        if (mAdapter == null) {            return;        }        if (mLayout == null) {            return;        }        mState.mIsMeasuring = false;        if (mState.mLayoutStep == State.STEP_START) {            dispatchLayoutStep1();            // 给 LayoutManager 的 mWidth,mWidthMode,mHeight,mHeightMode 赋值            mLayout.setExactMeasureSpecsFrom(this);            dispatchLayoutStep2();        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()                || mLayout.getHeight() != getHeight()) {            mLayout.setExactMeasureSpecsFrom(this);            dispatchLayoutStep2();        } else {            mLayout.setExactMeasureSpecsFrom(this);        }        dispatchLayoutStep3();    }    

到这里,就真正的开始布局了,分为了三步

  1. dispatchLayoutStep1()
  2. dispatchLayoutStep2()
  3. dispatchLayoutStep3()

dispatchLayoutStep1()

    /**     * The first step of a layout where we;     * - process adapter updates     * - decide which animation should run     * - save information about current views     * - If necessary, run predictive layout and save its information     */    private void dispatchLayoutStep1() {        mState.assertLayoutStep(State.STEP_START);        // 由于是刚开始布局,没有滑动,这里只是设置mState的mRemainingScrollHorizontal和mRemainingScrollVertical为0        fillRemainingScrollValues(mState);        mState.mIsMeasuring = false;        eatRequestLayout();        mViewInfoStore.clear();        onEnterLayoutOrScroll();        processAdapterUpdatesAndSetAnimationFlags();        saveFocusInfo();        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;        mItemsAddedOrRemoved = mItemsChanged = false;        // mState.mInPreLayout 为 false        mState.mInPreLayout = mState.mRunPredictiveAnimations;        // mState.mItemCount 等于 Adapter 中 getItemCount() 返回的个数        mState.mItemCount = mAdapter.getItemCount();        // RecyclerView 没有 children,所以mMinMaxLayoutPositions数组的两个值都是 NO_POSITION        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);        if (mState.mRunSimpleAnimations) {            // Step 0: Find out where all non-removed items are, pre-layout            // 本文不分析动画 ,所以这里省略一万行 ...        }        if (mState.mRunPredictiveAnimations) {            // Step 1: run prelayout: This will use the old positions of items. The layout manager            // 本文不分析动画 ,所以这里省略一万行 ...        } else {            clearOldPositions();        }        onExitLayoutOrScroll();        resumeRequestLayout(false);        mState.mLayoutStep = State.STEP_LAYOUT;    }

从注释中就可以看出,dispatchLayoutStep1() 做了四件事,而这四件事基本与本文的分析方向无关,这里只需要关心 mState 几个成员变量的值。

  1. mState.mInPreLayoutfalse
  2. mState.mItemCountmAdapter.getItemCount()

第二步之前还调用了 mLayout.setExactMeasureSpecsFrom(this)LayoutManager 保存 RecyclerView 的宽高的 sizemode

        void setExactMeasureSpecsFrom(RecyclerView recyclerView) {            setMeasureSpecs(                    MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),                    MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)            );        }        void setMeasureSpecs(int wSpec, int hSpec) {            mWidth = MeasureSpec.getSize(wSpec);            mWidthMode = MeasureSpec.getMode(wSpec);            if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {                mWidth = 0;            }            mHeight = MeasureSpec.getSize(hSpec);            mHeightMode = MeasureSpec.getMode(hSpec);            if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {                mHeight = 0;            }        }        

这里就是为 LayoutManager 对象设置了 mWidthmWidthModemHeightmHeightMode。 还记得前面也设置过一次吗?这里为何还要再设置一次呢,因为调用过 onMeasure ,所以得重新保存一次。

dispatchLayoutStep2()

第二步,dispatchLayoutStep2() 按照注释所说,是做实际的布局操作,这个就得重点看下。

    /**     * The second layout step where we do the actual layout of the views for the final state.     * This step might be run multiple times if necessary (e.g. measure).     */    private void dispatchLayoutStep2() {        eatRequestLayout();        onEnterLayoutOrScroll();        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);        // Step1: data update in one pass        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();        resumeRequestLayout(false);    }

dispatchLayoutStep2() 做了两件事情

  1. mAdapterHelper.consumeUpdatesInOnePass(); 对数据的操作(增加,删除,更新,移动)做更新。由于 RecyclerView 还没有 children,所以也就无法操作。
  2. mLayout.onLayoutChildren(mRecycler, mState);LayoutManagerRecyclerView 添加 View,并布局

LinearLayoutManageronLayoutChildren() 方法为例

    @Override    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.        // ...        // 第一步: 确定 mLayoutState 和 mOrientationHelper 已经被赋值        ensureLayoutState();        mLayoutState.mRecycle = false;        // resolve layout direction        // 决定 mShouldReverseLayout 值,因为我们设置的是垂直,所以值等于 mReverseLayout = false        resolveShouldLayoutReverse();        final View focused = getFocusedChild();        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION                || mPendingSavedState != null) {            mAnchorInfo.reset();            // 两个都是 false, 所以 mLayoutFromEnd 也是 false            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;            //关键步骤: calculate anchor position and coordinate            // 1. mAnchorInfo.mCoordinate = mRecyclerView.getPaddingTop()            // 2. mAnchorInfo.mPosition = 0;            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);            // 设置 mAnchroInfo.mValid 为 true            mAnchorInfo.mValid = true;        }         // ...        // LLM may decide to layout items for "extra" pixels to account for scrolling target,        // caching or predictive animations.        int extraForStart;        int extraForEnd;        // 还没有滚动位置,所以 extra 为 0        final int extra = getExtraLayoutSpace(state);        // If the previous scroll delta was less than zero, the extra space should be laid out        // at the start. Otherwise, it should be at the end.        // extraForEnd 和 extraForStart 都为 0        if (mLayoutState.mLastScrollDelta >= 0) {            extraForEnd = extra;            extraForStart = 0;        } else {            extraForStart = extra;            extraForEnd = 0;        }        // extraForStart 再加上 mRecyclerView.getPaddingTop()        extraForStart += mOrientationHelper.getStartAfterPadding();        // extraForEnd 再加上 mRecyclerView.getPaddingBottom()        extraForEnd += mOrientationHelper.getEndPadding();        // state.isPreLayout() 返回 false        if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION                && mPendingScrollPositionOffset != INVALID_OFFSET) {            // ...        }        int startOffset;        int endOffset;        final int firstLayoutDirection;        // mAnchorInfo.mLayoutFromEnd 为 false        if (mAnchorInfo.mLayoutFromEnd) {            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL                    : LayoutState.ITEM_DIRECTION_HEAD;        } else {            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD                    : LayoutState.ITEM_DIRECTION_TAIL;        }        // LinearLayoutManager 中,为空方法        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);        // 还没有 children,不做处理        detachAndScrapAttachedViews(recycler);        // resolveIsInfinite() 为 false        mLayoutState.mInfinite = resolveIsInfinite();        mLayoutState.mIsPreLayout = state.isPreLayout();        // mAnchorInfo.mLayoutFromEnd 默认为 false        if (mAnchorInfo.mLayoutFromEnd) {            // ...        } else {            // fill towards end            // 根据 mAnchorInfo 更新 mLayoutState            updateLayoutStateToFillEnd(mAnchorInfo);            // 前面说过 extraForEnd 等于额外的空间(这里为0)加上recyclerview的paddingBottom            mLayoutState.mExtra = extraForEnd;            fill(recycler, mLayoutState, state, false);            endOffset = mLayoutState.mOffset;            final int lastElement = mLayoutState.mCurrentPosition;            if (mLayoutState.mAvailable > 0) {                extraForStart += mLayoutState.mAvailable;            }            // fill towards start            updateLayoutStateToFillStart(mAnchorInfo);            mLayoutState.mExtra = extraForStart;            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;            fill(recycler, mLayoutState, state, false);            startOffset = mLayoutState.mOffset;            if (mLayoutState.mAvailable > 0) {                extraForEnd = mLayoutState.mAvailable;                // start could not consume all it should. add more items towards end                updateLayoutStateToFillEnd(lastElement, endOffset);                mLayoutState.mExtra = extraForEnd;                fill(recycler, mLayoutState, state, false);                endOffset = mLayoutState.mOffset;            }        }        // changes may cause gaps on the UI, try to fix them.        // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have        // changed        if (getChildCount() > 0) {            // because layout from end may be changed by scroll to position            // we re-calculate it.            // find which side we should check for gaps.            if (mShouldReverseLayout ^ mStackFromEnd) {                int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);                startOffset += fixOffset;                endOffset += fixOffset;                fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);                startOffset += fixOffset;                endOffset += fixOffset;            } else {                int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);                startOffset += fixOffset;                endOffset += fixOffset;                fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);                startOffset += fixOffset;                endOffset += fixOffset;            }        }        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);        if (!state.isPreLayout()) {            mOrientationHelper.onLayoutComplete();        } else {            mAnchorInfo.reset();        }        mLastStackFromEnd = mStackFromEnd;        if (DEBUG) {            validateChildOrder();        }    }

这个方法内部写明了步骤,但是代码很长,那现在来一步步抽丝剥茧。

ensureLayoutState() 是为了确保 mLayoutStatemOrientationHelper 被初始化。

    void ensureLayoutState() {        if (mLayoutState == null) {            // 只是 new LayoutState()            mLayoutState = createLayoutState();        }        if (mOrientationHelper == null) {            mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);        }    }

OrientationHelper.createOrientationHelper() 方法如下

    public static OrientationHelper createOrientationHelper(            RecyclerView.LayoutManager layoutManager, int orientation) {        switch (orientation) {            case HORIZONTAL:                return createHorizontalHelper(layoutManager);            case VERTICAL:                return createVerticalHelper(layoutManager);        }        throw new IllegalArgumentException("invalid orientation");    }

很明显,调用的是createVerticalHelper()方法,这个方法很长,但是我这里还是要把代码贴出来,方便后面查询。

   public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {        return new OrientationHelper(layoutManager) {            @Override            public int getEndAfterPadding() {                return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();            }            @Override            public int getEnd() {                return mLayoutManager.getHeight();            }            @Override            public void offsetChildren(int amount) {                mLayoutManager.offsetChildrenVertical(amount);            }            @Override            public int getStartAfterPadding() {                return mLayoutManager.getPaddingTop();            }            @Override            public int getDecoratedMeasurement(View view) {                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                        view.getLayoutParams();                return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin                        + params.bottomMargin;            }            @Override            public int getDecoratedMeasurementInOther(View view) {                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                        view.getLayoutParams();                return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin                        + params.rightMargin;            }            @Override            public int getDecoratedEnd(View view) {                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                        view.getLayoutParams();                return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;            }            @Override            public int getDecoratedStart(View view) {                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                        view.getLayoutParams();                return mLayoutManager.getDecoratedTop(view) - params.topMargin;            }            @Override            public int getTransformedEndWithDecoration(View view) {                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);                return mTmpRect.bottom;            }            @Override            public int getTransformedStartWithDecoration(View view) {                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);                return mTmpRect.top;            }            @Override            public int getTotalSpace() {                return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()                        - mLayoutManager.getPaddingBottom();            }            @Override            public void offsetChild(View view, int offset) {                view.offsetTopAndBottom(offset);            }            @Override            public int getEndPadding() {                return mLayoutManager.getPaddingBottom();            }            @Override            public int getMode() {                return mLayoutManager.getHeightMode();            }            @Override            public int getModeInOther() {                return mLayoutManager.getWidthMode();            }        };    }

再接着看 LinearLayoutManageronLayoutChildren() 方法中的 resolveShouldLayoutReverse() 方法

    private void resolveShouldLayoutReverse() {        // A == B is the same result, but we rather keep it readable        if (mOrientation == VERTICAL || !isLayoutRTL()) {            mShouldReverseLayout = mReverseLayout;        } else {            mShouldReverseLayout = !mReverseLayout;        }    }

mReverseLayout 是在 LinearLayoutManager 的构造函数中初始化的,之前的分析结果是 false,所以 mShouldReverseLayout 也为 false

再接着看下 LinearLayoutManageronLayoutChildren() 方法的下面代码,用来寻找绘制的锚点的信息 — 位置和坐标。

        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION                || mPendingSavedState != null) {            mAnchorInfo.reset();            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;            // calculate anchor position and coordinate            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);            mAnchorInfo.mValid = true;        }

变量 mAnchorInfo 在声明的时候就已经 new 了一个对象赋值给它了,所有的成员变量都是默认值。

mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd 这行代码中,mShouldReverseLayout 前面刚提过是false,而 mStackFromEnd 默认是 false,所以 mAnchorInfo.mLayoutFromEndfalse

updateAnchorInfoForLayout(recycler, state, mAnchorInfo) 用来计算锚点的位置和坐标,代码如下

    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,            AnchorInfo anchorInfo) {        if (updateAnchorFromPendingData(state, anchorInfo)) {            return;        }        if (updateAnchorFromChildren(recycler, state, anchorInfo)) {            return;        }        anchorInfo.assignCoordinateFromPadding();        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;    }

前两个 if 语句,因为条件不够,并没有做任何事情,这里跳过分析。

anchorInfo.assignCoordinateFromPadding() 这个是对 变量 mAnchroInfo 的成员变量 mCorrdinate 赋值

        void assignCoordinateFromPadding() {            mCoordinate = mLayoutFromEnd                    ? mOrientationHelper.getEndAfterPadding()                    : mOrientationHelper.getStartAfterPadding();        }

mLayoutFromEnd 的值是 false,那么 mCoordinate 的值实际就是 mOrientationHelper.getStartAfterPadding(), 实际就是调用 mLayoutManager.getPaddingTop(),最终调用的就是 mRecyclerView.getPaddingTop(),所以 anchorInfo.mCoordinate 其实就是等于 mRecyclerView.getPaddingTop()

anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; 这一行,因为 mStackFromEndfalse,导致 mAnchorInfomPosition0

现在再来看看 onLayoutChidren() 的 下面代码

        // LLM may decide to layout items for "extra" pixels to account for scrolling target,        // caching or predictive animations.        int extraForStart;        int extraForEnd;        final int extra = getExtraLayoutSpace(state);        // If the previous scroll delta was less than zero, the extra space should be laid out        // at the start. Otherwise, it should be at the end.        if (mLayoutState.mLastScrollDelta >= 0) {            extraForEnd = extra;            extraForStart = 0;        } else {            extraForStart = extra;            extraForEnd = 0;        }        extraForStart += mOrientationHelper.getStartAfterPadding();        extraForEnd += mOrientationHelper.getEndPadding();

由于我们现在的分析还没有涉及到滚动或者动画,因此 final int extra = getExtraLayoutSpace(state); 的结果就是 extra0mLayoutState.mLastScrollDelta 就是默认值 0,也就是第一个 if 语句中, extraForEnd0extraStart0.

最后两行,还对 extraForStartextraForEnd 进行计算,这里调用的就是 mOrientationHelper 的方法,具体两个调用如下

            // mOrientationHelper 的两个方法            @Override            public int getStartAfterPadding() {                return mLayoutManager.getPaddingTop();            }            @Override            public int getEndPadding() {                return mLayoutManager.getPaddingBottom();            }

实际上都是调用 RecylerView 的相应的方法,那么此时

  1. extraForStart 值就为 0 + mRecyclerView.getPaddingTop()
  2. extraForEnd 值就是 0 + mRecyclerView.getPaddingBottom()

现在再来看下 LinearLayoutManageronLayoutChildren() 的下面代码,确定 layout 的方向

        final int firstLayoutDirection;        if (mAnchorInfo.mLayoutFromEnd) {            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL                    : LayoutState.ITEM_DIRECTION_HEAD;        } else {            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD                    : LayoutState.ITEM_DIRECTION_TAIL;        }

mAnchroInformLayoutFromEnd 的值是 false,而且 mShouldReverseLayout 也是 false,所以 firstLayoutDirenction 的值为 LayoutState.ITEM_DIRECTION_TAIL,也就是从头到尾的方向,也就是从手机屏幕上面到下面。

现在再来看下 LinearLayoutManageronLayoutChildren() 的下面代码

        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);        detachAndScrapAttachedViews(recycler);        mLayoutState.mInfinite = resolveIsInfinite();        mLayoutState.mIsPreLayout = state.isPreLayout();        if (mAnchorInfo.mLayoutFromEnd) {            // fill towards start            // ... 这次真的省略了一万行        } else {            // fill towards end            updateLayoutStateToFillEnd(mAnchorInfo);            mLayoutState.mExtra = extraForEnd;            fill(recycler, mLayoutState, state, false);            endOffset = mLayoutState.mOffset;            final int lastElement = mLayoutState.mCurrentPosition;            if (mLayoutState.mAvailable > 0) {                extraForStart += mLayoutState.mAvailable;            }            // fill towards start            updateLayoutStateToFillStart(mAnchorInfo);            mLayoutState.mExtra = extraForStart;            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;            fill(recycler, mLayoutState, state, false);            startOffset = mLayoutState.mOffset;            if (mLayoutState.mAvailable > 0) {                extraForEnd = mLayoutState.mAvailable;                // start could not consume all it should. add more items towards end                updateLayoutStateToFillEnd(lastElement, endOffset);                mLayoutState.mExtra = extraForEnd;                fill(recycler, mLayoutState, state, false);                endOffset = mLayoutState.mOffset;            }        }

onAnchorReady()LinearLayoutManger 中是一个空方法。

detachAndScrapAttachedViews(recycler) 由于 RecyclerfView 还没有添加 View,因此做了不什么动作。

mLayoutState.mInfinite = resolveIsInfinite(); 赋值为 false,这是与 mOrentationHelper 变量的方法有关,大家可以自己去查下。

mLayoutState.mIsPreLayout = state.isPreLayout(); 赋值也为 false,在前面有说过。

那么现在进入到 else 的循环体中,首先我们应该看的就是 updateLayoutStateToFillEnd(mAnchorInfo) 更新 mLayoutState 的状态,用来后面填充 view 使用

    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {        updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);    }    private void updateLayoutStateToFillEnd(int itemPosition, int offset) {        // RecyclerVie 除去上下padding后的高度        mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :                LayoutState.ITEM_DIRECTION_TAIL;        mLayoutState.mCurrentPosition = itemPosition;        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;        mLayoutState.mOffset = offset;        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;    }

anchorInfo.mPostion 值为0anchorInfo.mCoordinate 值为 mRecyclerView.getPaddingTop(),这两个值前面说过。

mLayoutState 变量的赋值,我这里一起列举下

  1. mLayoutState.Available 的值为 RecyerlView.getHeight - RecyclerView.getPaddingTop() - RecyclerView.getPaddingBottom()
  2. mLayoutState.mItemDirection 的值为 LayoutState.ITEM_DIRECTION_TAIL
  3. mLayoutState.mLayoutDirection 的值为 LayoutState.LAYOUT_END
  4. mLayoutState.mOffset 值为 anchor.mCoordinate,也就是 RecyclerView.getPaddingTop()
  5. mLayoutState.mScrollingOffset 的值为 LayoutState.SCROLLING_OFFSET_NaN
  6. updateLayoutStateToFillEnd()方法之后,还有 mLayoutState.mExtra 的值为 extraForEnd,也就是前面说过的 RecyclerView.getPaddingBottom

现在继续看 onLayoutChildren()fill(recycler, mLayoutState, state, false),这就是真正的给 RecyclerView 填充 View 的地方。

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,            RecyclerView.State state, boolean stopOnFocusable) {        // max offset we should set is mFastScroll + available        final int start = layoutState.mAvailable;        // ...        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {            layoutChunkResult.resetInternal();            // ...            layoutChunk(recycler, state, layoutState, layoutChunkResult);            if (VERBOSE_TRACING) {                TraceCompat.endSection();            }            if (layoutChunkResult.mFinished) {                break;            }            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;            /**             * Consume the available space if:             * * layoutChunk did not request to be ignored             * * OR we are laying out scrap children             * * OR we are not doing pre-layout             */            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null                    || !state.isPreLayout()) {                layoutState.mAvailable -= layoutChunkResult.mConsumed;                // we keep a separate remaining space because mAvailable is important for recycling                remainingSpace -= layoutChunkResult.mConsumed;            }        // ...        return start - layoutState.mAvailable;    }

前面已经对 mLayoutState 的各种变量进行了赋值,这里应该就不难了。不过,int remainingSpace = layoutState.mAvailable + layoutState.mExtra; 这行代码,计算出来的 remainingSpace 的大小为 mRecyclerView.getHeight - mRecyclerView.getPaddingTop(),why? 看后面分析,这里先留个神。

看看 while 循环的条件

  1. layoutState.mInfinite 是为 false
  2. remainingSpace 现在肯定大于 0,后面会逐步减少
  3. layoutState.hasMore(state) 根据 mCurrentPosition(当前为0)来判断 Adapter 中是否有足够的数据,代码如下
        // LayoutState 的 hasMore() 方法        boolean hasMore(RecyclerView.State state) {            return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();        }      // RecyclerView.State 的 getItemCount() 方法      public int getItemCount() {            return mInPreLayout                    ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout)                    : mItemCount;        }        

可以看到,hasMore() 方法的实际逻辑就是 mCurrentPosition >= 0 && mCurrentPosition < mAdapter.getItemCount()

ok,循环条件已经看完,直接进入 layoutChunk() 方法

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,            LayoutState layoutState, LayoutChunkResult result) {        View view = layoutState.next(recycler);        if (view == null) {            if (DEBUG && layoutState.mScrapList == null) {                throw new RuntimeException("received null view when unexpected");            }            // if we are laying out views in scrap, this may return null which means there is            // no more items to layout.            result.mFinished = true;            return;        }        LayoutParams params = (LayoutParams) view.getLayoutParams();        if (layoutState.mScrapList == null) {            // layoutState.mLayoutDirection 为 LAYOUT_END            if (mShouldReverseLayout == (layoutState.mLayoutDirection                    == LayoutState.LAYOUT_START)) {                addView(view);            } else {                // 添加 view 到 Recyclerview                addView(view, 0);            }        } else {            if (mShouldReverseLayout == (layoutState.mLayoutDirection                    == LayoutState.LAYOUT_START)) {                addDisappearingView(view);            } else {                addDisappearingView(view, 0);            }        }        measureChildWithMargins(view, 0, 0);        // child.getMeasuredHeight() + mDecorInsets.top + mDecorInsets.bottom + mRecyclerView.topMargin + mRecyclerview.bottomMargin        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);        // 计算 view 的 left, top, right, bottom        // 把 child 的4个margin,ItemDecoration 的 4 个 margin 都计算在内        int left, top, right, bottom;        if (mOrientation == VERTICAL) {            if (isLayoutRTL()) {                right = getWidth() - getPaddingRight();                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);            } else {                left = getPaddingLeft();                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);            }            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {                bottom = layoutState.mOffset;                top = layoutState.mOffset - result.mConsumed;            } else {                top = layoutState.mOffset;                bottom = layoutState.mOffset + result.mConsumed;            }        } else {            top = getPaddingTop();            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {                right = layoutState.mOffset;                left = layoutState.mOffset - result.mConsumed;            } else {                left = layoutState.mOffset;                right = layoutState.mOffset + result.mConsumed;            }        }        // We calculate everything with View's bounding box (which includes decor and margins)        // To calculate correct layout position, we subtract margins.        // 对 view 进行 layout,详细见下面        layoutDecoratedWithMargins(view, left, top, right, bottom);        if (DEBUG) {            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));        }        // Consume the available space if the view is not removed OR changed        if (params.isItemRemoved() || params.isItemChanged()) {            result.mIgnoreConsumed = true;        }        result.mFocusable = view.hasFocusable();    }

首先用 layoutState.next(recycler) 获取下一个 View

        View next(RecyclerView.Recycler recycler) {            // STEP 1: 从 LinearLayoutManager 的 mScrapList 中获取            if (mScrapList != null) {                return nextViewFromScrapList();            }            // STEP2: 从 mRecycler 中获取            final View view = recycler.getViewForPosition(mCurrentPosition);            // STEP3: 更新 mCurrentPosition            mCurrentPosition += mItemDirection;            return view;        }

我把 next() 方法分为了三步,前两步是获取下一个 View,第三步是根据 mItemDirection 更新 mCurrentPosition.

先看看第三步,mItemDirection 之前已经赋值了,为 LayoutState.ITEM_DIRECTION_TAIL ,而这个值是 1,所以 mCurrentPosition 就 +1。 因为从头部往尾部绘制,+1 理所当然。

第一步是从 LinearLayoutManagerList<RecyclerView.ViewHolder> mScrapList 中获取,mScrapList 的默认值为 null,因此刚开始布局的话,应该是获取不到的。

第二步就是从 mRecycler 中获取,这段代码很长,我精简了下代码,主要是看到获取的几个路径,本文主要内容不在于缓存机制,所以不做分析。

        /**         * Obtain a view initialized for the given position.         *         * This method should be used by {@link LayoutManager} implementations to obtain         * views to represent data from an {@link Adapter}.         * <p>         * The Recycler may reuse a scrap or detached view from a shared pool if one is         * available for the correct view type. If the adapter has not indicated that the         * data at the given position has changed, the Recycler will attempt to hand back         * a scrap view that was previously initialized for that data without rebinding.         */        public View getViewForPosition(int position) {            return getViewForPosition(position, false);        }        View getViewForPosition(int position, boolean dryRun) {            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;        }        /**         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,         * cache, the RecycledViewPool, or creating it directly.         * <p>         * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return         * rather than constructing or binding a ViewHolder if it doesn't think it has time.         * If a ViewHolder must be constructed and not enough time remains, null is returned. If a         * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is         * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.         */        @Nullable        ViewHolder tryGetViewHolderForPositionByDeadline(int position,                boolean dryRun, long deadlineNs) {            if (position < 0 || position >= mState.getItemCount()) {                throw new IndexOutOfBoundsException("Invalid item position " + position                        + "(" + position + "). Item count:" + mState.getItemCount()                        + exceptionLabel());            }            boolean fromScrapOrHiddenOrCache = false;            ViewHolder holder = null;            // 0) If there is a changed scrap, try to find from there            if (mState.isPreLayout()) {                holder = getChangedScrapViewForPosition(position);                fromScrapOrHiddenOrCache = holder != null;            }            // 1) Find by position from scrap/hidden list/cache            if (holder == null) {                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);                // ...            }            if (holder == null) {                final int offsetPosition = mAdapterHelper.findPositionOffset(position);                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "                            + "position " + position + "(offset:" + offsetPosition + ")."                            + "state:" + mState.getItemCount() + exceptionLabel());                }                final int type = mAdapter.getItemViewType(offsetPosition);                // 2) Find from scrap/cache via stable ids, if exists                if (mAdapter.hasStableIds()) {                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),                            type, dryRun);                    // ...                }                if (holder == null && mViewCacheExtension != null) {                    // We are NOT sending the offsetPosition because LayoutManager does not                    // know it.                    final View view = mViewCacheExtension                            .getViewForPositionAndType(this, position, type);                    // ...                }                if (holder == null) { // fallback to pool                    if (DEBUG) {                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline("                                + position + ") fetching from shared pool");                    }                    holder = getRecycledViewPool().getRecycledView(type);                    // ...                }                if (holder == null) {                    long start = getNanoTime();                    if (deadlineNs != FOREVER_NS                            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {                        // abort - we have a deadline we can't meet                        return null;                    }                    holder = mAdapter.createViewHolder(RecyclerView.this, type);                    if (ALLOW_THREAD_GAP_WORK) {                        // only bother finding nested RV if prefetching                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);                        if (innerView != null) {                            holder.mNestedRecyclerView = new WeakReference<>(innerView);                        }                    }                    long end = getNanoTime();                    mRecyclerPool.factorInCreateTime(type, end - start);                    if (DEBUG) {                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");                    }                }            }            // ...            boolean bound = false;            if (mState.isPreLayout() && holder.isBound()) {                // do not update unless we absolutely have to.                holder.mPreLayoutPosition = position;            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {                if (DEBUG && holder.isRemoved()) {                    throw new IllegalStateException("Removed holder should be bound and it should"                            + " come here only in pre-layout. Holder: " + holder                            + exceptionLabel());                }                final int offsetPosition = mAdapterHelper.findPositionOffset(position);                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);            }                        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();            final LayoutParams rvLayoutParams;            if (lp == null) {                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();                holder.itemView.setLayoutParams(rvLayoutParams);            } else if (!checkLayoutParams(lp)) {                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);                holder.itemView.setLayoutParams(rvLayoutParams);            } else {                rvLayoutParams = (LayoutParams) lp;            }            rvLayoutParams.mViewHolder = holder;            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;            return holder;        }

代码注释我并没有省略,可能刚开始看很难理解,但是只要你分析完代码,再看注释,就会有种恍然大悟的感觉。

代码我做了精简,可以看到有好多个地方获取 ViewHolder。 由于本文分析的情况是刚开始加载,所以需要用 Adapter 来创建,也就是 holder = mAdapter.createViewHolder(RecyclerView.this, type); 这一行代码。

        public final VH createViewHolder(ViewGroup parent, int viewType) {            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);            final VH holder = onCreateViewHolder(parent, viewType);            holder.mItemViewType = viewType;            TraceCompat.endSection();            return holder;        }

LayoutManagercreateViewHolder() 方法只做了两件事

  1. 调用实现类的 onCreateViewHolder() 方法
  2. 设置 holder.mItemViewType 的值为参数 viewType

创建了 ViewHolder 了后,就需要绑定,也就是 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); 这行代码进行绑定,看下这个源码

        private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,                int position, long deadlineNs) {            // ...            mAdapter.bindViewHolder(holder, offsetPosition);            // ...            return true;        }

精简代码后,就清晰了吧,就是调用了 AdapterbindViewHolder() 方法进行绑定。

        public final void bindViewHolder(VH holder, int position) {            holder.mPosition = position;            if (hasStableIds()) {                holder.mItemId = getItemId(position);            }            holder.setFlags(ViewHolder.FLAG_BOUND,                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);            TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());            holder.clearPayload();            final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();            if (layoutParams instanceof RecyclerView.LayoutParams) {                ((LayoutParams) layoutParams).mInsetsDirty = true;            }            TraceCompat.endSection();        }

LayoutManagerbindViewHolder 做了不少事情。

  1. holer.mPosition 赋值为参数position。 如果要获取一个 ViewAdapter 中的位置的时候,这个参数就起了重要作用。
  2. 如果 Adapter 复写了 hasStableIds() 方法,并返回了 true,就会给 holder.mItemId 赋值为 getItemId() 返回的值,而这个 getItemId() 也可能要复写。
  3. 调用 holder.setFlag()ViewHolder 设置标志位为 FLAG_BOUND
  4. 调用 onBindViewHolder() 方法
  5. ItemViewLayoutParams 参数的变量 mInsetsDirty 值为 true。代表需要布局参数更新,需要重新测量。

绑定完了之后还没有完,需要给 ViewHolderitemView 设置 LayoutParams 参数。那么,再回到 tryGetViewHolderForPositionByDeadline() 方法的的下面片段

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();            final LayoutParams rvLayoutParams;            if (lp == null) {                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();                holder.itemView.setLayoutParams(rvLayoutParams);            } else if (!checkLayoutParams(lp)) {                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);                holder.itemView.setLayoutParams(rvLayoutParams);            } else {                rvLayoutParams = (LayoutParams) lp;            }            rvLayoutParams.mViewHolder = holder;            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;

由于在 AdapteronCrateViewHolder() 方法中,用 LayoutInflater.from() 创建 View 的时候,会传入一个 root 参数,也就是 onCreateViewHolder() 中的 ViewGroup parent 参数。 因此这里获取到的 ViewGroup.LayoutParams 不为 null

那么,就走到了 checkLayoutParams(lp)

    @Override    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {        return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);    }    public boolean checkLayoutParams(LayoutParams lp) {        return lp != null;    }

很显然,checkLayoutParams(lp) 返回 true,那么就执行了 rvLayoutParams = (LayoutParams) lp; 这一行代码了。

再回到 layoutChunck() 方法,此时通过 View view = layoutState.next(recycler); 已经获取了一个 ViewHolder.itemView,接下来就是把这个 View 添加到 RecyclerView 中,代码片段如下

        if (layoutState.mScrapList == null) {            if (mShouldReverseLayout == (layoutState.mLayoutDirection                    == LayoutState.LAYOUT_START)) {                addView(view);            } else {                addView(view, 0);            }        }

由于此时的 layoutState.mLayoutDirectionLayoutSate.LAYOUT_END,所以调用了 LayoutManageraddView(view);方法,而这个方法最终是调用了 RecyclerViewaddView(child, index) 方法,这里就不深究了。

LinearLayoutManagerlayoutChunck() 现在获取了 View ,又把这个 View 添加到了 RecyclerView 中,接下来,就要测量这个 View了,也就是 measureChildWithMargins(view, 0, 0); 这行代码。

        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);            widthUsed += insets.left + insets.right;            heightUsed += insets.top + insets.bottom;            // 下面两个计算宽高的规格            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),                    getPaddingLeft() + getPaddingRight()                            + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,                    canScrollHorizontally());            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),                    getPaddingTop() + getPaddingBottom()                            + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,                    canScrollVertically());            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {                // 让 child 自己去测量                child.measure(widthSpec, heightSpec);            }        }

final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 这段代码获取的是一个增加了 ItemDecorationRect

    Rect getItemDecorInsetsForChild(View child) {        final LayoutParams lp = (LayoutParams) child.getLayoutParams();        if (!lp.mInsetsDirty) {            return lp.mDecorInsets;        }        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {            // changed/invalid items should not be updated until they are rebound.            return lp.mDecorInsets;        }        final Rect insets = lp.mDecorInsets;        insets.set(0, 0, 0, 0);        final int decorCount = mItemDecorations.size();        for (int i = 0; i < decorCount; i++) {            mTempRect.set(0, 0, 0, 0);            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);            insets.left += mTempRect.left;            insets.top += mTempRect.top;            insets.right += mTempRect.right;            insets.bottom += mTempRect.bottom;        }        lp.mInsetsDirty = false;        return insets;    }

可以看到创建的 insets 刚开始把四个坐标都设置为了 0, 然后调用 ItemDecorationgetItemOffsets() 方法给 mTempRect 填充值,最后会惊奇的发现,insets 把所有 mTempRect 的四个坐标值分别累加起来了。 非常有意思! 那这到底什么意思呢?往下看

            widthUsed += insets.left + insets.right;            heightUsed += insets.top + insets.bottom;

这两行,计算了已经使用的宽度和高度。 纳尼!所以从 ItemDecoration 返回的 mTempRect 坐标累加的结果居然是已经使用的宽度和高度的意思!很明显,这就相当于一个 padding。所以添加到 RecyclerView 中的 View 需要考虑这个 padding,因为它们用来绘制 ItemDecoration

计算完已经使用的宽高后,就需要来计算 View 的宽高的 sizemode 了,也就是 measureChildWithMargins() 中的如下代码

final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),                    getPaddingLeft() + getPaddingRight()                            + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,                    canScrollHorizontally());            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),                    getPaddingTop() + getPaddingBottom()                            + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,                    canScrollVertically());

由于 LinearLayoutManager 中的 mOrientationVERTICAL,所以 canScrollVertically()true,而 canScrollHorizontally()false

而测量的策略采用的是 LayoutManager 中的 getChildMeasureSpec(),代码如下

        public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,                int childDimension, boolean canScroll) {            int size = Math.max(0, parentSize - padding);            int resultSize = 0;            int resultMode = 0;            if (canScroll) { // 测量高度                if (childDimension >= 0) {                    resultSize = childDimension;                    resultMode = MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    switch (parentMode) {                        case MeasureSpec.AT_MOST:                        case MeasureSpec.EXACTLY:                            resultSize = size;                            resultMode = parentMode;                            break;                        case MeasureSpec.UNSPECIFIED:                            resultSize = 0;                            resultMode = MeasureSpec.UNSPECIFIED;                            break;                    }                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    resultSize = 0;                    resultMode = MeasureSpec.UNSPECIFIED;                }            } else { // 测量宽度                if (childDimension >= 0) {                     resultSize = childDimension;                    resultMode = MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    resultSize = size;                    resultMode = parentMode;                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    resultSize = size;                    if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {                        resultMode = MeasureSpec.AT_MOST;                    } else {                        resultMode = MeasureSpec.UNSPECIFIED;                    }                }            }            //noinspection WrongConstant            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);        }

getChildMeasureSpec() 的第三个参数,因为我们设置的方向是垂直滚动,所以在测量高度的时候,第三个参数是 true,而测量宽度的时候是 false。最后通过 MeasureSpec.makeMeasureSpec() 生成一个 int 值来存储 sizemode

完成了这些之后就需要让 child 来完成测量,也就是 measureChildWithMargins() 方法中的如下片段

        if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {            child.measure(widthSpec, heightSpec);        }

shouldMeasureChild() 方法决定了是否需要测量,其中一个决定性的因素就是 child 在 XML 设置的宽高和实际设置测量的宽高是否相符,不然就需要 child 自己测量。代码逻辑如下:

        boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {            return child.isLayoutRequested()                    || !mMeasurementCacheEnabled                    || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)                    || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);        }        private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {            final int specMode = MeasureSpec.getMode(spec);            final int specSize = MeasureSpec.getSize(spec);            if (dimension > 0 && childSize != dimension) {                return false;            }            switch (specMode) {                case MeasureSpec.UNSPECIFIED:                    return true;                case MeasureSpec.AT_MOST:                    return specSize >= childSize;                case MeasureSpec.EXACTLY:                    return  specSize == childSize;            }            return false;        } 

现在还没有对获取的 View 进行 layout,因此 shouldMeasureChild() 应该是返回 true,也就是需要测量。所以会调用 child.measure(widthSpec, heightSpec); 来让 View 完成测量。

Viewmeasure 完成了,接下来就是 layout

layoutChunck() 方法首先执行 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); 来完成高度的测量,包括 ItemDecoration 提供的所谓的 “padding”,以及 View 自身的 topMarginbottomMargin

        // mOrientationHelper        public int getDecoratedMeasurement(View view) {            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                    view.getLayoutParams();            return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin                    + params.bottomMargin;        }        // LayoutManager.java        public int getDecoratedMeasuredHeight(View child) {            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;            return child.getMeasuredHeight() + insets.top + insets.bottom;        }            

这段代码很简单,result.mConsumed 的值为 child.getMeasuredHeight() + insets.top + insets.bottom + params.topMargin + params.bottomMargin

然后计算 left, top, right, bottom 的值,代码片段如下

        int left, top, right, bottom;        if (mOrientation == VERTICAL) {            if (isLayoutRTL()) {                right = getWidth() - getPaddingRight();                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);            } else {                 // 大部分情况都是从左到右,                left = getPaddingLeft();                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);            }            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {                bottom = layoutState.mOffset;                top = layoutState.mOffset - result.mConsumed;            } else {                // layoutState.mLayoutDirection 值为 LayoutState.LAYOUT_END                top = layoutState.mOffset;                bottom = layoutState.mOffset + result.mConsumed;            }        }         // mOrientationHelper        @Override        public int getDecoratedMeasurementInOther(View view) {            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)                    view.getLayoutParams();            return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin                    + params.rightMargin;        }          // LayoutManager.java        public int getDecoratedMeasuredWidth(View child) {            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;            return child.getMeasuredWidth() + insets.left + insets.right;        }              

这段代码就不多做解释了, 计算后的 left, top, right, bottom 如下

  1. left = mRecyclerView.getPaddingLeft()
  2. right = left + child.getMeasuredWidth() + insets.left + insets.right + params.leftMargin + params.rightMargin
  3. top = layoutState.mOffset,也就是 mRecyclerView.getPaddingTop()
  4. bottom = mRecyclerView.getPaddingTop() + result.mConsumed。 result.mConsumed 刚才已经计算出来了。。

有了 left, top, right, bottom 后,layoutChunck() 方法就调用 layoutDecoratedWithMargins(view, left, top, right, bottom);layout 这个 View

        public void layoutDecoratedWithMargins(View child, int left, int top, int right,                int bottom) {            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            final Rect insets = lp.mDecorInsets;            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,                    right - insets.right - lp.rightMargin,                    bottom - insets.bottom - lp.bottomMargin);        }

这个就很直观了,不用解释了。

至此 layoutChunck() 方法已经分析完毕。那么,现在总结下 layoutChunck() 方法到底做了那些事情
1. get view
2. add view
3. measure view
4. layout view

这四步是不是看起来很舒服~

那么,现在再回到 fill() 的方法,我再做了精简

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,            RecyclerView.State state, boolean stopOnFocusable) {        // max offset we should set is mFastScroll + available        final int start = layoutState.mAvailable;        // ...        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {            // ...            layoutChunk(recycler, state, layoutState, layoutChunkResult);            // ...            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null                    || !state.isPreLayout()) {                layoutState.mAvailable -= layoutChunkResult.mConsumed;                // we keep a separate remaining space because mAvailable is important for recycling                remainingSpace -= layoutChunkResult.mConsumed;            }            // ...        }        // ...        return start - layoutState.mAvailable;    }

直接看 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; 这行代码,layoutState.mOffset 的值是 mRecyclerView.getPaddingTop()layoutChunkResult.mConsumed 的值是之前已经 measurelayout 过的 View 的高度(包括 marginItemDecorationpadding),layoutState.mLayoutDirection 的值是 LaoutState.LAYOUT_END,也就是 1 。那么 layoutState.mOffset 的值就已经很明显了,下一个Viewmeasurelayout 的起始位置,为下一次循环调用 layoutChunck() 做准备。

然后,layoutState.mAvailable 要减去这个 layout 过的 View 的高度(包括 marginItemDecorationpadding),也就是 layoutState.mAvailable -= layoutChunkResult.mConsumed;.

再然后,remainingSpace 也要做一样的动作,也就是 remainingSpace -= layoutChunkResult.mConsumed;

这一切准备完成后,就进行下一次的 while 循环了,直到 remainingSpace 小于等于 0,或者 mCurrentPosition 大于等于 mAdapter.getItemCount()

int remainingSpace = layoutState.mAvailable + layoutState.mExtra; 这一行可以看出来,除去整个界面显示的 所有 View,还至少额外多加载了一个 View,当然前提是 AdaptergetItemCount() 返回的总数要足够。

第一个 View 的分析就完毕了,然后通过 while 循环来做相同的操作,直到结束。 这样就把所有的 View 都添加到了 RecyclerView 中。

注意 fill() 方法的返回值 start - layoutState.mAvailable,实际上就是等于所有获取到的 View 的高度。不过我们暂时还用不到。

至此,fill() 方法已经分析完毕。

现在再返回到 onLayoutChildren() 方法,继续看下面片段代码

            // fill towards end            updateLayoutStateToFillEnd(mAnchorInfo);            mLayoutState.mExtra = extraForEnd;            fill(recycler, mLayoutState, state, false);            endOffset = mLayoutState.mOffset;            final int lastElement = mLayoutState.mCurrentPosition;            if (mLayoutState.mAvailable > 0) {                extraForStart += mLayoutState.mAvailable;            }            // fill towards start            updateLayoutStateToFillStart(mAnchorInfo);            mLayoutState.mExtra = extraForStart;            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;            fill(recycler, mLayoutState, state, false);            startOffset = mLayoutState.mOffset;            if (mLayoutState.mAvailable > 0) {                extraForEnd = mLayoutState.mAvailable;                // start could not consume all it should. add more items towards end                updateLayoutStateToFillEnd(lastElement, endOffset);                mLayoutState.mExtra = extraForEnd;                fill(recycler, mLayoutState, state, false);                endOffset = mLayoutState.mOffset;            }

从注释中看有两步,现在已经分析完了 “fill towards end” 这一步的 fill() 方法,接下来用 endOffset 保存了 mLayoutState.mOffset 的值,然后用 lastElement 保存了 mLayoutState.mCurrentPosition 的值。 最后还添加了一个 mLayoutState.mAvailable > 0 的条件,这个条件是在 Adapter 提供的数据不够的情况下才会成立,如果发生这种情况,就会执行 extraForStart += mLayoutState.mAvailable;

接下来就是 “fill towards start”,从底部往头部填充,这种情况并不适合现在分析的状况,所以这里就不分析了。

然后,onLayoutChildren() 剩下的代码与动画有关,这里就不分析了。

那么,至此,onLayoutChildren() 方法已经分析完毕。剩余的 dispatchLayoutStep2() 和 后面的 dispatchLayoutStep3() 方法就是与动画有关,这里也不分析了。

那么现在看 ReyclerView 的 draw 过程了。

draw()

    @Override    public void draw(Canvas c) {        super.draw(c);        final int count = mItemDecorations.size();        for (int i = 0; i < count; i++) {            mItemDecorations.get(i).onDrawOver(c, this, mState);        }        // ...    }

draw() 方法首先调用的是 super.draw(), 系统就会绘制一些背景,滚动条,等等。 然后系统会调用 onDraw() 方法,RecyclerView 复写了 onDraw() 方法

    @Override    public void onDraw(Canvas c) {        super.onDraw(c);        final int count = mItemDecorations.size();        for (int i = 0; i < count; i++) {            mItemDecorations.get(i).onDraw(c, this, mState);        }    }  

onDraw() 方法中,可以看到调用了 ItemDecoration 的 onDraw()方法进行绘制。

调用完 onDraw() 方法后,就再回到 draw() 方法,可以看到调用了 ItemDecoration 的 onDrawOver()方法。 由此可见,ItemDecoration 的绘制顺序是先调用 onDraw() 方法,再调用 onDrawOver() 方法。

总结

本文主要对 RecyclerView 第一次加载显示 View 的过程进行的简要分析,其中穿插了 ItemDecoration 的绘制。 但是并不包括动画,也不包括回收机制的分析。 那么,经历了这篇文章的分析,我们能做什么呢?可以绘制自己的 ItemDecoration ! 在下一篇,我将会举例分析如何绘制 ItemDecoration。 OK,这篇文章就到此为止了,如果有任何问题,欢迎提出。

原创粉丝点击