美文网首页RecyclerView记录
RecyclerView源码学习笔记一(绘制流程)

RecyclerView源码学习笔记一(绘制流程)

作者: WonderSky_HY | 来源:发表于2019-03-27 17:52 被阅读0次

    本文参考了【进阶】RecyclerView源码解析(一)——绘制流程,有不少地方我是直接照搬过来的,侵权立删。

    源码版本基于com.android.support:recyclerview-v7:27.1.1

    RecyclerView的源码非常复杂,再加上一些辅助类其代码量超过了2W行,这也导致很多人望而却步,但是作为Android体系中一个非常重要的组件,学习其源码对于我们了解RecyclerView是如何运行的有很大的帮助。由于RecyclerView的源码实在太多,所以我们就从主要的流程开始,毕竟RecyclerView也是直接继承自ViewGroup,我们都知道自定义ViewGroup其主要流程在于重写onMeasure,onLayout以及onDraw三个方法,我们也先从这三个流程来入手。

    onMesure

    protected void onMeasure(int widthSpec, int heightSpec) {
        //没有设置LayoutManager走默认的measure流程
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        //这里LayoutManager的几个子类isAutoMeasureEnabled()方法默认为true
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
    
            //代码省略......
             mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            //如果测量模式是精确模式,直接返回,不继续测量
            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }
    
            //mState.mLayoutStep默认值是State.STEP_START
            if (mState.mLayoutStep == State.STEP_START) {
                //measure前的一些准备工作
                dispatchLayoutStep1();
                //走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //实际measure发生的地方,这里执行完后mState.mLayoutStep变为State.STEP_ANIMATIONS
            dispatchLayoutStep2();
    
            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    
            // 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.
            //判断是否需要执行二次测量,如果需要则再执行一次 dispatchLayoutStep2()
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
    
            //代码省略......       
    
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear
        }
    }
    

    整个measure的流程大致如上,中间删掉了一些代码,其实看源码我们没有必要去深究代码细节,只需要关注其整体流程即可。
    我们从上往下逐个分:

    //没有设置LayoutManager走默认的measure流程
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);     
        return;
    }
    

    mLayout就是我们为RecyclerView设置的LayoutManager,这里判断当我们没有设置LayoutManager时,走默认的测量。

    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);
        }
    }
    

    chooseSize方法就是根据自身的测量模式(mode)来获取其相应的自身宽高,然后直接调用setMeasuredDimension方法设置自身宽高。由于这里没有进行child的测量,因此不难理解当我们没有设置LayoutManager时界面也就会出现什么都没有的情况。
    然后判断mLayout.isAutoMeasureEnabled()。

    //LinearLayoutManager
    public boolean isAutoMeasureEnabled() {
        return true;
    }
    

    这里默认为true(GridLayoutManager和StaggerLayoutManager与此类似),进入内部流程。
    接下来判断测量模式,如果是精确测量模式则什么也不做,直接返回,否则进入下一步

    final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
    if (measureSpecModeIsExactly || mAdapter == null) {
        return;
    }
    

    判断当前绘制步骤状态(默认是State.STEP_START,RecyclerView内部维护了一个叫State的类,便于管理RecyclerView相应的状态信息,这个后面再分析),执行相应操作。

    //mState.mLayoutStep默认值是State.STEP_START
    if (mState.mLayoutStep == State.STEP_START) {
        //measure前的一些准备工作
        dispatchLayoutStep1();
        //走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
    }
    
    //RecyclerView的一个内部类,管理RecyclerView的状态信息
    public static class State {
        static final int STEP_START = 1;
        static final int STEP_LAYOUT = 1 << 1;
        static final int STEP_ANIMATIONS = 1 << 2;
        ......
        @LayoutState
        int mLayoutStep = STEP_START;
        ......
    }
    

    执行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
     */
    private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        fillRemainingScrollValues(mState);
        mState.mIsMeasuring = false;
        startInterceptRequestLayout();
        mViewInfoStore.clear();
        onEnterLayoutOrScroll();
        processAdapterUpdatesAndSetAnimationFlags();
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
    
        if (mState.mRunSimpleAnimations) {
            // Step 0: Find out where all non-removed items are, pre-layout
            int count = mChildHelper.getChildCount();
            ......
        }
        if (mState.mRunPredictiveAnimations) {
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.
    
            // Save old positions so that LayoutManager can run its mapping logic.
            ......
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        //方法执行完后,将mState.mLayoutStep设置为State.STEP_LAYOUT,便于下一步操作
        mState.mLayoutStep = State.STEP_LAYOUT;
    }
    

    执行dispatchLayoutStep2(),这里也是真正执行子view布局的地方,可以看到RecyclerView将layout工作转交给了LayoutManager。

    /**
     * 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() {
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        //获取子view的数量,这里调用了adapter的getItemCount()方法。
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    
        // Step 2: Run layout
        mState.mInPreLayout = false;
        //实际执行子view布局的地方,这里是将measure工作转交给LayoutManager。
        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;
        //测量工作完成后,修改State的layoutStep为State.STEP_ANIMATIONS,
        //为后续子view显示或退出显示动画做准备
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }
    

    可以看到,实际执行子元素布局的工作转交给了LayoutManager,其具体实现在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.
        //填充子view,从锚点往上和从锚点往下
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        //RecyclerView滑动时填充需要的布局
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state
    
        //代码省略......
    
        ensureLayoutState();
        mLayoutState.mRecycle = false;
        //判断绘制方向,默认是垂直布局或者LayoutDirection是从左往右
        // resolve layout direction
        resolveShouldLayoutReverse();
    
        final View focused = getFocusedChild();
        //如果锚点信息无效则重置锚点信息
        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;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }
    
        //代码省略......
    
        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;
        }
    
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mIsPreLayout = state.isPreLayout();
    
        if (mAnchorInfo.mLayoutFromEnd) {
            //如果是倒着布局的话
            // fill towards start
            //从锚点往上布局
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            final int firstElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // fill towards end
            //从锚点往下布局
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
    
            //如果还有多余的填充像素数,再次运行fill方法
            if (mLayoutState.mAvailable > 0) {
                // end could not consume all. add more items towards start
                extraForStart = mLayoutState.mAvailable;
                updateLayoutStateToFillStart(firstElement, startOffset);
                mLayoutState.mExtra = extraForStart;
                fill(recycler, mLayoutState, state, false);
                startOffset = mLayoutState.mOffset;
            }
        } 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;
    
            //与上面类似,如果有多余的绘制,则再次调用fill方法
            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;
        //代码省略......
    }
    

    子元素具体的布局是在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;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            //代码省略......
        }
        //代码省略......
        return start - layoutState.mAvailable;
    }
    

    具体的逻辑都在layoutChunk(recycler, state, layoutState, layoutChunkResult)方法里面。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LayoutState layoutState, LayoutChunkResult result) {
        //获取下一个子view
        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();
        //根据view的状态执行对应的addview方法
        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);
            }
        }
        //测量从LayoutState中取出的子view
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        //计算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 {
                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的布局
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        //代码省略......
    }
    

    首先看下这行代码View view = layoutState.next(recycler);,这里非常重要。

    View next(RecyclerView.Recycler recycler) {
        //在第一次加载RecyclerView的时候mScrapList为null,所以暂时不考虑
        if (mScrapList != null) {
            return nextViewFromScrapList();
        }
        //获取view,这里使用了缓存策略
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return 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;
    }
    
    ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
        //代码省略......
        ViewHolder holder = null;
        //省略掉从缓存中获取view的代码......
        //代码省略......
            if (holder == null) {
                //代码省略......
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
                //代码省略......
            }
        }
        //代码省略......
        return holder;
    }
    

    这里获取view最终调用ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)方法,并在里面进行了一堆判断,主要是判断scrap、cache以及RecyclerViewPool中是否有缓存,如果这三个缓存中都没有缓存的view,最后才调用adapter的createViewHolder方法创建一个viewholder,在createViewHolder方法中最终会调用我们自己实现的onCreateViewHolder方法。注意RecyclerView这里缓存的是ViewHolder,这也是RecyclerView与ListView不同的地方之一。

    View view = layoutState.next(recycler);的分析暂时告一段落,沿着layoutChunk方法继续往下分析。

    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);
        }
    }
    

    这里就是将我们通过layoutState.next(recycler)获取的view添加到RecyclerView中,继续往下。

    //测量获取的子view
    //LinearLayoutManager->layoutChunk
    measureChildWithMargins(view, 0, 0);
    
    //RecyclerView->LayoutManager->measureChildWithMargins
    public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
        //这里获取Itemdecoration里面设置的水平方向和竖直方向的偏移量
        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
        widthUsed += insets.left + insets.right;
        heightUsed += insets.top + insets.bottom;
    
        //获取子view的measureSpec
        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.measure方法测量子view自身宽高
            child.measure(widthSpec, heightSpec);
        }
    }
    

    这个方法主要是获取childView上下左右的偏移量以及完成childView自身的测量。这里分析下mRecyclerView.getItemDecorInsetsForChild(child);方法。

    Rect getItemDecorInsetsForChild(View child) {
        //代码省略......
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        //mItemDecorations是保存ItemDecoration的集合,
        //我们给RecyclerView绘制分割线等诸多view的装饰效果都是通过继承ItemDecoration来实现的
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            //调用ItemDecoration的getItemOffsets方法来获取相应的view偏移量
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        //代码省略......
        return insets;
    }
    

    这个方法里面循环调用了ItemDecoration的getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)方法(RecyclerView可以设置多个ItemDecoration),并将我们设置的left,top,right,bottom偏移量设置到outRect中去,最后得到RecyclerView设置的所有ItemDecoration的left,top,right,bottom偏移量。这个方法我们在继承ItemDecoration的时候需要我们自己去实现,这里简单说下偏移量,用一张图片来表示:

    image.png
    分析完了measureChildWithMargins后,继续往下。
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); 
    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;
        }
    }
    

    这里是设置RecyclerView中各个子view应该布局的位置(left,top,right,bottom),设置完了后调用layoutDecoratedWithMargins(view, left, top, right, bottom);开始布局子view。

    LinearLayoutManager->layoutChunk->layoutDecoratedWithMargins
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    
    RecyclerView->LayoutManager->layoutDecoratedWithMargins
    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);
    }
    

    可以看到,在layoutDecoratedWithMargins方法中,子view的布局位置是由left,top,right,bottom和ItemDecoration设置的偏移量共同决定的。至此onMeasure的流程就分析完了,接下来看下onLayout方法。

    onLayout

    在分析onMeasure的时候有几个问题:

    • 1、为什么当不设置LayoutManager时,界面什么都没有?
      答:因为RecyclerView把子view的测量以及布局交给了LayoutManager来完成,当我们不设置LayoutManager时,RecyclerView无法完成子view的测量和布局,自然也就无法显示到界面上。
    • 2、当我们给RecyclerView设置精确宽高度时,onMeasure不也是直接就返回,没有经过LayoutManager的测量和布局吗,为什么最后能显示子view呢?
      答:当给RecyclerView设置精确宽高值时,LayoutManager对子view的测量和布局工作是在onLayout方法中完成的。这也是接下来将要分析的部分。
    • 3、在执行完dispatchLayoutStep2();方法后,设置了mState.mLayoutStep = State.STEP_ANIMATIONS;,这个STEP_ANIMATIONS又是在哪里调用的呢?
    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() {
        //代码省略......
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }
    

    可以看到在dispatchLayout方法中,仍然会判断State的mLayoutStep这个变量,当给RecyclerView设置精确宽高值是,在onMeasure方法中直接返回,跳过了接下来的步骤,因此mLayoutStep仍然是其初始值,也就是State.STEP_START,因此条件成立并执行dispatchLayoutStep1();dispatchLayoutStep2();完成子view的测量和布局。
    这里还有一个判断,也就是当我们在布局的时候改变了RecyclerView的宽高值,也会调用dispatchLayoutStep2();方法重新测量和布局。
    在dispatchLayout()方法的最后调用了dispatchLayoutStep3();,这里就可以回答上面的第三个问题,也就是STEP_ANIMATIONS就是在这个方法中判断的。根据注释,我们可以看出,这个方法主要是保存子view的一些动画以及做一些相关的清理工作。

    private void dispatchLayoutStep3() {
        //这里有一个前提就是mLayoutStep必须是State.STEP_ANIMATIONS,
        //也就是执行完了dispatchLayoutStep2()后该方法才能继续往下走
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        //这里将mLayoutStep又重置为了State.STEP_START
        mState.mLayoutStep = State.STEP_START;
        //如果设置了ItemAnimator,这里判断成立,RecyclerView预置了默认的ItemAnimator
        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.
            //循环保存ItemAnimator中设置的动画信息,如果有设置的话,RecyclerView中预置了默认动画
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                //代码省略......
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                    final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                            oldChangeViewHolder);
                    final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                    if (oldDisappearing && oldChangeViewHolder == holder) {
                        // run disappear animation instead of change
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                    } else {
                        final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
                                oldChangeViewHolder);
                        // we add and remove so that any post info is merged.
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                        ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                        if (preInfo == null) {
                            handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                        } else {
                            animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
                                    oldDisappearing, newDisappearing);
                        }
                    }
                } else {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }
    
            // Step 4: Process view info lists and trigger animations
            //执行ItemAnimator中设置的动画
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
    
        //layout完成后,执行一些清理工作
        mLayout.removeAndRecycleScrapInt(mRecycler);
        mState.mPreviousLayoutItemCount = mState.mItemCount;
        mDataSetHasChangedAfterLayout = false;
        mDispatchItemsChangedEvent = false;
        mState.mRunSimpleAnimations = false;
        //代码省略......
    }
    

    这里看下mViewInfoStore.process(mViewInfoProcessCallback);这个方法。

    mViewInfoStore.process(mViewInfoProcessCallback);
    
    /**
     * The callback to convert view info diffs into animations.
     */
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
        new ViewInfoStore.ProcessCallback() {
           //代码省略......
            @Override
            public void processAppeared(ViewHolder viewHolder,
                                        ItemHolderInfo preInfo, ItemHolderInfo info) {
                animateAppearance(viewHolder, preInfo, info);
            }
           //代码省略......
        };
    
    void animateAppearance(@NonNull ViewHolder itemHolder,
                           @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        itemHolder.setIsRecyclable(false);
        //判断是否设置了动画,如果设置了,则执行动画
        if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();
        }
    }
    
    void postAnimationRunner() {
        if (!mPostedAnimatorRunner && mIsAttached) {
            //post动画执行
            ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
            mPostedAnimatorRunner = true;
        }
    }
    
    private Runnable mItemAnimatorRunner = new Runnable() {
        @Override
        public void run() {
            if (mItemAnimator != null) {
                //调用ItemAnimator的runPendingAnimations()方法执行动画,该方法需要我们自己实现
                mItemAnimator.runPendingAnimations();
            }
            mPostedAnimatorRunner = false;
        }
    };
    

    可以看到,所有动画逻辑都封装在了mViewInfoProcessCallback中,这个callback是ViewInfoStore类的一个内部接口,里面封装了各种动画行为。

    interface ProcessCallback {
        void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                                @Nullable ItemHolderInfo postInfo);
        void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
                             ItemHolderInfo postInfo);
        void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                               @NonNull ItemHolderInfo postInfo);
        void unused(ViewHolder holder);
    }
    
    
    void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                // Appeared then disappeared. Not useful for animations.
                callback.unused(viewHolder);
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                // Set as "disappeared" by the LayoutManager (addDisappearingView)
                if (record.preInfo == null) {
                    // similar to appear disappear but happened between different layout passes.
                    // this can happen when the layout manager is using auto-measure
                    callback.unused(viewHolder);
                } else {
                    callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
                }
            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
                // Appeared in the layout but not in the adapter (e.g. entered the viewport)
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                // Persistent in both passes. Animate persistence
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                // Was in pre-layout, never been added to post layout
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                // Was not in pre-layout, been added to post layout
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_APPEAR) != 0) {
                // Scrap view. RecyclerView will handle removing/recycling this.
            } else if (DEBUG) {
                throw new IllegalStateException("record without any reasonable flag combination:/");
            }
            InfoRecord.recycle(record);
        }
    }
    

    mViewInfoStore.process(mViewInfoProcessCallback);方法内部就是各种判断,并调用相应的动画执行。至此,onLayout方法的分析也告一段落,我们接着分析onDraw方法。

    onDraw

    在分析onDraw之前,首先了解下View的绘制流程。View的draw方法太长了,这里就不拿出来分析,我们只了解下流程,通过其源码我们可以得出大致的流程:

    draw -> drawbackground(绘制背景,不能重写) -> onDraw(绘制自身) -> dispatchDraw(绘制子view) 
    -> onDrawForeground(绘制前景) -> drawDefaultFocusHighlight(绘制其他)
    

    RecyclerView继承了ViewGroup,内部重写了draw,onDraw这两个方法,我们就来分析下:

    
    public void draw(Canvas c) {
        //调用父类的draw方法绘制自身
        //这里会依次调用上面所说的流程,完成绘制
        super.draw(c);
    
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            //绘制悬浮效果
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
        // need find children closest to edges. Not sure if it is worth the effort.
        //判断是否需要重绘
        boolean needsInvalidate = false;
        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
            //......
            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mTopGlow != null && !mTopGlow.isFinished()) {
            //......
        }
        if (mRightGlow != null && !mRightGlow.isFinished()) {
            //......
        }
        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
            //......
        }
    
        // If some views are animating, ItemDecorators are likely to move/change with them.
        // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
        // display lists are not invalidated.
        //如果有子view的动画还在执行,则需要重绘
        if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
                && mItemAnimator.isRunning()) {
            needsInvalidate = true;
        }
    
        //如果需要重绘,发送重绘信息
        if (needsInvalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    
    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);
        }
    }
    

    先看下draw方法,首先调用super.draw(canvas c),完成自身的绘制(这里会调用onDraw方法,完成divider的绘制);然后调用ItemDecoration的onDrawOver(需要我们自己实现)来绘制子view的悬浮效果,最后进行一些列判断,判断是否需要重绘。
    接着看onDraw方法,这里首先调用super.onDraw(c);来绘制。然后调用ItemDecoration的onDraw方法来绘制分割线等效果,这个onDraw也是需要我们自己去实现的。

    至此RecyclerView的整个绘制流程就分析完了,由于这里只侧重整体的流程,有很多详细的地方没有分析到,如有兴趣可自行分析细节。总结下几个关键点:

    1、RecyclerView将子view的measure工作交给了LayoutManager来完成的,如果没有设置LayoutManager,则列表不会有任何显示。
    2、RecyclerView的子view绘制分为正序绘制和倒序绘制。首先是确定锚点,然后由锚点分别向两边绘制,因此fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)方法至少会执行两次,如果绘制完了还有剩余空间,则继续执行fill方法。
    3、fill方法的核心逻辑在于layoutChunk方法中,layoutChunk方法中首先会通过layoutState.next(recycler)获取view,首先是从scrap、cache以及RecyclerViewPool等缓存中获取,如果这些缓存中都没有,则调用adapter的createViewHolder方法创建一个ViewHolder
    4、如果没有为RecyclerView设置精确宽高值,则onMeasure方法会完成子view的测量以及布局(如有设置ItemDecoration,则会将ItemDecoration的getItemOffsets方法中设置的left,top,right,bottom偏移量也算在内),在onLayout方法中仅仅只需要完成一些清理工作以及子view进出动画的处理工作;如果设置了精确的宽高值,则对子view的测量以及布局工作会在onLayout中完成。
    5、RecyclerView重写了draw和onDraw方法,如果设置了ItemDecoration,并重写了onDrawOver以及onDraw方法,则在draw方法中会调用ItemDecoration的onDrawOver绘制悬浮效果;在onDraw方法中调用ItemDecoration的onDraw方法绘制子view之间的分割线以及其他装饰效果。

    相关文章

      网友评论

        本文标题:RecyclerView源码学习笔记一(绘制流程)

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