對於 RecyclerView 是那麼熟悉又那麼陌生。熟悉是由於做爲一名 Android 開發者,RecyclerView 是常常會在項目裏面用到的,陌生是由於只是知道怎麼用,可是殊不知道 RecyclerView 的內部實現機制。html
但凡是一位有所追求的開發者,都不會只讓本身停留在只會使用上,而是會研讀相關源碼,知其然知其因此然。android
對於 RecyclerView 的源碼解析一篇文章確定是不夠的,後續有時間會繼續更新。每一篇都會有本身的主題。RecyclerView 的使用,本文也就不講了,具體能夠查看以前的文章:RecyclerView 使用指南。算法
對於用過 RecyclerView 的開發者來講,這個 View 的功能確實強大,能夠在平常開發的不少場景均可以使用。在講解 RecyclerView 繪製源碼的時候,我但願你們去思考一些問題:緩存
若是是你,你會怎麼來設計 RecyclerView 的繪製過程,和普通的 View 同樣?app
RecyclerView 能夠支持不一樣的流式佈局,一列,多列,因此裏面的繪製邏輯它是如何設計的?less
分割線是能夠定製的,那我要如何設計這塊的代碼?ide
其實也還有其餘的問題,可是本文只講繪製流程,所以,其餘問題就在其餘模塊去思考。要是在之前呢,我也是爲了分析源碼而分析源碼,而後把文章發出去。不多去思考源碼背後的一些東西。直到最近本身須要去重構一個模塊的時候,發現設計一個技術方案是多麼的難。oop
本文源碼版本:androidx.recyclerView:1.1.0 源碼分析
對於 view 來講,必有的三大流程:測量,佈局,繪製。所以 RecyclerView 也是同樣。若是你如今仍是對 View 的繪製流程,不瞭解能夠推薦看文章:佈局
下面進入正題,首先來看下 RecyclerView 類的定義:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3 { // ...... }
能夠看到 RecyclerView 是一個 ViewGroup,也就是說,RecyclerView 本質是一個自定義 view,須要本身去管理繪製流程。對於瞭解自定義 View 來講,其實就是須要重寫 onMeasure 方法。
在 Android 自定義 View 詳解 一文中總結了 onMeausre 的具體邏輯,到這裏,依然能夠作個參考:
super.onMeasure 會先計算自定義 view 的大小;
自定義 view 設置的寬高參數不是 MeasureSpec.EXACTLY 的話,對於子 View 是 match_parent 須要額外處理,同時也須要對 MeasureSpec.AT_MOST 狀況進行額外處理。
當自定義View 的大小肯定後,在對子 View 是 match_parent 從新測量;
下面來看下 RecyclerView 的 onMeausre 代碼:
protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 第一種狀況 } if (mLayout.isAutoMeasureEnabled()) { // 第二種狀況 } else { // 第三種狀況 } }
onMeasure
方法仍是有點長,這裏我將它分爲3種狀況,我將簡單解釋這三種狀況:
mLayout
即 LayoutManager
的對象。咱們知道,當 RecyclerView
的 LayoutManager
爲空時,RecyclerView
不能顯示任何的數據,在這裏咱們找到答案。
LayoutManager
開啓了自動測量時,這是一種狀況。在這種狀況下,有可能會測量兩次。
第三種狀況就是沒有開啓自動測量的狀況,這種狀況比較少,由於 RecyclerView 爲了
支持 warp_content
屬性,系統提供的 LayoutManager
都開啓自動測量的,不過仍是要分析的。
首先咱們來第一種狀況。
這種狀況下比較簡單,咱們來看看源碼:
if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; }
這裏是調用了 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); }
在 defaultOnMeasure
方法裏面,主要是經過 LayoutManager
的 chooseSize
方法來計算寬高,最後調用 setMeasuredDimension
方法來設置寬高。下面來看下 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); } }
這裏主要是根據不一樣的設置,來返回最終的大小。這塊邏輯不是很懂的讀者能夠閱讀前面提到的文章,裏面詳細解讀了。可是這裏有個問題須要指出來的就是沒有測量子 view 的大小,這也是白屏的緣由。由於 RecyclerView 的繪製實際上是委託給 LayoutManager 來管理呢,LayoutManager = null 的狀況下測量子 view 沒有任何的意義。
在分析這種狀況以前,咱們先對了解幾個東西。
RecyclerView
的測量分爲兩步,分別調用 dispatchLayoutStep1
和 dispatchLayoutStep2
。同時,瞭解過 RecyclerView
源碼的同窗應該知道在 RecyclerView
的源碼裏面還一個dispatchLayoutStep3
方法。這三個方法的方法名比較接近,因此容易讓人搞混淆。本文會詳細的講解這三個方法的做用。
因爲在這種狀況下,只會調用 dispatchLayoutStep1
和 dispatchLayoutStep2
這兩個方法,因此這裏會重點的講解這兩個方法。而 dispatchLayoutStep3
方法的調用在RecyclerView
的 onLayout
方法裏面,因此在後面分析 onLayout
方法時再來看 dispatchLayoutStep3
方法。
咱們在分析以前,先來看一個東西 —— mState.mLayoutStep
。這個變量有幾個取值狀況。咱們分別來看看:
取值 | 含義 |
---|---|
State.STEP_START |
|
State.STEP_LAYOUT |
當 |
State.STEP_ANIMATIONS |
當 |
從上表中,咱們瞭解到 mState.mLayoutStep
的三個狀態對應着不一樣的 dispatchLayoutStep
方法。這一點,咱們必須清楚,不然接下來的代碼將難以理解。
if (mLayout.isAutoMeasureEnabled()) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); /** * This specific call should be considered deprecated and replaced with * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could * break existing third party code but all documentation directs developers to not * override {@link LayoutManager#onMeasure(int, int)} when * {@link LayoutManager#isAutoMeasureEnabled()} returns true. */ 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(); } // set dimensions in 2nd step. Pre-layout should happen with old dimensions for // consistency mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true;
// 第二次 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. 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); } }
首先,咱們來看看 onMeasure
方法。
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); }
RecyclerView
的
defaultOnMeasure
方法,其實就是前面咱們介紹過的自定義 View 的三個步驟:先是測量本身自己的大小。
/** * 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();
// 處理 adapter 更新 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); // This is NOT the only place where a ViewHolder is added to old change holders // list. There is another case where: // * A VH is currently hidden but not deleted // * The hidden item is changed in the adapter // * Layout manager decides to layout the item in the pre-Layout pass (step1) // When this case is detected, RV will un-hide that view and add to the old // change holders list. mViewInfoStore.addToOldChangeHolders(key, holder); } } } 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. saveOldPositions(); final boolean didStructureChange = mState.mStructureChanged; mState.mStructureChanged = false; // temporarily disable flag because we are asking for previous layout mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = didStructureChange; for (int i = 0; i < mChildHelper.getChildCount(); ++i) { final View child = mChildHelper.getChildAt(i); final ViewHolder viewHolder = getChildViewHolderInt(child); if (viewHolder.shouldIgnore()) { continue; } if (!mViewInfoStore.isInPreLayout(viewHolder)) { int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); boolean wasHidden = viewHolder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (!wasHidden) { flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; } final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); if (wasHidden) { recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); } else { mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); } } } // we don't process disappearing list because they may re-appear in post layout pass. clearOldPositions(); } else { clearOldPositions(); } onExitLayoutOrScroll(); stopInterceptRequestLayout(false);
// 上面的執行完之後,改變狀態 mState.mLayoutStep = State.STEP_LAYOUT; }
其實最上面的方法註釋,已經把這個方法所作的事情都總結好了,該方法主要工做以下:
處理 Adapter
更新;
決定執行哪種動畫
保存每一個 ItemView
的信息
有必要的話,會進行預佈局,並把相關信息保存下來。
能夠看到整個方法內部調用的方法仍是不少,致使你會以爲這個方法的邏輯很複雜。不過既然是源碼閱讀,我們只關注一些重要的點,在衆多被調用的方法中 processAdapterUpdatesAndSetAnimationFlags 是須要點進去看看裏面的邏輯的,後續的 if else 邏輯其實都是在該方法裏面決定的。
/** * Consumes adapter updates and calculates which type of animations we want to run. * Called in onMeasure and dispatchLayout. * <p> * This method may process only the pre-layout state of updates or all of them. */ 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); } } // simple animations are a subset of advanced animations (which will cause a // pre-layout step) // If layout supports predictive animations, pre-process to decide if we want to run them if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); } boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && !mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled(); }
mFirstLayoutComplete
變量裏面,咱們發現
mRunSimpleAnimations
的值與
mFirstLayoutComplete
有關,
mRunPredictiveAnimations
同時跟
mRunSimpleAnimations
有關。因此這裏咱們能夠得出一個結論,當
RecyclerView
第一次加載數據時,是不會執行的動畫?那到底會不會呢,這裏先賣個關子。
dispatchLayoutStep2
方法,這個方法是真正佈局
children
。上代碼:
/** * 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(); mState.mItemCount = mAdapter.getItemCount(); mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; // Step 2: Run layout 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); }
能夠看到的是,這裏的邏輯彷佛簡單不少,那是由於這裏把對子 view 的繪製邏輯放到 LayoutManager 中去了。到這裏,state 的狀態已經改變了,變成了 State.STEP_LAYOUT | State.STEP_ANIMATIONS。
系統的 LayoutManager
的 onLayoutChildren
方法是一個空方法,因此須要 LayoutManager
的子類本身來實現。
這裏先不作過多介紹,不一樣的 LayoutManager 有不一樣的實現。
仍是先來看看這一塊的代碼:
{ if (mHasFixedSize) { mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } // custom onMeasure if (mAdapterUpdateDuringMeasure) { startInterceptRequestLayout(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); onExitLayoutOrScroll(); if (mState.mRunPredictiveAnimations) { mState.mInPreLayout = true; } else { // consume remaining updates to provide a consistent state with the layout pass. mAdapterHelper.consumeUpdatesInOnePass(); mState.mInPreLayout = false; } mAdapterUpdateDuringMeasure = false; stopInterceptRequestLayout(false); } else if (mState.mRunPredictiveAnimations) { // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: // this means there is already an onMeasure() call performed to handle the pending // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time // because getViewForPosition() will crash when LM uses a child to measure. 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
的 onMeasure
方法來進行測量。
若是mHasFixedSize
爲true(也就是調用了setHasFixedSize
方法),將直接調用LayoutManager
的onMeasure
方法進行測量。
若是mHasFixedSize
爲false,同時此時若是有數據更新,先處理數據更新的事務,而後調用LayoutManager
的onMeasure
方法進行測量
到這裏,關於測量的邏輯就講完了,接下去開始看 layout 邏輯:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; }
首先會調用 dispatchLayout 進行 layout 操做,能夠看到前面關注過的一個變量 mFirstLayoutComplete 賦值變爲 true 。
下面主要看 dispatchLayout 方法:
void dispatchLayout() { if (mAdapter == null) { Log.e(TAG, "No adapter attached; skipping layout"); // leave the state in START return; } if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START 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(); }
dispatchLayout
方法也是很是的簡單,這個方法保證 RecyclerView
必須經歷三個過程 —— dispatchLayoutStep1
、dispatchLayoutStep2
、dispatchLayoutStep3
。同時,若是在這時候,發現子 view 寬高參數發生變化後,會再次調用 dispatchLayoutStep2() 方法。
最後,來看下千呼萬喚使出來的 dispatchLayoutStep3 方法:
/** * The final step of the layout where we save the information about views for animations, * trigger animations and do any necessary cleanup. */ private void dispatchLayoutStep3() {
// 動畫 mState.assertLayoutStep(State.STEP_ANIMATIONS); startInterceptRequestLayout(); onEnterLayoutOrScroll();
// 標記進行復位 mState.mLayoutStep = State.STEP_START; 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. for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); if (holder.shouldIgnore()) { continue; } long key = getChangedHolderKey(holder); final ItemHolderInfo animationInfo = mItemAnimator .recordPostLayoutInformation(mState, holder); ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { // run a change animation // If an Item is CHANGED but the updated version is disappearing, it creates // a conflicting case. // Since a view that is marked as disappearing is likely to be going out of // bounds, we run a change animation. Both views will be cleaned automatically // once their animations finish. // On the other hand, if it is the same view holder instance, we run a // disappearing animation instead because we are not going to rebind the updated // VH unless it is enforced by the layout manager. 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 作動畫 mViewInfoStore.process(mViewInfoProcessCallback); } mLayout.removeAndRecycleScrapInt(mRecycler);
// 記錄數據,並把以前用到一些標誌位復位 mState.mPreviousLayoutItemCount = mState.mItemCount; mDataSetHasChangedAfterLayout = false; mDispatchItemsChangedEvent = false; mState.mRunSimpleAnimations = false; mState.mRunPredictiveAnimations = false; mLayout.mRequestedSimpleAnimations = false; if (mRecycler.mChangedScrap != null) { mRecycler.mChangedScrap.clear(); } if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { // Initial prefetch has expanded cache, so reset until next prefetch. // This prevents initial prefetches from expanding the cache permanently. mLayout.mPrefetchMaxCountObserved = 0; mLayout.mPrefetchMaxObservedInInitialPrefetch = false; mRecycler.updateViewCacheSize(); } mLayout.onLayoutCompleted(mState); onExitLayoutOrScroll(); stopInterceptRequestLayout(false); mViewInfoStore.clear();
// if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { dispatchOnScrolled(0, 0); } recoverFocusFromState(); resetFocusInfo(); }
從上面的邏輯能夠看出 dispatchLayoutStep3
主要是作 Item 的動畫,本文不對動畫進行展開,因此先省略動畫部分。而後就是對一些標誌位復位。清除一些狀態。
這裏對這三個方法作一個小結,方便你們記住這幾個方法的做用:
方法名 | 做用 |
---|---|
dispatchLayoutStep1 | 本方法的做用主要有三點:
|
dispatchLayoutStep2 | 在這個方法裏面,真正進行 children 的測量和佈局。 |
dispatchLayoutStep3 | 這個方法的做用執行在 dispatchLayoutStep1 方法裏面保存的動畫信息。本方法不是本文的介紹重點 |
接下來,咱們來分析三大流程的最後一個階段 —— draw。
下面來看看 RecyclerView 的 draw() 和 onDraw() 方法:
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); } // ...... }
真是考慮周到啊。
@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); } }
發現這裏並無作太多,只是調用 ItemDecoration
的 onDraw 和
onDrawOver 方法。這樣就將分割線添加到其中。
dispatchLayoutStep2
方法時,只是簡單的介紹了,
RecyclerView
經過調用
LayoutManager
的
onLayoutChildren
方法。
LayoutManager
自己對這個方法沒有進行實現,因此必須得看看它的子類,這裏以 LinearLayoutManager 來舉例說明:
@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { // ...... ensureLayoutState(); mLayoutState.mRecycle = false; // resolve layout direction resolveShouldLayoutReverse();
// ......
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
// 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 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtraFillSpace = 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.mExtraFillSpace = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { extraForEnd = mLayoutState.mAvailable; // start could not consume all it should. add more items towards end updateLayoutStateToFillEnd(lastElement, endOffset); mLayoutState.mExtraFillSpace = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; } } layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); if (!state.isPreLayout()) { mOrientationHelper.onLayoutComplete(); } else { mAnchorInfo.reset(); } mLastStackFromEnd = mStackFromEnd; if (DEBUG) { validateChildOrder(); } }
onLayoutChildren 方法很長,所以省略一些無關的代碼。其實主要是作兩件事肯定錨點的信息,這裏面的信息包括:
1Children
的佈局方向,有 start 和 end 兩個方向;
mPosition
和mCoordinate
,分別表示 Children
開始填充的 position 和座標。
根據錨點信息,調用 fill
方法進行 Children
的填充。這個過程當中根據錨點信息的不一樣,可能會調用兩次 fill
方法。
要想看錨點信息的計算過程,咱們能夠從 updateAnchorInfoForLayout
方法裏面來找出答案,咱們來看看 updateAnchorInfoForLayout
方法:
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; }
我相信經過上面的代碼註釋,你們都能明白 updateAnchorInfoForLayout
方法到底幹了嘛,這裏我簡單分析一下這三種肯定所作的含義,具體是怎麼作的,這裏就不討論。
第一種計算方式,表示含義有兩種:1. RecyclerView
被重建,期間回調了 onSaveInstanceState
方法,因此目的是爲了恢復上次的佈局;2. RecyclerView
調用了scrollToPosition
之類的方法,因此目的是讓
RecyclerView
滾到準確的位置上去。因此,錨點的信息根據上面的兩種狀況來計算。
第二種計算方法,從C hildren
上面來計算錨點信息。這種計算方式也有兩種狀況:1. 若是當前有擁有焦點的 Child
,那麼有當前有焦點的 Child 的位置來計算錨點;2. 若是沒有 child 擁有焦點,那麼根據佈局方向(此時佈局方向由 mLayoutFromEnd
來決定)獲取可見的第一個 ItemView
或者最後一個 ItemView
。
若是前面兩種方式都計算失敗了,那麼採用第三種計算方式,也就是默認的計算方式。
而後就是調用 fill
方法來填充 Children
。在正式分析填充過程時,咱們先來看一張圖片:
上圖形象的展示出三種fill
的狀況。其中,咱們能夠看到第三種狀況,fill
方法被調用了兩次。
咱們看看 fill
方法:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // ······ while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { // ······ layoutChunk(recycler, state, layoutState, layoutChunkResult); } // ······ }
fill
方法的代碼比較長,其實都是來計算可填充的空間,真正填充 Child
的地方是 layoutChunk
方法。咱們來看看 layoutChunk
方法。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next(recycler); ... if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { addView(view, 0); } } ... measureChildWithMargins(view, 0, 0); ... // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin); ... }
提醒下別小看這個 next
方法,RecyclerView
緩存機制的起點就是從這個方法開始,可想而知,這個方法到底爲咱們作了多少事情。
這裏的 addView() 方法,其實就是 ViewGroup 的 addView() 方法;measureChildWithMargins() 方法看名字就知道是用於測量子控件大小的,這裏我先跳過這個方法的解釋,放在後面來作,目前就簡單地理解爲測量子控件大小就行了。下面是 layoutDecoreated() 方法:
public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
// 將分割線考慮進去 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom); }
總結上面代碼,在 RecyclerView 的 measure 及 layout 階段,填充 ItemView 的算法爲:向父容器增長子控件,測量子控件大小,佈局子控件,佈局錨點向當前佈局方向平移子控件大小,重複上訴步驟至 RecyclerView 可繪製空間消耗完畢或子控件已所有填充。
這樣全部的子控件的 measure 及 layout 過程就完成了。回到 RecyclerView 的 onMeasure 方法,執行 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec) 這行代碼的做用就是根據子控件的大小,設置 RecyclerView 的大小。至此,RecyclerView 的 measure 和 layout 實際上已經完成了。
可是,你有可能已經發現上面過程當中的問題了:如何肯定 RecyclerView 的可繪製空間?不過,若是你熟悉 android 控件的繪製機制的話,這就不是問題。其實,這裏的可繪製空間,能夠簡單地理解爲父容器的大小;更準確的描述是,父容器對 RecyclerView 的佈局大小的要求,能夠經過 MeasureSpec.getSize() 方法得到。
到這裏,關於 RecyclerView 的繪製流程就講完了,因爲主打繪製流程,沒有分析其餘,可能會致使整個邏輯有些跳躍,但不妨礙理解整個繪製過程。
最後回到文章前面的問題上,能夠發現 RecyclerView 將繪製過程實際上是委託給 layoutManager 來操做,這和普通自定義 view 是很不同的。這樣的靈活操做,可讓使用者自定義各類樣式,使得 RecyclerView 使用場景變得更加豐富。
其次在於分割線的處理上,它並不把分割線當作是子 view 來處理,而是在佈局子 view 的時候,將分割線考慮進去給留下間隙。