RecyclerView源碼學(xué)習(xí)筆記一(繪制流程)

本文參考了【進(jìn)階】RecyclerView源碼解析(一)——繪制流程,有不少地方我是直接照搬過來的,侵權(quán)立刪。

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

RecyclerView的源碼非常復(fù)雜,再加上一些輔助類其代碼量超過了2W行,這也導(dǎo)致很多人望而卻步,但是作為Android體系中一個(gè)非常重要的組件,學(xué)習(xí)其源碼對于我們了解RecyclerView是如何運(yùn)行的有很大的幫助。由于RecyclerView的源碼實(shí)在太多,所以我們就從主要的流程開始,畢竟RecyclerView也是直接繼承自ViewGroup,我們都知道自定義ViewGroup其主要流程在于重寫onMeasure,onLayout以及onDraw三個(gè)方法,我們也先從這三個(gè)流程來入手。

onMesure

protected void onMeasure(int widthSpec, int heightSpec) {
    //沒有設(shè)置LayoutManager走默認(rèn)的measure流程
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    //這里L(fēng)ayoutManager的幾個(gè)子類isAutoMeasureEnabled()方法默認(rèn)為true
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);

        //代碼省略......
         mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        //如果測量模式是精確模式,直接返回,不繼續(xù)測量
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }

        //mState.mLayoutStep默認(rèn)值是State.STEP_START
        if (mState.mLayoutStep == State.STEP_START) {
            //measure前的一些準(zhǔn)備工作
            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;
        //實(shí)際measure發(fā)生的地方,這里執(zhí)行完后mState.mLayoutStep變?yōu)镾tate.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.
        //判斷是否需要執(zhí)行二次測量,如果需要?jiǎng)t再執(zhí)行一次 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
    }
}

整個(gè)measure的流程大致如上,中間刪掉了一些代碼,其實(shí)看源碼我們沒有必要去深究代碼細(xì)節(jié),只需要關(guān)注其整體流程即可。
我們從上往下逐個(gè)分:

//沒有設(shè)置LayoutManager走默認(rèn)的measure流程
if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);     
    return;
}

mLayout就是我們?yōu)镽ecyclerView設(shè)置的LayoutManager,這里判斷當(dāng)我們沒有設(shè)置LayoutManager時(shí),走默認(rèn)的測量。

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方法就是根據(jù)自身的測量模式(mode)來獲取其相應(yīng)的自身寬高,然后直接調(diào)用setMeasuredDimension方法設(shè)置自身寬高。由于這里沒有進(jìn)行child的測量,因此不難理解當(dāng)我們沒有設(shè)置LayoutManager時(shí)界面也就會(huì)出現(xiàn)什么都沒有的情況。
然后判斷mLayout.isAutoMeasureEnabled()。

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

這里默認(rèn)為true(GridLayoutManager和StaggerLayoutManager與此類似),進(jìn)入內(nèi)部流程。
接下來判斷測量模式,如果是精確測量模式則什么也不做,直接返回,否則進(jìn)入下一步

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

判斷當(dāng)前繪制步驟狀態(tài)(默認(rèn)是State.STEP_START,RecyclerView內(nèi)部維護(hù)了一個(gè)叫State的類,便于管理RecyclerView相應(yīng)的狀態(tài)信息,這個(gè)后面再分析),執(zhí)行相應(yīng)操作。

//mState.mLayoutStep默認(rèn)值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
    //measure前的一些準(zhǔn)備工作
    dispatchLayoutStep1();
    //走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}

//RecyclerView的一個(gè)內(nèi)部類,管理RecyclerView的狀態(tài)信息
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;
    ......
}

執(zhí)行dispatchLayoutStep1()方法,從這個(gè)方法的注釋就可以看到,dispatchLayoutStep1()僅僅只是做一些準(zhǔn)備工作,包括一些狀態(tài)的清理及初始化,具體的細(xì)節(jié)就不分析了。

