RecyclerView 源碼分析1-繪製流程

在Android開發中RecyclerView是咱們高頻使用的一個組件,用來展現大量的數據。咱們不只要熟練使用它,還要對它的實現有一個認知。本片文章介紹RecyclerView的繪製流程,也就是onMeasureonLayoutonDraw這三個方法中主要作了些什麼工做,let's go!java

OnMeasure

咱們知道該方法是測量當前View及子View的寬高,可是查看RecyclerView的源碼發現代碼很長,它不只僅作了測量的工做,還作了一些其餘的工做,咱們一塊兒來看一下緩存

if (mLayout == null) {
   //第一種狀況
}
if (mLayout.isAutoMeasureEnabled()) {
  //第二種狀況,一般會進入到這種狀況
}else{
 // 第三種狀況
}
複製代碼

根據條件分支將onMeasure方法分紅了三種狀況,咱們挨個來討論一下bash

  • 狀況一: mLayout == null
if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    return;
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
    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);
}
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);
    }
}
複製代碼

mLayout == null時,從傳入的參數能夠看出來RecyclerView沒有考慮子View就決定了本身的大小,是一個比較粗糙的測量,具體的大小還須要根據以後屢次測量的結果來定app

  • 狀況二 mLayout.isAutoMeasureEnabled() == true

mLayout.isAutoMeasureEnabled()true時,將調用LayoutManager.onLayoutChildren(Recycler, State)來計算子View們所須要的大小,RecyclerView.LayoutManager的實現類LinearLayoutManagerStaggeredGridLayoutManager都重載了該方法並返回true,因此一般都會走入這個分支(列出了部分代碼)ide

if (mLayout.isAutoMeasureEnabled()) {
    //將測量交給LaytouManager
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    final boolean measureSpecModeIsExactly =
    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
    //若是width和height都已是精確值,那麼就不用再根據內容進行測量,後面步驟再也不處理
    if (measureSpecModeIsExactly || mAdapter == null) {
        return;
    }
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
    }
    dispatchLayoutStep2();
    //若是須要二次測量的話
    if (mLayout.shouldMeasureTwice()) {
    dispatchLayoutStep2();
    }
}
複製代碼

第一步調用了LayoutManager.onMeasure()方法源碼分析

public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,int heightSpec) {
    mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
複製代碼

該方法直接調用了RecyclerView的默認測量方法,就是咱們以前分析的第一種狀況。閱讀該方法的註釋咱們能夠發現,LayoutoManager強烈推薦開啓自動測量,而且若是開啓了自動測量就不要重寫該方法,LayoutoManager的三個默認實現也確實沒重寫該方法。還介紹了下測量的策略,若是寬高測量模式爲UNSPECIFIED. AT_MOST則指定爲EXACTLY而且RecyclerView佔用可用的最大空間。佈局

第二步 若是寬高的測量模式都爲MeasureSpec.EXACTLY或者沒有設置Adapter直接返回。post

接下來咱們繼續看,mState.mLayoutStep的默認值就是State.STEP_START,進入條件語句執行dispatchLayoutStep1(),而後執行dispatchLayoutStep2(),若是須要執行二次測量的話在執行一次dispatchLayoutStep2()動畫

咱們重點看dispatchLayoutStep1()dispatchLayoutStep2(),與這兩個方法息息相關的一個變量是mState.mLayoutStep,該變量得意義是決定了dispatchLayoutStep1()dispatchLayoutStep1()dispatchLayoutStep2()這三個方法該執行哪一步了,它的取值有三個ui

mLayoutStep 描述
STEP_START 默認值或者dispatchLayoutStep3()已經執行了
STEP_LAYOUT dispatchLayoutStep1()已經執行了
STEP_ANIMATIONS dispatchLayoutStep2()已經執行了

對這三個方法的具體分析,咱們放到onLayout中處理,先說一下結論dispatchLayoutStep1()處理了Adapter的數據更新以及準備動畫前的數據;dispatchLayoutStep2()進行itemView的佈局

  • 狀況三: mLayout.isAutoMeasureEnabled() == false

    咱們一般使用的LayoutManager都返回true,除非咱們自定義,因此暫不分析這種狀況

總結一下onMeasure所作的工做,假設LayoutManagerLinearLayoutManager

  1. 測量一下本身,可能須要屢次測量
  2. 若是寬高不都爲MeasureSpec.EXACTLY模式則執行
  • dispatchLayoutStep1(),處理Adapter更新以及準備動畫前的數據
  • dispatchLayoutStep2()進行itemView的佈局

OnLayout

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

void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            return;
        }
        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();
    }
複製代碼

