RecycleView源碼理解 繪制流程(一)

備注

雖然工作有幾年了,但是玩安卓的時(shí)間比較短,第一次看recycleview源碼看的也是有點(diǎn)頭大。這篇文章純單是自己的一個(gè)學(xué)習(xí)筆記看待,描述有問題的地方,敬請(qǐng)諒解。

原因

為什么會(huì)有想法去看RecycleView源碼,主要的原因是:剛好在工作中碰到一個(gè)問題是:在滑動(dòng)item中,未配置的imageview會(huì)顯示之前的配置的imageView。由于一開始對(duì)recycleview的不熟悉,這個(gè)問題弄了好久。因此,想著要不干脆一不做二不休,去理解梳理一遍源碼吧。

帶著問題

看源碼,經(jīng)??粗粗拖肜斫饷恳徊降木唧w的邏輯,但是這樣勢必造成理解起來很困難,花的時(shí)間也很長。還是需要帶著問題去看源碼,通過打斷點(diǎn)的方式,去查看調(diào)用方式,理清一個(gè)脈絡(luò),去慢慢的理解源碼。
那么接下來,我們就慢慢去查看源碼

路口

那么我們從哪里開始看代碼呢?我們?cè)谝婚_始設(shè)置adapter調(diào)用的函數(shù)是:setAdapter函數(shù),那么就從這里開始看起:

 /**
     * 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) {
        ....
        //設(shè)置新的adapter,并且觸發(fā)監(jiān)聽
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        //請(qǐng)求布局,直接調(diào)用View類的請(qǐng)求布局方法
        requestLayout();
    }

setAdapter函數(shù)里,按我們所關(guān)心的具體做了兩件事情:

  1. 調(diào)用setAdapterInternal函數(shù),用新的adapter替換掉老的adapter,并且觸發(fā)監(jiān)聽。看看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); //注冊(cè)觀察者
            adapter.onAttachedToRecyclerView(this);
        }
        ...
    }

setAdapterInternal函數(shù)中主要做的是注銷之前注冊(cè)的adapter,重新注冊(cè)新的adapter,并且綁定recycleView。

  1. 調(diào)用requestLayout 函數(shù),請(qǐng)求布局。
    我們看看requestLayout 中函數(shù)的實(shí)現(xiàn):
@Override
    public void requestLayout() {
        if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
            super.requestLayout();
        } else {
            mLayoutWasDefered = true;
        }
    }

我們看到requestLayout 最終調(diào)用父類的requestLayout();但是,我現(xiàn)在對(duì)自定義的View不是很熟悉,按照參考的一些博客的說法是:最終調(diào)用到onLayout函數(shù),最終由子類自己實(shí)現(xiàn)onLayout函數(shù)實(shí)現(xiàn)。

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

從這個(gè)函數(shù)我們看到,最終是調(diào)用dispatchLayout()去分發(fā)Layout。
又可以繼續(xù)看看dispatchLayout函數(shù)具體做了啥:

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()函數(shù)說明執(zhí)行以下幾件事:

 1.處理Adapter的更新
 2.決定那些動(dòng)畫需要執(zhí)行
 3.保存當(dāng)前View的信息
 4.如果必要的話,執(zhí)行上一個(gè)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;
        //運(yùn)行l(wèi)ayout
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = false;
        mPendingSavedState = null;
       ......
    }

朋友們,看到這一步,最重點(diǎn)的要來了。mLayout.onLayoutChildren這個(gè)函數(shù)就是我們要開始規(guī)劃item怎么規(guī)劃,怎么布局的關(guān)鍵啦。這里我們放在后面介紹,這里的內(nèi)容會(huì)比較多。
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.

第三步按照函數(shù)的說明:主要做了保存一些views和animations信息,并且觸發(fā)animations并且做一些一些必要的清理。

看到這里我們大概算是理清了一個(gè)脈絡(luò):直接拿取另外一個(gè)博主的截圖:


image.png

重點(diǎn)來了

上面我們將一個(gè)流程梳理完成,從setAdapter到最終的
onLayoutChildren函數(shù)。接著我們看onLayoutChildren具體是怎么實(shí)現(xiàn)的。
dispatchLayoutStep2函數(shù)調(diào)用的onLayoutChildren的方式是:mLayout.onLayoutChildren(mRecycler, mState); 。而mLayout 是什么時(shí)候賦值的呢?
我們一開始在setAdapter的時(shí)候,也會(huì)使用setLayoutManager函數(shù)設(shè)定mLayout。這次我們以LinearLayoutManager作為參考,進(jìn)行代碼解析。
因此onLayoutChildren 最終是調(diào)用mLayout對(duì)象的onLayoutChildren。這里也間接說明RecyclerView布局就通過這個(gè)mLayout布局管理者來做。
我們繼續(xù)看代碼:

 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:
        //找尋錨點(diǎn)
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        // item position.
        //兩個(gè)方向填充,從錨點(diǎn)往上,從錨點(diǎn)往下
        // 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賦值,默認(rèn)是正向繪制,則mShouldReverseLayout是false
        resolveShouldLayoutReverse();
        final View focused = getFocusedChild();
        //mValid的默認(rèn)值是false,一次測量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                || mPendingSavedState != null) {
        ....
            //mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)都會(huì)false,異或則為false
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            //計(jì)算錨點(diǎn)的位置和偏移量
            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
            // 從錨點(diǎn)到往上
            updateLayoutStateToFillStart(mAnchorInfo);
            ....
            fill(recycler, mLayoutState, state, false);
            ....
            // 從錨點(diǎn)到往下
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            ....
            //調(diào)兩遍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);
        //完成后重置參數(shù)
        if (!state.isPreLayout()) {
            mOrientationHelper.onLayoutComplete();
        } else {
            mAnchorInfo.reset();
        }
        mLastStackFromEnd = mStackFromEnd;
    }

這里我們大概概括一下onLayoutChildren中布局的邏輯:

  1. 通過resolveShouldLayoutReverse 判斷繪制的方向
  2. 獲取錨點(diǎn)mAnchorInfo。
  3. 繪制的方式有兩種:
    3.1 以錨點(diǎn)為基準(zhǔn),先往上繪制,再往下繪制。
    3.2 以錨點(diǎn)為基準(zhǔn),先往下繪制,再往上繪制。
  4. 繪制完后還有剩余空間可以繪制,繼續(xù)繪制。
    接下來,我們基于以上幾個(gè)點(diǎn)繼續(xù)看源碼:

繪制方向

  // 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函數(shù)中調(diào)用resolveShouldLayoutReverse函數(shù)根據(jù)mOrientation 變量判斷是橫向填充還是豎向填充。mOrientation 默認(rèn)是為VERTICAL,調(diào)用setOrientation設(shè)置mOrientation。

尋找錨點(diǎn)

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

mAnchorInfo就是我們要找的錨點(diǎn),它的成員變量主要有以下四個(gè)

        int mPosition;  位置
        int mCoordinate; 坐標(biāo)
        boolean mLayoutFromEnd; 
        boolean mValid;

mValid 默認(rèn)值為false,一次測量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse。
mLayoutFromEnd mShouldReverseLayout默認(rèn)是fasle的,mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)都會(huì)false,異或則為false。
updateAnchorInfoForLayout這個(gè)函數(shù)是更新mAnchorInfo 數(shù)據(jù)中的mPositionmCoordinate,后續(xù)再分析。

開始繪制

繪制的地方,主要就是兩種方向,正向(先向上再向下),逆向(先向下再向上),所以這里我們就看平常的情況。

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

這里我們發(fā)現(xiàn)主要通過updateLayoutStateToFillEndupdateLayoutStateToFillStart,fill函數(shù)進(jìn)行繪制。
updateLayoutStateToFill...其實(shí)就是確定當(dāng)前方向上錨點(diǎn)的相關(guān)的狀態(tài)信息。
fill()主要用來繪制可以看到這里至少調(diào)用了兩次fill()方法,當(dāng)還有剩余可以繪制的時(shí)候會(huì)再調(diào)一次fill()方法。
這里繼續(xù)引用一個(gè)博主的圖:

image.png

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

fill函數(shù)最終調(diào)用layoutChunk函數(shù)。

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

這個(gè)函數(shù)關(guān)注點(diǎn)會(huì)很多,我一個(gè)個(gè)的看:

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

按照函數(shù)說明就是用來獲取view的,那么怎么獲取呢?
mScrapList 這個(gè)變量默認(rèn)是null,只有執(zhí)行l(wèi)ayoutForPredictiveAnimations前不為空,執(zhí)行完后又變?yōu)榭眨赃@里暫時(shí)不需要考慮。
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創(chuàng)建
         */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
          //一堆判斷之后,如果不成立
            holder = mAdapter.createViewHolder(RecyclerView.this, type);            
        }

