作者:opLW
參考:RecyclerView全面的源碼解析
最近用RecyclerView用的很多,打算寫一系列相關的文章,做一個總結,加深理解。? 有什麼不對還望指出。(RV代表RecyclerView, LM代表LayoutManager)
目錄
1.onMeasure方法
2.onLayout方法
3.onDraw方法
前言: 提到繪製流程,那麼離不開三個方法onMeasure,onLayout,onDraw。下面按照這個思路整理一下RecyclerView的繪製流程。不熟悉Android view繪製機制的可以自行百度或者查閱《Android開發藝術探索》第四章。
1.onMeasure方法
-
1)大體流程:
//RecyclerView @Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 沒有設置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { // 2 mAutoMeasure爲true } else { // 3 mAutoMeasure爲false } } //RecyclerView#LayoutManager public boolean isAutoMeasureEnabled() { return mAutoMeasure; }
-
2)沒有設置LayoutManager
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); // 4 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); }
- 4 在沒有設置LayoutManager的情況下,RV會調用
defaultOnMeasure
測量並設置自身的大小,然後return
,跳出onMeasure方法。 - 感覺好像有點不對,測量完自身之後不是應該測量子view????。這點與我們所認知的不同,所以
LayoutManager
是RV的必要成分體現出來了,沒有LayoutManager
的話一切免談。
- 4 在沒有設置LayoutManager的情況下,RV會調用
-
-
A 什麼是AutoMeasure 根據RV源碼註釋簡單說下,
AutoMeasure
是RV的一種機制,RV會在調用onMeasure
時,同時調用LayoutManager
的onLayoutChildren
方法,來獲取子view
的大小和位置並在測量好子view之後再來設置RV的尺寸,以此來更好的支持RV的動畫效果。當AutoMeasure
取值爲true時,會使用這個機制。系統自帶的三個LayoutManager
默認設置爲true,當我們需要自定義測量工作時,需要將AutoMeasure
設置爲false,並且重寫LayoutManager
的onMeasure
方法。 -
B mState.mLayoutStep的三種取值以及三個dispatchLayoutStep 顧名思義,這個變量記錄了當前執行到的步驟,下面看看它的取值:
取值 含義 State.STEP_START 代表尚未執行dispatchLayoutStep1() State.STEP_LAYOUT 代表執行了dispatchLayoutStep1(),尚未執行dispatchLayoutStep2() State.STEP_ANIMATIONS 代表執行了1和2,尚未執行dispatchLayoutStep3() dispatchLayoutStep1() 源代碼很多,這裏根據註釋簡要說明這一步做了什麼:1.處理Adapter的更新;2.決定做哪些動畫;3.保存一些相關的信息;4.必要的話,執行預佈局並保存信息。執行之後更新mState.mLayoutStep爲State.STEP_LAYOUT dispatchLayoutStep2() 最重要的步驟,這個方法真正的執行了對子View的測量和佈局工作。執行之後更新mState.mLayoutStep爲State.STEP_ANIMATIONS dispatchLayoutStep3() 這個步驟根據之前保存的動畫信息,觸發相應的動畫效果。 -
C AutoMeasure爲true時所作的工作
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 沒有設置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); //省略註釋 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 5 final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; // 6 } if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); // 7 } mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2(); // 8 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9 if (mLayout.shouldMeasureTwice()) { mLayout.setMeasureSpecs( MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); mState.mIsMeasuring = true; dispatchLayoutStep2(); mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } else { // 3 mAutoMeasure爲false } }
- D 5 看到標識5處,疑惑來了???不是說設置AutoMeasure爲false時,纔會調用LM的onMeasure方法嗎?
上面是源碼中對5的註釋,提到了本來應該用前面提到的RV的/** * 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.*/
defaultOnMeasure
方法代替的,但是可能會影響到一些第三方代碼,所以沒有。而且官文文檔已經很好的引導了開發者,在AutoMeasure
爲true時不要重寫LM的onLayout
方法,我們看下面的源碼,可以知道LM的onMeasure
方法的默認實現是調用的RV的defaultOnMeasure
。所以最終調用的還是RV的defaultOnMeasure
。這裏有點不明白,如何影響到第三方代碼,如有知道還望留言賜教。?
//RecyclerView#LayoutManager public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); }
- E 6 下面是標識6的代碼,可以看出當RV的大小可以確定時,直接返回不再根據子View的大小來設置自身的尺寸。那什麼時候RV的大小可以確定呢?就是當RV的大小設置爲明確數字或者match_parent時。
final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; // 6 }
- F 7 在mState.mLayoutStep爲State.STEP_START時執行了
dispatchLayoutStep1
。 通過前面的表格對三個步驟有了大體的瞭解。這裏爲了不脫離主線和簡單,只重點介紹dispatchLayoutStep2
。
- G 8 調用
dispatchLayoutStep2
private void dispatchLayoutStep2() { startInterceptRequestLayout(); onEnterLayoutOrScroll(); mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); mAdapterHelper.consumeUpdatesInOnePass(); mState.mItemCount = mAdapter.getItemCount(); // 8.1 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; // Step 2: Run layout mState.mInPreLayout = false; // 8.2 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); }
startInterceptRequestLayout()
和stopInterceptRequestLayout(false)
成對出現,主要作用是防止在layout的過程中,某一個子View觸發了onRequestLayout
方法,從而導致多餘的layout操作。- 這段代碼主要獲取了子View的個數(8.1)以及調用LM的
onLayoutChildren
(8.2)對子View進行測量和佈局。此處也體現了RV的強大,將佈局抽離出來交給LM管理,從而使得可以靈活的實現各種佈局。後面會有文章介紹自定義LM。這種思想值得學習,應用到實際的代碼編寫中。?
- H 9 根據最後的測量結果設置RV的大小。
//RV#onMeasure mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9 //RV#LM#setMeasuredDimensionFromChildren void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { final int count = getChildCount(); if (count == 0) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); return; } // 9.1 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); // 9.2 }
- (9.1) 省略了代碼,主要是根據前面對子View的測量和佈局計算RV的大小(9.2);
- I 總結 自此mAutoMeasure爲true的情況結束。主要是判斷RV的尺寸是否爲EXACTLY,是則直接設置,否則調用
dispatchLayoutStep1
和dispatchLayoutStep2
進行信息的初始化計算,對子View的測量和佈局以及根據計算的結果設置RV的大小。下面看看mAutoMeasure爲false的情況。
-
-
4)mAutoMeasure爲false
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 沒有設置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { // 2 mAutoMeasure爲true } else { if (mHasFixedSize) { // 10 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } // 省略部分更新操作 if (mAdapter != null) { mState.mItemCount = mAdapter.getItemCount(); } else { mState.mItemCount = 0; } startInterceptRequestLayout(); mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 11 stopInterceptRequestLayout(false); mState.mInPreLayout = false; // clear } }
- 10 是RV留給開發者的一個優化點,可以根據需要將此值設爲true,來減少不必要的測量工作從而達到優化,感興趣的同學可自行百度。
- 11 此處調用LM
onMeasure
進行測量,可以是自定義的也可以是默認的方法。默認的方法會調用RV的defaultOnMeasure
。
-
5)總結onMeasure mAutoMeasure爲true時,會涉及到子View的測量和佈局(在LM的onLayoutChildren方法裏)。而爲false時,單純的測量並沒有佈局。那麼就有佈局和沒佈局的區別了,如何解決呢?RV在onLayout方法裏處理了這個問題,接着往下看。
2.onLayout方法
-
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); // 12 TraceCompat.endSection(); mFirstLayoutComplete = true; } void dispatchLayout() { if (mAdapter == null) { // 13 Log.e(TAG, "No adapter attached; skipping layout"); // leave the state in START return; } if (mLayout == null) { // 14 Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START return; } mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { // 15 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(); // 16 }
- 12 可以看見最終調用
dispatchLayout
進行佈局工作。 - 13,14 再次檢查
adapter
和LM是否爲空。 - 15 上面提到
onLayout
會解決佈局和沒佈局的區別,此處會進行判斷,從而調用dispatchLayoutStep1()
和dispatchLayoutStep2()
中的一個或兩個進行佈局工作。忘記dispatchLayoutStep2()
是幹嘛的,點擊傳送門 - 16 最後統一調用
dispatchLayoutStep3()
進行動畫相關的工作。 - 在onLayout方法裏,會統一保證相關的layout方法得到調用。
- 12 可以看見最終調用
3.onDraw方法
-
public void draw(Canvas c) { super.draw(c); // 17 final int count = mItemDecorations.size(); // 18 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(); // 18 for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } }
- 17 RV重寫了
View.draw
方法,在17處調用View
的draw
方法進行子View繪製的分發。 - 18 兩處18看出在RV在
draw
的過程中,調用了ItemDecoration
的兩個draw
相關的方法,此處也是RV的一個亮點,抽離出了ItemDecoration
,方便製作更加漂亮的界面。詳細ItemDecoration
自行查看相關文章。
- 17 RV重寫了
總結
RV對繪製相關的內容進行了完整編寫的,同時也留下許多方法供我們自定義,很是靈活。但也很複雜,值得深入理解,後面還會有其他相關的文章介紹。RecyclerView (二) – 緩存複用機制(上)
萬水千山總是情,麻煩手下別留情。
如若講得有不妥,文末留言告知我,
如若覺得還可以,收藏點贊要一起。
opLW原創七言律詩,轉載請註明出處