流程也很清晰

  • 若是AdapterLayoutManager沒設置就不進行佈局,RecyclerView也就只能顯示一片空白
  • 若是以前onMeasure中執行了dispatchLayoutStep1()dispatchLayoutStep2()則再也不執行這兩個方法,不過dispatchLayoutStep2()可能須要被再次調用
  • 執行dispatchLayoutStep3()

如今咱們來查看下dispatchLayoutStep系列方法到底幹了些什麼

  • dispatchLayoutStep1()
/** * The first step of a layout where we; * - process adapter updates * - decide which animation should run * - save information about current views * - If necessary, run predictive layout and save its information */
    private void dispatchLayoutStep1() {
        processAdapterUpdatesAndSetAnimationFlags();
        if (mState.mRunSimpleAnimations){
            //...
        }
        if (mState.mRunPredictiveAnimations){
            //...
        }
        mState.mLayoutStep = State.STEP_LAYOUT;
    }
複製代碼

以上代碼縮減了不少,從該方法的註釋能夠看出來,dispatchLayoutStep1()主要處理了Adapter更新以及準備動畫所需數據,而processAdapterUpdatesAndSetAnimationFlags()就是用來處理Adapter更新和動畫的Flag處理,咱們看一下這個方法裏面

private void processAdapterUpdatesAndSetAnimationFlags() {
    if (mDataSetHasChangedAfterLayout) {
            // Processing these items have no value since data set changed unexpectedly.
            // Instead, we just reset it.
            mAdapterHelper.reset();
            if (mDispatchItemsChangedEvent) {
                mLayout.onItemsChanged(this);
            }
        }
        爲動畫的flag進行賦值
        mState.mRunSimpleAnimations = ...
        mState.mRunPredictiveAnimations = ...
}
複製代碼

首先處理Adapter的更新(Adapter.notifyDataSetChanged()或者RecyclerView.swapAdapter(Adapter, boolean)表明Adapter有更新),就是簡單的把以前記錄的每個item的操做重置一下,由於數據集的更改致使以前存的信息都沒有意義了,下邊的代碼是爲動畫的標誌位賦值,咱們調用RecyclerView.setAdapterAdapter.notifyDataSetChanged()是不會觸發動畫的,因此咱們先不考慮動畫相關的東西。

咱們繼續來看dispatchLayoutStep1()的內容,下面是兩個if條件,涉及兩個變量mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations這兩個變量在要執行動畫時才爲true,因此先不考慮裏面的內容。 最後執行mState.mLayoutStep = State.STEP_LAYOUT,表明dispatchLayoutStep1()已經執行完畢了

總結dispatchLayoutStep1() 處理了Adapter更新以及準備動畫所需數據

  • dispatchLayoutStep2()
private void dispatchLayoutStep2() {
    mLayout.onLayoutChildren(mRecycler, mState);
    mState.mLayoutStep = State.STEP_ANIMATIONS;
}
複製代碼

方法很簡潔,調用了LayoutManager.onLayoutChildren(Recycler recycler, State state),該方法就是進行子View佈局的實質方法,不過是一個空實現,子類必須去實現這個方法,聲明以下

public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, Statestate) ");
}
複製代碼

最後mState.mLayoutStep = State.STEP_ANIMATIONS表明dispatchLayoutStep2()已經執行完畢了

總結dispatchLayoutStep2() 調用LayoutManager.onLayoutChildren來進行子View的佈局

  • dispatchLayoutStep3()
private void dispatchLayoutStep3() {
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) {
    //記錄layout以後View的信息,並觸發動畫
    //...
    }
    //...一些清理工做
}
複製代碼

首先將mState.mLayoutStep = State.STEP_START,標誌dispatchLayoutStep3()已經執行了,而後mState.mRunSimpleAnimations這個變量表示是否執行動畫,第一次佈局的時候是不須要動畫的因此不會進入這個分支,動畫咱們以後在講,最後作一些清理的工做。

總結dispatchLayoutStep3() 觸發動畫

總結一下onLayout所作的工做,大致流程以下

  1. dispatchLayoutStep1()處理了Adapter更新以及準備動畫所需數據
  2. dispatchLayoutStep2()調用LayoutManager.onLayoutChildren來進行子View的佈局
  3. dispatchLayoutStep3()觸發動畫

draw

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);
        }
     //...處理clipToPadding="false"的狀況
    }
複製代碼

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);
        }
    }
複製代碼

總結:drawonDrawRecyclerView的分割線進行了繪製,固然分割線須要咱們本身去實現具體的繪製內容,同時咱們也知道了ItemDecorationonDrawonDrawOver的區別