/**
 * 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);
    //方法執(zhí)行完后,將mState.mLayoutStep設(shè)置為State.STEP_LAYOUT,便于下一步操作
    mState.mLayoutStep = State.STEP_LAYOUT;
}

執(zhí)行dispatchLayoutStep2(),這里也是真正執(zhí)行子view布局的地方,可以看到RecyclerView將layout工作轉(zhuǎn)交給了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的數(shù)量,這里調(diào)用了adapter的getItemCount()方法。
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    //實(shí)際執(zhí)行子view布局的地方,這里是將measure工作轉(zhuǎn)交給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,
    //為后續(xù)子view顯示或退出顯示動(dòng)畫做準(zhǔn)備
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

可以看到,實(shí)際執(zhí)行子元素布局的工作轉(zhuǎn)交給了LayoutManager,其具體實(shí)現(xiàn)在LayoutManager的子類中,這里以LinearLayoutManager為例來分析。

public 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.
    //填充子view,從錨點(diǎn)往上和從錨點(diǎn)往下
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    //RecyclerView滑動(dòng)時(shí)填充需要的布局
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state

    //代碼省略......

    ensureLayoutState();
    mLayoutState.mRecycle = false;
    //判斷繪制方向,默認(rèn)是垂直布局或者LayoutDirection是從左往右
    // resolve layout direction
    resolveShouldLayoutReverse();

    final View focused = getFocusedChild();
    //如果錨點(diǎn)信息無效則重置錨點(diǎn)信息
    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默認(rèn)為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
        //從錨點(diǎn)往上布局
        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
        //從錨點(diǎn)往下布局
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;

        //如果還有多余的填充像素?cái)?shù),再次運(yùn)行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 {
        //默認(rèn)正常布局,先從錨點(diǎn)往下布局,在往上布局
        // 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;

        //與上面類似,如果有多余的繪制,則再次調(diào)用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
    //這里是修復(fù)由于子元素滑動(dòng)可能會(huì)導(dǎo)致出現(xiàn)的一些空白
    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;
        }
    }

    //完成后重置相關(guān)的一些參數(shù)
    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) {
    //獲取下一個(gè)子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();
    //根據(jù)view的狀態(tài)執(zhí)行對應(yīng)的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);
    //計(jì)算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的時(shí)候mScrapList為null,所以暫時(shí)不考慮
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //獲取view,這里使用了緩存策略
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

繼續(xù)看recycler.getViewForPosition(mCurrentPosition);這個(gè)方法。

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最終調(diào)用ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)方法,并在里面進(jìn)行了一堆判斷,主要是判斷scrap、cache以及RecyclerViewPool中是否有緩存,如果這三個(gè)緩存中都沒有緩存的view,最后才調(diào)用adapter的createViewHolder方法創(chuàng)建一個(gè)viewholder,在createViewHolder方法中最終會(huì)調(diào)用我們自己實(shí)現(xiàn)的onCreateViewHolder方法。注意RecyclerView這里緩存的是ViewHolder,這也是RecyclerView與ListView不同的地方之一。

View view = layoutState.next(recycler);的分析暫時(shí)告一段落,沿著layoutChunk方法繼續(xù)往下分析。

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中,繼續(xù)往下。

//測量獲取的子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里面設(shè)置的水平方向和豎直方向的偏移量
    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)) {
        //調(diào)用child.measure方法測量子view自身寬高
        child.measure(widthSpec, heightSpec);
    }
}

這個(gè)方法主要是獲取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來實(shí)現(xiàn)的
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0, 0, 0, 0);
        //調(diào)用ItemDecoration的getItemOffsets方法來獲取相應(yīng)的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;
}

這個(gè)方法里面循環(huán)調(diào)用了ItemDecoration的getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)方法(RecyclerView可以設(shè)置多個(gè)ItemDecoration),并將我們設(shè)置的left,top,right,bottom偏移量設(shè)置到outRect中去,最后得到RecyclerView設(shè)置的所有ItemDecoration的left,top,right,bottom偏移量。這個(gè)方法我們在繼承ItemDecoration的時(shí)候需要我們自己去實(shí)現(xiàn),這里簡單說下偏移量,用一張圖片來表示:

image.png

分析完了measureChildWithMargins后,繼續(xù)往下。

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

這里是設(shè)置RecyclerView中各個(gè)子view應(yīng)該布局的位置(left,top,right,bottom),設(shè)置完了后調(diào)用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設(shè)置的偏移量共同決定的。至此onMeasure的流程就分析完了,接下來看下onLayout方法。

onLayout

在分析onMeasure的時(shí)候有幾個(gè)問題:

  • 1、為什么當(dāng)不設(shè)置LayoutManager時(shí),界面什么都沒有?
    答:因?yàn)镽ecyclerView把子view的測量以及布局交給了LayoutManager來完成,當(dāng)我們不設(shè)置LayoutManager時(shí),RecyclerView無法完成子view的測量和布局,自然也就無法顯示到界面上。
  • 2、當(dāng)我們給RecyclerView設(shè)置精確寬高度時(shí),onMeasure不也是直接就返回,沒有經(jīng)過LayoutManager的測量和布局嗎,為什么最后能顯示子view呢?
    答:當(dāng)給RecyclerView設(shè)置精確寬高值時(shí),LayoutManager對子view的測量和布局工作是在onLayout方法中完成的。這也是接下來將要分析的部分。
  • 3、在執(zhí)行完dispatchLayoutStep2();方法后,設(shè)置了mState.mLayoutStep = State.STEP_ANIMATIONS;,這個(gè)STEP_ANIMATIONS又是在哪里調(diào)用的呢?
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方法中,仍然會(huì)判斷State的mLayoutStep這個(gè)變量,當(dāng)給RecyclerView設(shè)置精確寬高值是,在onMeasure方法中直接返回,跳過了接下來的步驟,因此mLayoutStep仍然是其初始值,也就是State.STEP_START,因此條件成立并執(zhí)行dispatchLayoutStep1();dispatchLayoutStep2();完成子view的測量和布局。
這里還有一個(gè)判斷,也就是當(dāng)我們在布局的時(shí)候改變了RecyclerView的寬高值,也會(huì)調(diào)用dispatchLayoutStep2();方法重新測量和布局。
在dispatchLayout()方法的最后調(diào)用了dispatchLayoutStep3();,這里就可以回答上面的第三個(gè)問題,也就是STEP_ANIMATIONS就是在這個(gè)方法中判斷的。根據(jù)注釋,我們可以看出,這個(gè)方法主要是保存子view的一些動(dòng)畫以及做一些相關(guān)的清理工作。

private void dispatchLayoutStep3() {
    //這里有一個(gè)前提就是mLayoutStep必須是State.STEP_ANIMATIONS,
    //也就是執(zhí)行完了dispatchLayoutStep2()后該方法才能繼續(xù)往下走
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    //這里將mLayoutStep又重置為了State.STEP_START
    mState.mLayoutStep = State.STEP_START;
    //如果設(shè)置了ItemAnimator,這里判斷成立,RecyclerView預(yù)置了默認(rèn)的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.
        //循環(huán)保存ItemAnimator中設(shè)置的動(dòng)畫信息,如果有設(shè)置的話,RecyclerView中預(yù)置了默認(rèn)動(dòng)畫
        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
        //執(zhí)行ItemAnimator中設(shè)置的動(dòng)畫
        mViewInfoStore.process(mViewInfoProcessCallback);
    }

    //layout完成后,執(zhí)行一些清理工作
    mLayout.removeAndRecycleScrapInt(mRecycler);
    mState.mPreviousLayoutItemCount = mState.mItemCount;
    mDataSetHasChangedAfterLayout = false;
    mDispatchItemsChangedEvent = false;
    mState.mRunSimpleAnimations = false;
    //代碼省略......
}

這里看下mViewInfoStore.process(mViewInfoProcessCallback);這個(gè)方法。

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);
    //判斷是否設(shè)置了動(dòng)畫,如果設(shè)置了,則執(zhí)行動(dòng)畫
    if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

void postAnimationRunner() {
    if (!mPostedAnimatorRunner && mIsAttached) {
        //post動(dòng)畫執(zhí)行
        ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
        mPostedAnimatorRunner = true;
    }
}

private Runnable mItemAnimatorRunner = new Runnable() {
    @Override
    public void run() {
        if (mItemAnimator != null) {
            //調(diào)用ItemAnimator的runPendingAnimations()方法執(zhí)行動(dòng)畫,該方法需要我們自己實(shí)現(xiàn)
            mItemAnimator.runPendingAnimations();
        }
        mPostedAnimatorRunner = false;
    }
};

可以看到,所有動(dòng)畫邏輯都封裝在了mViewInfoProcessCallback中,這個(gè)callback是ViewInfoStore類的一個(gè)內(nèi)部接口,里面封裝了各種動(dòng)畫行為。

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);方法內(nèi)部就是各種判斷,并調(diào)用相應(yīng)的動(dòng)畫執(zhí)行。至此,onLayout方法的分析也告一段落,我們接著分析onDraw方法。

onDraw

在分析onDraw之前,首先了解下View的繪制流程。View的draw方法太長了,這里就不拿出來分析,我們只了解下流程,通過其源碼我們可以得出大致的流程:

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

RecyclerView繼承了ViewGroup,內(nèi)部重寫了draw,onDraw這兩個(gè)方法,我們就來分析下:


public void draw(Canvas c) {
    //調(diào)用父類的draw方法繪制自身
    //這里會(huì)依次調(diào)用上面所說的流程,完成繪制
    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的動(dòng)畫還在執(zhí)行,則需要重繪
    if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
            && mItemAnimator.isRunning()) {
        needsInvalidate = true;
    }

    //如果需要重繪,發(fā)送重繪信息
    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方法,首先調(diào)用super.draw(canvas c),完成自身的繪制(這里會(huì)調(diào)用onDraw方法,完成divider的繪制);然后調(diào)用ItemDecoration的onDrawOver(需要我們自己實(shí)現(xiàn))來繪制子view的懸浮效果,最后進(jìn)行一些列判斷,判斷是否需要重繪。
接著看onDraw方法,這里首先調(diào)用super.onDraw(c);來繪制。然后調(diào)用ItemDecoration的onDraw方法來繪制分割線等效果,這個(gè)onDraw也是需要我們自己去實(shí)現(xiàn)的。

至此RecyclerView的整個(gè)繪制流程就分析完了,由于這里只側(cè)重整體的流程,有很多詳細(xì)的地方?jīng)]有分析到,如有興趣可自行分析細(xì)節(jié)??偨Y(jié)下幾個(gè)關(guān)鍵點(diǎn):

1、RecyclerView將子view的measure工作交給了LayoutManager來完成的,如果沒有設(shè)置LayoutManager,則列表不會(huì)有任何顯示。
2、RecyclerView的子view繪制分為正序繪制和倒序繪制。首先是確定錨點(diǎn),然后由錨點(diǎn)分別向兩邊繪制,因此fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)方法至少會(huì)執(zhí)行兩次,如果繪制完了還有剩余空間,則繼續(xù)執(zhí)行fill方法。
3、fill方法的核心邏輯在于layoutChunk方法中,layoutChunk方法中首先會(huì)通過layoutState.next(recycler)獲取view,首先是從scrap、cache以及RecyclerViewPool等緩存中獲取,如果這些緩存中都沒有,則調(diào)用adapter的createViewHolder方法創(chuàng)建一個(gè)ViewHolder
4、如果沒有為RecyclerView設(shè)置精確寬高值,則onMeasure方法會(huì)完成子view的測量以及布局(如有設(shè)置ItemDecoration,則會(huì)將ItemDecoration的getItemOffsets方法中設(shè)置的left,top,right,bottom偏移量也算在內(nèi)),在onLayout方法中僅僅只需要完成一些清理工作以及子view進(jìn)出動(dòng)畫的處理工作;如果設(shè)置了精確的寬高值,則對子view的測量以及布局工作會(huì)在onLayout中完成。
5、RecyclerView重寫了draw和onDraw方法,如果設(shè)置了ItemDecoration,并重寫了onDrawOver以及onDraw方法,則在draw方法中會(huì)調(diào)用ItemDecoration的onDrawOver繪制懸浮效果;在onDraw方法中調(diào)用ItemDecoration的onDraw方法繪制子view之間的分割線以及其他裝飾效果。

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

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