步步前行(RecyclerView拆分解析)-ViewGroup篇

步步前行(RecyclerView拆分解析)-ViewGroup篇

不喜勿噴,有錯請留言(以下源碼均來自recyclerView-27.1.1版本)

對於android開發而言,recyclerView是最熟悉常用的組件了,在很多層面上,常常替代ListView,往上分析RecyclerView的代碼文章更是數不勝數,不過這一篇我想換一個角度,從拆分RecyclerView,讓Recyclerview迴歸ViewGroup的角度來理解RecyclerView,那我們如何去拆分呢?我決定以一個模擬的方式,從RecyclerView上拆分下來的功能模塊在模擬類上使用,從而理解其功能

組成

首先相信大家或多或少都使用過RecyclerView,對於最最基本的使用我相信點進來的都會,如圖

在這裏插入圖片描述

我們必須要創建一個RecyclerView,然後爲這個RecyclerView準備兩樣東西一個是LayoutManager,一個是Adapter,也就是擺放佈局和數據源

LayoutManager決定了我們RecyclerView如何展示橫向還是豎向等,Adapter決定了我們RecyclerView條目個數和條目樣式,至此一個RecyclerView就按照我們的想法呈現在我們面前了。

接下來我們先粗略的看看RecyclerView的組成:

在這裏插入圖片描述

一目瞭然,RecyclerView本身繼承的viewGroup,所以RecyclerView形容成ViewGroup也不爲過

既然是viewGroup,那麼它自身必定重寫onLayout和onMeasure

onMeasure

@Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

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

            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                onEnterLayoutOrScroll();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear
        }
    }

首先layoutManager爲null的情況下會執行defaultOnMeasure方法,我們看看defaultOnMeasure方法

void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }
**************************************************************
從該方法中我們可以看出其直接調用了setMeasuredDimension設置了寬高,併爲對子view做任何的測量

接着對LayoutManager的isAutoMeasureEnabled的不同狀態,做了兩種情況,我們先看看isAutoMeasureEnabled,由於是layoutManager我們不能以RecyclerView的實現爲準,我們參考LinearlayoutManager,對於LinearLayoutManager,其是這麼實現的

@Override
public boolean isAutoMeasureEnabled() {
   return true;
}
******************************************
在LinearLayoutManager中,竟恆定給了true
我們繼續順着onMeasure的true情況看看
**********onMeasure爲true的部分代碼*********
    final int widthMode = MeasureSpec.getMode(widthSpec);
    final int heightMode = MeasureSpec.getMode(heightSpec);
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

    final boolean measureSpecModeIsExactly =
                        widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

    if (measureSpecModeIsExactly || mAdapter == null) {
          return;
    }
*******************************************
在這裏先獲取mode,若爲MeasureSpec.EXACTLY即精確值時,就直接return掉不再往下執行了

而接下來的代碼:

若是始態,執行dispatchLayoutStep1
********************************
if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
}
    mLayout.setMeasureSpecs(widthSpec, heightSpec);
    mState.mIsMeasuring = true;
    dispatchLayoutStep2(); 
**************************************************************
執行完dispatchLayoutStep1和dispatchLayoutStep2擺放後,這時候我們就可以獲取寬高了,下面就是獲取寬高,並設置給LayoutManager,避免有不精確的寬高時,就重新測量
**************************************************************
    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    if (mLayout.shouldMeasureTwice()) {
        mLayout.setMeasureSpecs(
                            MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    mState.mIsMeasuring = true;
    dispatchLayoutStep2();
    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}

至此onMeasure部分的就執行完畢了,接下來就是開始onLayout的部分了。

onLayout

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 **************************************************************
   TraceCompat是support-v4提供的統計一下trace事件給系統
 *************************************************************
   TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
   dispatchLayout();//這裏是我們分析的重點
   TraceCompat.endSection();
   mFirstLayoutComplete = true;
 }

onLayout的dispatchLayout分成了三部分,根據不同的狀態執行了三步:dispatchLayoutStep1,dispatchLayoutStep2和dispatchLayoutStep3