至此咱們已經完成了RecyclerView三大流程的源碼分析,上面列出的代碼大多都通過了精簡,省去了不少細節,不過剛開始閱讀源碼時,咱們只要把握總體的流程就好,拋開細節來看,以上的總體流程並不難理解。可是有一個很重要的方法沒有細講,就是LayoutManager.onLayoutChildren(),該方法纔是佈局子View的核心,咱們對該方法進行單獨的一波分析,以LinearLayoutManager(只考慮豎直方向)爲例來看一下

LinearLayoutManager.onLayoutChildren()

在進行子View的佈局中利用了一些幫助類來幫助佈局,咱們須要先了解一下這些幫助類

  • LayoutState
屬性 解釋
mOffset 偏移多少個像素點以後開始佈局
mAvailable 當前佈局方向上可用的空間
mCurrentPosition 要佈局子View在Adapter中的表明的位置
mInfinite 佈局的View數量沒有限制
  • AnchorInfo
屬性 解釋
mPosition 錨點位置
mCoordinate 錨點座標信息
mValid 是否可用
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.

    //新建一個LayoutState
    ensureLayoutState();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null) {
            mAnchorInfo.reset();
            //更新錨點信息
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
    }
    //...計算LinearLayoutManager所需的額外空間
    //錨點信息準備好了
    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    //將現有的子View都緩存起來
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd){
        //...
    }else{
        // fill towards end
        //將錨點的信息保存到mLayoutState中
        updateLayoutStateToFillEnd(mAnchorInfo);
        fill(recycler, mLayoutState, state, false);
        // fill towards start
        updateLayoutStateToFillEnd(mAnchorInfo);
        fill(recycler, mLayoutState, state, false);
        
        if (mLayoutState.mAvailable > 0) {
            fill(recycler, mLayoutState, state, false);
        }
    }
}
複製代碼

以上代碼省略了不少有用信息,包括對LayoutState內部一些有用屬性的賦值等。由代碼剛開始的註釋可瞭解到該方法內部執行邏輯

  1. 找到錨點位置和錨點座標
  2. 從錨點開始,往上填充佈局子View,直到填滿區域
  3. 從錨點開始,往下填充佈局子View,直到填滿區域
  4. 滾動以知足需求,如從底部堆疊

關鍵是錨點,對於LinearLayoutManager來講,它不必定是從最高點或者最低點開始佈局,有多是從中間某個點開始佈局的,如圖所示

原圖出自 RecyclerView剖析,

  • 肯定錨點信息
    1. 第一次佈局,錨點信息確定是不可用的,進入更新錨點的條件語句中,裏面調用updateAnchorInfoForLayout(recycler, state, mAnchorInfo)更新錨點信息
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo) {
        // 第一種計算方式
        if (updateAnchorFromPendingData(state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor info from pending information");
            }
            return;
        }
        // 第二種計算方式
        if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor info from existing children");
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "deciding anchor info for fresh state");
        }
        // 第三種計算方式
        anchorInfo.assignCoordinateFromPadding();
        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    }
複製代碼

能夠看出來有三種計算錨點信息的方法,每一個方法裏的代碼雖然多卻不難理解

  • 方法一
private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
        if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
            return false;
        }
        //...
}
複製代碼

mPendingScrollPosition == RecyclerView.NO_POSITION這個條件判斷默認狀況是true的,只有在調用scrollToPosition或者在onRestoreInstanceState恢復以前記錄的mPendingScrollPosition時纔會有其餘值(上面省略代碼就是計算mPendingScrollPosition不爲默認值的錨點信息,本文沒有分析),因此默認狀況該方法沒有計算錨點信息,往下走

  • 方法二 updateAnchorFromChildren這個方法根據子View來肯定錨點信息
    • 若是沒有子View則,直接返回false,表示沒有計算出錨點信息
    • 有子View的話,通常會選擇屏幕中可見的子View的position爲錨點。這裏會選取屏幕上第一個可見View,也就是positon=1的子View做爲參考點, anchorInfo.mCoordinate 被賦值爲1號子View上面的Decor的頂部位置

該方法的詳細分析能夠看這篇文章RecyclerView源碼解析

  • 方法三

最後兜底的方法,直接將anchorInfo.mCoordinate賦值爲padding,若是沒有設置padding,則anchorInfo.mCoordinate = 0anchorInfo.mPosition = 0(mStackFromEnd == false的狀況,該值默認是false)

錨點信息的計算主要是爲mPositionmCoordinate這兩個變量賦值,這樣咱們就知道了從哪一個點開始填充子View和子View對應的數據在Adapter中的位置