可以看到這里,getViewForPosition會(huì)調(diào)用tryGetViewHolderForPositionByDeadline方法,tryGetViewHolderForPositionByDeadline方法的注釋寫的很清楚從Recycler的scrap,cache,RecyclerViewPool,或者直接create創(chuàng)建。

  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后會(huì)調(diào)用

//測量ChildView
        measureChildWithMargins(view, 0, 0);

//----------------------------------------------------------
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //設(shè)置分割線中的回調(diào)方法
            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);
            }
        }

從這個(gè)方法里我們看到了子View的測量,當(dāng)然還有一個(gè)需要我們注意的地方那就是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()實(shí)現(xiàn)分割線的回調(diào)方法!
            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;
    }

其實(shí)可以看到這里在測量子View的時(shí)候是將我們實(shí)現(xiàn)自定義分割線重寫的getItemOffsets方法。這里其實(shí)也就可以理解了自定義分割線的原理就是在子View的測量過程前給上下左右加上自定義分割線所對(duì)應(yīng)設(shè)置給這個(gè)child的邊距。
測量完成后,緊接著就調(diào)用了layoutDecoratedWithMargins(view, left, top, right, bottom)對(duì)子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);
        }

最后

中間有大量摘錄
博客:http://www.itdecent.cn/p/c52b947fe064
博客:http://www.itdecent.cn/p/10298503c134
這篇文章全當(dāng)自己的學(xué)習(xí)筆記。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容