美文网首页源码解析相关recycleViewAndroid开发
【进阶】RecyclerView源码解析(一)——绘制流程

【进阶】RecyclerView源码解析(一)——绘制流程

作者: 被代码淹没的小伙子 | 来源:发表于2018-03-13 21:27 被阅读581次

    1.【进阶】RecyclerView源码解析(一)——绘制流程
    2.【进阶】RecyclerView源码解析(二)——缓存机制
    3.【进阶】RecyclerView源码解析(三)——深度解析缓存机制
    4.【进阶】RecyclerView源码解析(四)——RecyclerView进阶优化使用

    自从Google出了RecyclerView后,基本上列表的场景已经完全替代了原来的ListView和GridView,现在不仅仅是列表,多样式(俗称盖楼),复杂页面等,只要我们愿意,RecyclerView几乎可以代替实现80%的布局,GitHub可以发现各种各样给予RecyclerView的开源库,无论是Adapter还是LayoutManager等。阿里对应出的vlayout其实也是RecyclerView的深度拓展,所以最近感觉仅仅了解用法是不够的,使用RecyclerView越多,就越会发现这个组件的深度不能仅仅停留在使用层面,从RecyclerView可以延伸出一系列进阶的使用、拓展、优化、封装等,所以打算从源码角度来看一下RecyclerView的实现机制,更方便我们进行进阶学习使用RecyclerView。

    带着问题看源码

    最早的看源码可能带着一点强迫和模仿性质,大家都在看源码都在分析源码,虽然不知道看源码能干什么,但是就是看就够了,所以挑选的也是大家都在分析的Volley,所以前面看的几个框架的源码从Volley到最近的OkHttp的源码,总感觉是为了看源码而看源码,虽然最后看完后收获确实很多,但是感觉方式总是不是特别正确,这次的RecyclerView的分析想法却不是这样的,由于对RecyclerView使用的程度和频率越来越多,越来越多的不解和疑问让我想要去看一看RecyclerView的源码,总的疑惑有下面几个:

    • 1.首先没有见过特别详细的RecyclerView的源码分析系列,所有关于RecyclerView都是停留在使用或者少数进阶使用的博客

    • 2.RecyclerView,LayoutManager,Adapter,ViewHolder,ItemDecoration这些和RecycleView使用息息相关的类到底是什么关系

    • 3.RecyclerView作为列表,绘制流程到底什么样的

    • 4.RecyclerView有什么不常用的进阶使用方式,但是却很适合RecyclerView作为很“重”的组件的优化,像setRecyclerPool用处到底是什么

    • 5.大家都只要要使用RecyclerView替代ListView和GridView,好用,都在用,但是都没有追究到底这背后的原因到底是什么,RecyclerView到底比ListView好在哪里,到底该不该替换,性能到底提升多少。

    以上问题不一定看完源码都能解决(个人能力堪忧啊...),但是带着问题来看源码总会有不错的收获,或者大家有什么关于使用RecyclerView的学习博客链接,使用经验和体会都可以在下面评论供大家一起学习讨论

    思路

    当打开RecyclerVew的源码会发现非常的复杂,感觉无从下手,不会像Volley和OkHttp从发起开始到发起结束,目标性那么明确。那么既然RecyclerView继承的是ViewGroup,也就是说RecyclerView其实就是和LinearLayout等布局一样的一个自定义ViewGroup。
    既然涉及到自定义组件,那么当我们自己来实现一个自定义ViewGroup,最重要的步骤无非是下面几点:

    1. 重写onMeasure用于确定自定义ViewGroup的大小
    2. 重写onLayout用于布局子view的位置
      所以源码的分析也对应从这里开始进行分析。

    onMeasure

    和分析OkHttp的源码一样,这里就不放上RecyclerView中的onMeasure的所有源码了,那样只会增加我们的理解难度,所以同样选择利用伪代码进行分析。

    protected void onMeasure(int widthSpec, int heightSpec) {
            if (mLayout == null) {
                //layoutManager没有设置的话,直接走default的方法,所以会为空白
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
            if (mLayout.mAutoMeasure) {
                final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                        && heightMode == MeasureSpec.EXACTLY;
                //如果测量是绝对值,则跳过measure过程直接走layout
                if (skipMeasure || mAdapter == null) {
                    return;
                }
                if (mState.mLayoutStep == State.STEP_START) {
                    //mLayoutStep默认值是 State.STEP_START
                    dispatchLayoutStep1();
                    //执行完dispatchLayoutStep1()后是State.STEP_LAYOUT
                }
                 ..........
                //真正执行LayoutManager绘制的地方
                dispatchLayoutStep2();
                //执行完后是State.STEP_ANIMATIONS
                 ..........
                //宽高都不确定的时候,会绘制两次
                // if RecyclerView has non-exact width and height and if there is at least one child
                // which also has non-exact width & height, we have to re-measure.
                if (mLayout.shouldMeasureTwice()) {
                 ..........
                    dispatchLayoutStep2();
                 ..........            }
            } else {
                if (mHasFixedSize) {
                    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                    return;
                }
                 ..........
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                 ..........
                mState.mInPreLayout = false; // clear
            }
        }
    

    源码放上了,现在可以一步一步分析onMeasure的过程了,先从第一个小点开始吧。

    if (mLayout == null) {
                //layoutManager没有设置的话,直接走default的方法,所以会为空白
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
    

    这里的mLayout其实就是我们给RecyclerView设置的LayoutManager对象,这一段代码其实就很好的解释了为什么我们有时候初次使用RecyclerView的时候忘记设置LayoutManager后,RecyclerView会没有按照我们所想的那样显示出来。
    这里可以看到,如果mLayout为null的话,会走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);
        }
    

    可以看到这里的chooseSize方法其实就是更加宽高的Mode得到相应的值后直接调用setMeasuredDimension(width, height)设置宽高了,可以发现这里其实是没有进行child的测量就直接return结束了onMeasure过程的,这也就解释了为什么我们没有设置LayoutManager会导致显示空白了。
    接下来技术一个判断

      if (mLayout.mAutoMeasure) {
    }else{
    }
    
    

    这里其实mAutoMeasure这个值,LinearLayoutManager还是其他两个Manager,默认值都是true。所以接着往下看。

    final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                        && heightMode == MeasureSpec.EXACTLY;
                //如果测量是绝对值,则跳过measure过程直接走layout
                if (skipMeasure || mAdapter == null) {
                    return;
                }
    

    这里解释的很清楚,如果宽和高的测量值是绝对值时,直接跳过onMeasure方法。那这里可能有疑问了,如果没有执行onMeasure方法,那么子View没有绘制,会造成空白的情况,但是实际情况是当我们给RecyclerView设置绝对值大小的时候,子View仍可以正常绘制出来。这个问题后面会解答。(onLayout里会执行子View的绘制)

     if (mState.mLayoutStep == State.STEP_START) {
                    //mLayoutStep默认值是 State.STEP_START
                    dispatchLayoutStep1();
                    //执行完dispatchLayoutStep1()后是State.STEP_LAYOUT
                }
    

    接下来就要开始绘制的准备了,这里可以看到首先判断mLayoutStep,这里mLayoutStep的默认值其实就是 State.STEP_START,并且每次绘制流程结束后,会重置为 State.STEP_START。接下来执行dispatchLayoutStep1();方法dispatchLayoutStep1();其实没有必要过多分析,因为分析源码主要是对于绘制思想的理解,如果过多的纠结于每一行代码的含义,那么会陷入很大的困扰中。这里就放上官方对于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
         */
        /**
         * 1.处理Adapter的更新
         * 2.决定那些动画需要执行
         * 3.保存当前View的信息
         * 4.如果必要的话,执行上一个Layout的操作并且保存他的信息
         */
    

    接下来就是我们的真正执行LayoutManager绘制的地方dispatchLayoutStep2()。

    private void dispatchLayoutStep2() {
            ....
            //重写的getItemCount方法
            mState.mItemCount = mAdapter.getItemCount();
            ....
            // Step 2: Run layout
            mState.mInPreLayout = false;
            mLayout.onLayoutChildren(mRecycler, mState);
             ....
        }
    

    同样的,这里放上伪代码,便于理解。
    这里注意两个地方,第一个mAdapter.getItemCount(),可以看到我们每次重写Adapter时重写的方法getItemCount方法用到的地方了。
    第二个,可以看到mLayout.onLayoutChildren(mRecycler, mState);这个方法,为什么说RecyclerView将View的绘制交给了LayoutManager,这里就是最有力的体现,可以看到,这里将RecycleView内部持有的Recycler和state传给了LayoutManager的onLayoutChildren方法,单从方法的名字其实就可以看出。这里我们进入LayoutManager里看一看。(本次分析给予LinearLayoutManager)

    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
            ....
            // resolve layout direction
            //判断绘制方向,给mShouldReverseLayout赋值,默认是正向绘制,则mShouldReverseLayout是false
            resolveShouldLayoutReverse();
            final View focused = getFocusedChild();
            //mValid的默认值是false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false
            if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                    || mPendingSavedState != null) {
            ....
                //mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false
                mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
                // calculate anchor position and coordinate
                //计算锚点的位置和偏移量
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            ....
            } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
                    || mOrientationHelper.getDecoratedEnd(focused)
                    <= mOrientationHelper.getStartAfterPadding())) {
             ....
            }
             ....
            //mLayoutFromEnd为false
            if (mAnchorInfo.mLayoutFromEnd) {
                //倒着绘制的话,先往上绘制,再往下绘制
                // fill towards start
                // 从锚点到往上
                updateLayoutStateToFillStart(mAnchorInfo);
                ....
                fill(recycler, mLayoutState, state, false);
                ....
                // 从锚点到往下
                // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                ....
                //调两遍fill方法
                fill(recycler, mLayoutState, state, false);
                ....
                if (mLayoutState.mAvailable > 0) {
                    // end could not consume all. add more items towards start
                ....
                    updateLayoutStateToFillStart(firstElement, startOffset);
                    mLayoutState.mExtra = extraForStart;
                    fill(recycler, mLayoutState, state, false);
                 ....
                }
            } else {
                //正常绘制流程的话,先往下绘制,再往上绘制
                // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                ....
                fill(recycler, mLayoutState, state, false);
                 ....
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                ....
                fill(recycler, mLayoutState, state, false);
                 ....
                if (mLayoutState.mAvailable > 0) {
                    ....
                    // start could not consume all it should. add more items towards end
                    updateLayoutStateToFillEnd(lastElement, endOffset);
                     ....
                    fill(recycler, mLayoutState, state, false);
                    ....
                }
            }
            ....
            layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
            //完成后重置参数
            if (!state.isPreLayout()) {
                mOrientationHelper.onLayoutComplete();
            } else {
                mAnchorInfo.reset();
            }
            mLastStackFromEnd = mStackFromEnd;
        }
    

    这里虽然已经尽量删减了很多代码,但是还是很多...但是其实原理理解起来还是比较容易的。简单的说其实可以总结缩略为:

    先寻找页面当前的锚点
    以这个锚点未基准,向上和向下分别填充
    填充完后,如果还有剩余的可填充大小,再填充一次

    这样理解起来就比较容易了,接下来我们就一步一步来看一下源码。
    寻找锚点

            resolveShouldLayoutReverse();
            final View focused = getFocusedChild();
            //mValid的默认值是false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false
            if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                    || mPendingSavedState != null) {
            ....
                //mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false
                mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
                // calculate anchor position and coordinate
                //计算锚点的位置和偏移量
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            ....
            } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
                    || mOrientationHelper.getDecoratedEnd(focused)
                    <= mOrientationHelper.getStartAfterPadding())) {
             ....
            }
    
    

    首先执行的resolveShouldLayoutReverse();方法,从方法的命名上可以理解为是否需要倒着绘制

     //判断绘制方向,给mShouldReverseLayout赋值,默认是正向绘制,则mShouldReverseLayout是false
    private void resolveShouldLayoutReverse() {
            // A == B is the same result, but we rather keep it readable
            if (mOrientation == VERTICAL || !isLayoutRTL()) {
                //默认mReverseLayout是false,构造函数,可以通过setReverseLayout来设置
                mShouldReverseLayout = mReverseLayout;
            } else {
                mShouldReverseLayout = !mReverseLayout;
            }
        }
    

    可以看到这里我注释写的很清楚,如果我们没有手动调用setReverseLayout()方法,默认情况下,是不会倒着绘制的。
    接下来对于几个变量的注释这里解释一下。
    首先是mAnchorInfo.mValid,这里mAnchorInfo就是我们要的锚点。mValid的默认值是false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false
    再就是mAnchorInfo.mLayoutFromEnd。

    mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
    

    可以看到这里用到了位运算符^异或,前面已经分析了,mShouldReverseLayout默认是fasle的,mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false。

     updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
    

    接下来这个方法就是对于锚点位置的确定,里面其实就是对于当前状态的偏移量的计算,得出当前的锚点位置,具体比较复杂,这里就不做分析了。后面就是绘制的地方,主要就是两种方向,正向(先向上再向下),逆向(先向下再向上),所以这里我们就看平常的情况。

    {
                //正常绘制流程的话,先往下绘制,再往上绘制
                // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                ....
                fill(recycler, mLayoutState, state, false);
                 ....
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                ....
                fill(recycler, mLayoutState, state, false);
                 ....
                if (mLayoutState.mAvailable > 0) {
                    ....
                    // start could not consume all it should. add more items towards end
                    updateLayoutStateToFillEnd(lastElement, endOffset);
                     ....
                    fill(recycler, mLayoutState, state, false);
                    ....
                }
    
    

    可以看到这里有两种方法。
    1.updateLayoutStateToFill...()
    2.fill()
    第一个方法其实就是确定当前方向上锚点的相关的状态信息。
    这里最主要的就是第二个方法fill(),可以看到这里至少调用了两次fill()方法,当还有剩余可以绘制的时候会再调一次fill()方法。这也证明了我们的想法,是通过锚点分别向上和向下两次绘制。这里放上一张图便于理解

    测量流程
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                 RecyclerView.State state, boolean stopOnFocusable) {
                .....
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                             .....
            return start - layoutState.mAvailable;
        }
    

    这里fill其实最重要的就是看这里的layoutChunk(recycler, state, layoutState, layoutChunkResult)方法。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                         LayoutState layoutState, LayoutChunkResult result) {
            //next方法很重要
            View view = layoutState.next(recycler);
            if (view == null) {
                if (DEBUG && layoutState.mScrapList == null) {
                    throw new RuntimeException("received null view when unexpected");
                }
               ...
            }
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addDisappearingView(view);
                } else {
                    addDisappearingView(view, 0);
                }
            }
            //测量ChildView
            measureChildWithMargins(view, 0, 0);
            ......
            // We calculate everything with View's bounding box (which includes decor and margins)
            // To calculate correct layout position, we subtract margins.
            //layout Child
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            ......
        }
    

    这个方法其实我是不忍注释的,满满的干货啊,首先是我们的next方法,千万不要因为这个next短小不起眼,就认为不重要。我只能说这个方法很重要

    View next(RecyclerView.Recycler recycler) {
                //默认mScrapList=null,但是执行layoutForPredictiveAnimations方法的时候不会为空
                if (mScrapList != null) {
                    return nextViewFromScrapList();
                }
                //重要,从recycler获得View,mScrapList是被LayoutManager持有,recycler是被RecyclerView持有
                final View view = recycler.getViewForPosition(mCurrentPosition);
                mCurrentPosition += mItemDirection;
                return view;
            }
    

    第一个mSrapList其实默认是空的,只有执行layoutForPredictiveAnimations前不为空,执行完后又变为空,所以这里暂时不需要考虑。
    可以看到View view = recycler.getViewForPosition(mCurrentPosition)终于看到RecyclerView中缓存策略的身影,RecyclerView的缓存不会在这篇博客讲解,但是这里不得不让我们注意下面从流程上简单的看一下View view = recycler.getViewForPosition(mCurrentPosition);这个方法

    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.
             */
            /**
             * 注释写的很清楚,从Recycler的scrap,cache,RecyclerViewPool,或者直接create创建
             */
            @Nullable
            ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                    boolean dryRun, long deadlineNs) {
              //一堆判断之后,如果不成立
                holder = mAdapter.createViewHolder(RecyclerView.this, type);            
            }
    

    可以看到这里,getViewForPosition会调用tryGetViewHolderForPositionByDeadline方法,tryGetViewHolderForPositionByDeadline方法的注释写的很清楚从Recycler的scrap,cache,RecyclerViewPool,或者直接create创建,这里我们也看到了我们最熟悉的**mAdapter.createViewHolder(RecyclerView.this, type); **方法!
    关于RecyclerView的缓存策略不出意外应该会在下篇博客进行分析
    next()方法分析结束后,其实就比较容易了。

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

    剩下的就是RecyclerView的addView方法。添加完View后会调用

    //测量ChildView
            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)) {
                    //子View的测量
                    child.measure(widthSpec, heightSpec);
                }
            }
    
    

    从这个方法里我们看到了子View的测量,当然还有一个需要我们注意的地方那就是mRecyclerView.getItemDecorInsetsForChild(child)

    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);
                //getItemOffsets()实现分割线的回调方法!
                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;
        }
    

    其实可以看到这里在测量子View的时候是将我们实现自定义分割线重写的getItemOffsets方法。这里其实也就可以理解了自定义分割线的原理就是在子View的测量过程前给上下左右加上自定义分割线所对应设置给这个child的边距。
    测量完成后,紧接着就调用了layoutDecoratedWithMargins(view, left, top, right, bottom)对子View完成了layout。


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

    终于到此,我们对于onMeasure方法分析结束了,这里分析完成对于后面的onLayout的分析就比较简单了。

    onLayout

    @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
          ` ...
            dispatchLayout();
            ...
        }
    
    void dispatchLayout() {
             ....
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
             ...
                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.
             ...
                dispatchLayoutStep2();
            } else {
                // always make sure we sync them (to ensure mode is exact)
                mLayout.setExactMeasureSpecsFrom(this);
            }
            dispatchLayoutStep3();
        }
    

    这里的代码就比较好理解了,并且上面提到的问题也就迎刃而解了,当我们给RecyclerView设置固定的宽高的时候,onMeasure是直接跳过了执行,那么为什么子View仍然能绘制出来。
    这里可以看到,如果onMeasure没有执行,mState.mLayoutStep == State.STEP_START就成立,所以仍然会执行 dispatchLayoutStep1(), dispatchLayoutStep2();也就对应的会绘制子View。
    而后面的注释也比较清楚,由于我们在Layout的时候改变了宽高,也会导致dispatchLayoutStep2();,也就是子View的重新绘制。
    如果上面情况都没有,那么onLayout的作用就仅仅是dispatchLayoutStep3(),而 dispatchLayoutStep3()方法的作用除了重置一些参数,外还和执行动画有关。

    private void dispatchLayoutStep3() {
             //重置参数
            if (mState.mRunSimpleAnimations) {
                // Step 3: Find out where things are now, and process change animations.
                // traverse list in reverse because we may call animateChange in the loop which may
                // remove the target view holder.
                //需要动画的情况。找出ViewHolder现在的位置,并且处理改变动画。最后触发动画。
                }
                // Step 4: Process view info lists and trigger animations
                mViewInfoStore.process(mViewInfoProcessCallback);
            }
            //成功回调
        }
    

    到此!!!对于RecyclerView的绘制流程其实我们有了一个大体的了解,总结一下关键点:

    1.RecyclerView是将绘制流程交给LayoutManager处理,如果没有设置不会测量子View。
    2.绘制流程是区分正向绘制和倒置绘制。
    3.绘制是先确定锚点,然后向上绘制,向下绘制,fill()至少会执行两次,如果绘制完还有剩余空间,则会再执行一次fill()方法。
    4.LayoutManager获得View是从RecyclerView中的Recycler.next()方法获得,涉及到RecyclerView的缓存策略,如果缓存没有拿到,则走我们自己重写的onCreateView方法。
    5.如果RecyclerView宽高没有写死,onMeasure就会执行完子View的measure和Layout方法,onLayout仅仅是重置一些参数,如果写死,子View的measure和layout会延后到onLayout中执行。

    相关文章

      网友评论

      • 伟伟大王:你这篇算是写recyclerview源码里写的比较好的,条理比较清晰。
      • Bernardo_Silva:上面的人说看的很懵逼,我倒觉得写得挺好的,思路也比较清晰,RecyclerView源码分析里面写的很好的了
      • 冉桓彬:你的博客我看的很懵逼; 没准儿这也是喜欢的数量很多, 评论的数量很少的原因吧; 看了前言感觉很牛逼, 然后赶紧喜欢, 然后继续读下去, 发现越看越懵逼;:joy:
        nodzhang:同感
      • 快乐石头111:博主, 我看源码中这些变量都是false啊,
        @nullable
        SmoothScroller mSmoothScroller;

        boolean mRequestedSimpleAnimations = false;

        boolean mIsAttachedToWindow = false;

        boolean mAutoMeasure = false;
        快乐石头111:@被代码淹没的小伙子 谢谢楼主
        被代码淹没的小伙子:像mAutoMeasure确实默认值是false,但是你在setLayoutManager的时候会new一个LayoutMananger,你可以看一下LinearLayoutManager的构造函数,会调用setAutoMeasureEnabled(true)方法,把mAutoMeasure设置为true的。

      本文标题:【进阶】RecyclerView源码解析(一)——绘制流程

      本文链接:https://www.haomeiwen.com/subject/zxvcxftx.html