更新錨點信息以後,源碼中有一長段代碼用來計算LinearLayoutManager須要的「額外空間」,這段代碼我也沒懂,就不分析了,不過並不影響咱們把握總體佈局流程。錨點信息都準備好以後,updateLayoutStateToFillEnd()將錨點信息保存到mLayoutState中,而後調用fill()方法開始填充子View了,mAnchorInfo.mLayoutFromEnd將填充分爲兩種狀況

  • true: 從Adapter最後一項開始,從下往上佈局
  • false: 從Adapter第一項開始,從上往下佈局(默認狀況) 如圖所示(虛線表示屏幕外邊的ItemView

默認狀況爲false,從上往下開始佈局,而後進入關鍵的fill()方法

int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
    //可用空間
    final int start = layoutState.mAvailable;
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    //保存了每一子View消耗的空間
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    //循環佈局子View
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //核心方法
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {  
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                //消費子View佔據的空間
                remainingSpace -= layoutChunkResult.mConsumed;
            }
         //...
    }      
return start - layoutState.mAvailable;
}
複製代碼

fill的核心思路就是在一個循環裏不斷地進行子View佈局,結束條件是沒有可用空間或者數據源沒有數據了,layoutChunk負責填充,每填充一個子View,剩餘空間就減相對應View佔據的空間(豎直方向上來講就是高度),而後填充下一個,最後返回的是本次佈局所填充的區域大小。

咱們進入layoutChunk來看具體的實現

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        //獲取一個子View
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        //將View添加到RecyclerView中
        RecyclerView.LayoutParams params = (RecyclerView.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);
            }
        }
        //測量子View的大小包括margins和decorations
        measureChildWithMargins(view, 0, 0);
        //將佔據的空間保存到LayoutChunkResult之中,供外層循環使用
        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.
        //將子View放到合適的位置 
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

複製代碼
  • 第一步 View view = layoutState.next(recycler)獲取一個itemView,這裏涉及到RecyclerView的緩存機制,咱們後邊的篇章在討論。
  • 第二步 是將itemView添加到RecyclerView中,又分爲兩種狀況
    • addView 很好理解,方法內部作了一些位置正確性、避免重複添加等邏輯判斷,而後調用ViewGroupaddView來實現。
    • addDisappearingView 表明該View即將從屏幕上消失,好比劃出屏幕或者調用Adapter.notifyItemRemoved,該方法和上面的addView都會調用內部的addViewInt(View child, int index, boolean disappearing),只不過是最後一個參數不同而已。
  • 第三步 測量itemView的大小,measureChildWithMargins(view, 0, 0)這個方法內部除了自身大小以外,還須要考慮margindecorations(咱們常說的分割線)的大小。測量以後把消耗的空間保存到LayoutChunkResult之中,供外層循環使用。
  • 第四步 將itemView放到合適的位置,計算位置時layoutState.mOffset跟咱們以前算的錨點座標息息相關,若是是第一個itemView,則layoutState.mOffset和錨點座標是同樣的,你們能夠經過調試來觀察數據對應關係。固然佈局時還了考慮margindecorations(咱們常說的分割線)

以上將fill()方法分析完成以後,LinearLayoutManager.onLayoutChildren的核心 已經分析完畢了,最後還有一個layoutForPredictiveAnimations,從該方法的註釋來看,是爲了動畫作一些佈局,也不是必須執行的,就再也不分析了,若是有讀者清楚這塊內容的,但願能告知我一下。

至此,LinearLayoutManager.onLayoutChildren分析完畢,可是該方法註釋的最後一條,貼一下原文 4) scroll to fulfill requirements like stack from bottom.我並無看到它體如今哪,多是上文省略的一些細節中包含,總之這一點我並不明白,若是有讀者清楚,但願能告知我一下。

總結

RecyclerView的繪製流程咱們分析完了,總結一下

  1. onMeasureLayoutManager是否開啓自動測量是有關係的,若是支持自動測量的話,可能會進行預佈局,默認實現的三個LayoutManager都是支持自動測量的,若是自定義LayoutManager的話要注意這一點
  2. onLayout 中主要是dispatchLayoutStep1()dispatchLayoutStep1()dispatchLayoutStep1()這三個方法按順序調用,第一個和第三個主要處理了動畫相關,第二個將佈局的任務交給LayoutManager
  3. drawonDraw調用了ItemDecoration中的方法,咱們實現這些方法來自定義分割線

最後,因爲做者水平有限,若是以上分析有出錯的地方,歡迎提出,我及時進行改正,以避免誤導其餘人

相關資料

RecyclerView 源碼分析(一) - RecyclerView的三大流程

RecyclerView源碼解析

RecyclerView剖析

相關文章
相關標籤/搜索