void dispatchLayout() {
		******************************************************
        此處省略了對layoutManger和adapter進行判null處理...
        ******************************************************
        mState.mIsMeasuring = false;
        **************************************************************
  		mState是對我們循環過程狀態的標記,默認是STEP_START,避免重複執行和不必要的計算,執行dispatchLayoutStep1和dispatchLayoutStep2
	  	* STEP_START ->  dispatchLayoutStep1()
		* STEP_LAYOUT -> dispatchLayoutStep2()
		* STEP_ANIMATIONS -> dispatchLayoutStep2(), dispatchLayoutStep3()
		**************************************************************
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {//這個判斷條件,我們很容易可看出當adapter或layout進行變化的時候,就進行dispatchLayoutStep2
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
    *************************************
  	最後每次都執行dispatchLayoutStep3
  	*************************************
        dispatchLayoutStep3();
    }

那我們先從dispatchLayoutStep1開始看,其主要作用是用於動畫等視圖信息,我們大致分析一下簡單理解

private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
	    *****************************************************
  		計算滑動的距離並存儲進state
  		*****************************************************
        fillRemainingScrollValues(mState);
        mState.mIsMeasuring = false;
        *****************************************************
	  	startInterceptRequestLayout與stopInterceptRequestLayout聯動使用
	  	在子view可能觸發requestLayout前和後調用,主要用於截斷requestLayout重新佈局
	  	覺得不太明白的可以看RecyclerView的requestLayout方法再輔助viewGroup的整個requestLayout傳遞可瞭解這一步
	    **************************************************
        startInterceptRequestLayout();
  		還原子view的狀態
        mViewInfoStore.clear();
  		做layout或scroll的計數器
        onEnterLayoutOrScroll();
  		使用適配器更新並計算要運行的動畫類型(Add/REMOVE/UPDATE/MOVE)並存儲到state中
        processAdapterUpdatesAndSetAnimationFlags();//不做深入分析
  		存儲焦點view的信息
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
  		根據所有子view的layoutPosition計算動畫最小位置和最大位置
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
根據processAdapterUpdatesAndSetAnimationFlags計算的動畫類型,去處理做動畫
        if (mState.mRunSimpleAnimations) {
            ...
        }
        if (mState.mRunPredictiveAnimations) {
            ...
        } else {
            clearOldPositions();
        }
  		維護計數器
        onExitLayoutOrScroll();
  		還原requestLayout之行順序
        stopInterceptRequestLayout(false);
  		維護mLayoutStep 準備進行layout
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

這第一步dispatchLayoutStep1到這裏就結束了

下面我們進入dispatchLayoutStep2看看其是什麼樣的作用,從其官方註釋我們可以瞭解,這個方法是整個viewGroup的重中之重,是對實圖的最終形態佈局

private void dispatchLayoutStep2() {
  	老樣子 與第一步很一致呀 不解釋這裏了
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

	*****************************************************
    進入layout詳細,進入到了layoutManager的onLayoutChildren方法中,至此我們如何的擺放就通過這裏傳遞到了LayoutManager身上,交由對方決定(此處會在拆分解析-LayoutManager篇做詳細解釋)
    *****************************************************
    
    mState.mInPreLayout = false;
    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;
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

其實到這裏我們就已經完成了,佈局的展示了,最後的dispatchLayoutStep3,其實是對dispatchLayoutStep1的動畫進行清理等操作,這裏就不做詳細的解釋

最後可能有小夥伴會發現還有onDraw操作,我們就簡單分析下onDraw,其實對於Recyclerview而言onDraw實現是最簡單的,基本上都是圍繞ItemDecoration去寫的

onDraw

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

還有一個重寫了View的draw方法

		@Override
    public void draw(Canvas c) {
        super.draw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        ...
    }

細心的朋友會發現,其調用的位置不同,一個調用的ItemDecoration的onDraw方法,一個調用的ItemDecoration的onDrawOver方法

onDraw是在畫布上draw一些我們想要的東西,其繪製的內容在我們的視圖之前,也就是說視圖壓在Decoration之上,

而onDrawOver是與onDraw一致,但其在視圖之後,也就是說它壓在我們的視圖之上畫的

如果不理解這兩個方法,我用兩張圖來輔助大家理解下:

在這裏插入圖片描述

從上圖我們可看到,代表onDraw的視圖被RecyclerView視圖所遮擋,而onDrawOver的視圖卻遮擋着RecyclerView視圖

至此RecyclerView的onMeasure和onLayout就分析結束了,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章