美文网首页安卓
RecycleView源码理解 绘制流程(一)

RecycleView源码理解 绘制流程(一)

作者: 她和她的喵真好看 | 来源:发表于2019-10-10 20:17 被阅读0次

    备注

    虽然工作有几年了,但是玩安卓的时间比较短,第一次看recycleview源码看的也是有点头大。这篇文章纯单是自己的一个学习笔记看待,描述有问题的地方,敬请谅解。

    原因

    为什么会有想法去看RecycleView源码,主要的原因是:刚好在工作中碰到一个问题是:在滑动item中,未配置的imageview会显示之前的配置的imageView。由于一开始对recycleview的不熟悉,这个问题弄了好久。因此,想着要不干脆一不做二不休,去理解梳理一遍源码吧。

    带着问题

    看源码,经常看着看着就想理解每一步的具体的逻辑,但是这样势必造成理解起来很困难,花的时间也很长。还是需要带着问题去看源码,通过打断点的方式,去查看调用方式,理清一个脉络,去慢慢的理解源码。
    那么接下来,我们就慢慢去查看源码

    路口

    那么我们从哪里开始看代码呢?我们在一开始设置adapter调用的函数是:setAdapter函数,那么就从这里开始看起:

     /**
         * Set a new adapter to provide child views on demand.
         * <p>
         * When adapter is changed, all existing views are recycled back to the pool. If the pool has
         * only one adapter, it will be cleared.
         */
        public void setAdapter(@Nullable Adapter adapter) {
            ....
            //设置新的adapter,并且触发监听
            setAdapterInternal(adapter, false, true);
            processDataSetCompletelyChanged(false);
            //请求布局,直接调用View类的请求布局方法
            requestLayout();
        }
    

    setAdapter函数里,按我们所关心的具体做了两件事情:

    1. 调用setAdapterInternal函数,用新的adapter替换掉老的adapter,并且触发监听。看看setAdapterInternal具体做了什么事情:
     /**
         * Replaces the current adapter with the new one and triggers listeners.
         * @param adapter The new adapter
         * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
         *                               item types with the current adapter (helps us avoid cache
         *                               invalidation).
         * @param removeAndRecycleViews  If true, we'll remove and recycle all existing views. If
         *                               compatibleWithPrevious is false, this parameter is ignored.
         */
        private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
                boolean removeAndRecycleViews) {
            if (mAdapter != null) {
                mAdapter.unregisterAdapterDataObserver(mObserver); // 注销老的adapter观察者
                mAdapter.onDetachedFromRecyclerView(this); //解绑recycleview
            }
            ....
            mAdapterHelper.reset();
            final Adapter oldAdapter = mAdapter;
            mAdapter = adapter;
            if (adapter != null) {
                adapter.registerAdapterDataObserver(mObserver); //注册观察者
                adapter.onAttachedToRecyclerView(this);
            }
            ...
        }
    

    setAdapterInternal函数中主要做的是注销之前注册的adapter,重新注册新的adapter,并且绑定recycleView。

    1. 调用requestLayout 函数,请求布局。
      我们看看requestLayout 中函数的实现:
    @Override
        public void requestLayout() {
            if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
                super.requestLayout();
            } else {
                mLayoutWasDefered = true;
            }
        }
    

    我们看到requestLayout 最终调用父类的requestLayout();但是,我现在对自定义的View不是很熟悉,按照参考的一些博客的说法是:最终调用到onLayout函数,最终由子类自己实现onLayout函数实现。

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

    从这个函数我们看到,最终是调用dispatchLayout()去分发Layout。
    又可以继续看看dispatchLayout函数具体做了啥:

    void dispatchLayout() {
            ...
            if (mState.mLayoutStep == State.STEP_START) {
                //dispath第一步
                dispatchLayoutStep1();
                mLayout.setExactMeasureSpecsFrom(this);
                //dispatch第二步
                dispatchLayoutStep2();
            }
            ...
            //dispatch第二步
            dispatchLayoutStep3();
        }
    

    2.1 dispatch 第一步

     /**
         * 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() {
            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();
                for (int i = 0; i < count; ++i) {
                    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                    if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                        continue;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator
                            .recordPreLayoutInformation(mState, holder,
                                    ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                    holder.getUnmodifiedPayloads());
                    mViewInfoStore.addToPreLayout(holder, animationInfo);
                    if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                            && !holder.shouldIgnore() && !holder.isInvalid()) {
                        long key = getChangedHolderKey(holder);
                        mViewInfoStore.addToOldChangeHolders(key, holder);
                    }
                }
            }
            ......
            mState.mLayoutStep = State.STEP_LAYOUT;
        }
    

    按照dispatchLayoutStep1()函数说明执行以下几件事:

     1.处理Adapter的更新
     2.决定那些动画需要执行
     3.保存当前View的信息
     4.如果必要的话,执行上一个Layout的操作并且保存他的信息
     5.更新mLayoutStep  为State.STEP_LAYOUT
    

    2.2 dispatch 第二步

      /**
         * 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() {
            ......
            //更新mItemCount
            mState.mItemCount = mAdapter.getItemCount();
            mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
            // Step 2: Run layout
            mState.mInPreLayout = false;
            //运行layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = false;
            mPendingSavedState = null;
           ......
        }
    

    朋友们,看到这一步,最重点的要来了。mLayout.onLayoutChildren这个函数就是我们要开始规划item怎么规划,怎么布局的关键啦。这里我们放在后面介绍,这里的内容会比较多。
    2.3 dispatch 第三步

    The final step of the layout where we save the information about views for animations,
         * trigger animations and do any necessary cleanup.
    

    第三步按照函数的说明:主要做了保存一些views和animations信息,并且触发animations并且做一些一些必要的清理。

    看到这里我们大概算是理清了一个脉络:直接拿取另外一个博主的截图:


    image.png

    重点来了

    上面我们将一个流程梳理完成,从setAdapter到最终的
    onLayoutChildren函数。接着我们看onLayoutChildren具体是怎么实现的。
    dispatchLayoutStep2函数调用的onLayoutChildren的方式是:mLayout.onLayoutChildren(mRecycler, mState); 。而mLayout 是什么时候赋值的呢?
    我们一开始在setAdapter的时候,也会使用setLayoutManager函数设定mLayout。这次我们以LinearLayoutManager作为参考,进行代码解析。
    因此onLayoutChildren 最终是调用mLayout对象的onLayoutChildren。这里也间接说明RecyclerView布局就通过这个mLayout布局管理者来做。
    我们继续看代码:

     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
           ......
            if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
                mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
            }
            ensureLayoutState();
            mLayoutState.mRecycle = false;
            // resolve layout direction
            resolveShouldLayoutReverse();
    
            final View focused = getFocusedChild();
            if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.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())) {
                // This case relates to when the anchor child is the focused view and due to layout
                // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
                // up after tapping an EditText which shrinks RV causing the focused view (The tapped
                // EditText which is the anchor child) to get kicked out of the screen. Will update the
                // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
                // the available space in layoutState will be calculated as negative preventing the
                // focused view from being laid out in fill.
                // Note that we won't update the anchor position between layout passes (refer to
                // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
                // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
                // child which can change between layout passes).
                mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
            }
            if (DEBUG) {
                Log.d(TAG, "Anchor info:" + mAnchorInfo);
            }
    
            // LLM may decide to layout items for "extra" pixels to account for scrolling target,
            // caching or predictive animations.
    
            mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
                    ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            calculateExtraLayoutSpace(state, mReusableIntPair);
            int extraForStart = Math.max(0, mReusableIntPair[0])
                    + mOrientationHelper.getStartAfterPadding();
            int extraForEnd = Math.max(0, mReusableIntPair[1])
                    + mOrientationHelper.getEndPadding();
            if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
                    && mPendingScrollPositionOffset != INVALID_OFFSET) {
                // if the child is visible and we are going to move it around, we should layout
                // extra items in the opposite direction to make sure new items animate nicely
                // instead of just fading in
                final View existing = findViewByPosition(mPendingScrollPosition);
                if (existing != null) {
                    final int current;
                    final int upcomingOffset;
                    if (mShouldReverseLayout) {
                        current = mOrientationHelper.getEndAfterPadding()
                                - mOrientationHelper.getDecoratedEnd(existing);
                        upcomingOffset = current - mPendingScrollPositionOffset;
                    } else {
                        current = mOrientationHelper.getDecoratedStart(existing)
                                - mOrientationHelper.getStartAfterPadding();
                        upcomingOffset = mPendingScrollPositionOffset - current;
                    }
                    if (upcomingOffset > 0) {
                        extraForStart += upcomingOffset;
                    } else {
                        extraForEnd -= upcomingOffset;
                    }
                }
            }
            int startOffset;
            int endOffset;
            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;
            }
    
            onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
            detachAndScrapAttachedViews(recycler);
            mLayoutState.mInfinite = resolveIsInfinite();
            mLayoutState.mIsPreLayout = state.isPreLayout();
            // noRecycleSpace not needed: recycling doesn't happen in below's fill
            // invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
            mLayoutState.mNoRecycleSpace = 0;
            if (mAnchorInfo.mLayoutFromEnd) {
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                mLayoutState.mExtraFillSpace = 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.mExtraFillSpace = extraForEnd;
                mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
    
                if (mLayoutState.mAvailable > 0) {
                    // end could not consume all. add more items towards start
                    extraForStart = mLayoutState.mAvailable;
                    updateLayoutStateToFillStart(firstElement, startOffset);
                    mLayoutState.mExtraFillSpace = extraForStart;
                    fill(recycler, mLayoutState, state, false);
                    startOffset = mLayoutState.mOffset;
                }
            } else {
                // fill towards end
                updateLayoutSpublic 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;
        }
    

    这里我们大概概括一下onLayoutChildren中布局的逻辑:

    1. 通过resolveShouldLayoutReverse 判断绘制的方向
    2. 获取锚点mAnchorInfo。
    3. 绘制的方式有两种:
      3.1 以锚点为基准,先往上绘制,再往下绘制。
      3.2 以锚点为基准,先往下绘制,再往上绘制。
    4. 绘制完后还有剩余空间可以绘制,继续绘制。
      接下来,我们基于以上几个点继续看源码:

    绘制方向

      // resolve layout direction
            resolveShouldLayoutReverse();
    
     /**
         * Calculates the view layout order. (e.g. from end to start or start to end)
         * RTL layout support is applied automatically. So if layout is RTL and
         * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
         */
        private void resolveShouldLayoutReverse() {
            // A == B is the same result, but we rather keep it readable
            if (mOrientation == VERTICAL || !isLayoutRTL()) {
                mShouldReverseLayout = mReverseLayout;
            } else {
                mShouldReverseLayout = !mReverseLayout;
            }
        }
    

    onLayoutChildren函数中调用resolveShouldLayoutReverse函数根据mOrientation 变量判断是横向填充还是竖向填充。mOrientation 默认是为VERTICAL,调用setOrientation设置mOrientation。

    寻找锚点

     mAnchorInfo.reset();
     mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
      // calculate anchor position and coordinate
     updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
    mAnchorInfo.mValid = true;
    

    mAnchorInfo就是我们要找的锚点,它的成员变量主要有以下四个

            int mPosition;  位置
            int mCoordinate; 坐标
            boolean mLayoutFromEnd; 
            boolean mValid;
    

    mValid 默认值为false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false。
    mLayoutFromEnd mShouldReverseLayout默认是fasle的,mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false。
    updateAnchorInfoForLayout这个函数是更新mAnchorInfo 数据中的mPositionmCoordinate,后续再分析。

    开始绘制

    绘制的地方,主要就是两种方向,正向(先向上再向下),逆向(先向下再向上),所以这里我们就看平常的情况。

     // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                ......
                fill(recycler, mLayoutState, state, false);
                ......
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                ......
                fill(recycler, mLayoutState, state, false);
                ......
    
                if (mLayoutState.mAvailable > 0) {
                    ......
                    updateLayoutStateToFillEnd(lastElement, endOffset);
                    mLayoutState.mExtraFillSpace = extraForEnd;
                    fill(recycler, mLayoutState, state, false);
                    endOffset = mLayoutState.mOffset;
                }
    

    这里我们发现主要通过updateLayoutStateToFillEndupdateLayoutStateToFillStart,fill函数进行绘制。
    updateLayoutStateToFill...其实就是确定当前方向上锚点的相关的状态信息。
    fill()主要用来绘制可以看到这里至少调用了两次fill()方法,当还有剩余可以绘制的时候会再调一次fill()方法。
    这里继续引用一个博主的图:

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

    fill函数最终调用layoutChunk函数。

           View view = layoutState.next(recycler);
            .....
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } 
            measureChildWithMargins(view, 0, 0);
            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;
                }
            }
            // We calculate everything with View's bounding box (which includes decor and margins)
            // To calculate correct layout position, we subtract margins.
            layoutDecoratedWithMargins(view, left, top, right, bottom);
           ....
    

    这个函数关注点会很多,我一个个的看:

    1. layoutState.next(recycler);
     if (mScrapList != null) {
                    return nextViewFromScrapList();
                }
                final View view = recycler.getViewForPosition(mCurrentPosition);
                mCurrentPosition += mItemDirection;
                return view;
    

    按照函数说明就是用来获取view的,那么怎么获取呢?
    mScrapList 这个变量默认是null,只有执行layoutForPredictiveAnimations前不为空,执行完后又变为空,所以这里暂时不需要考虑。
    getViewForPosition 涉及的东西比较多,流程上简单的看一下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创建。

    1. addView
    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。


    image.png
    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);
            }
    

    最后

    中间有大量摘录
    博客:https://www.jianshu.com/p/c52b947fe064
    博客:https://www.jianshu.com/p/10298503c134
    这篇文章全当自己的学习笔记。

    相关文章

      网友评论

        本文标题:RecycleView源码理解 绘制流程(一)

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