從今天開始,樓主正式開始分析RecyclerView
的源碼。爲了閱讀RecyclerView
的源碼,樓主專門去看了View
的三大流程,也就是所謂的刷裝備。固然在閱讀RecyclerView
的源碼時,也參考了其餘大佬的文章,本文儘量的貼出比較優秀的文章,正所謂他山之石,能夠攻玉。緩存
做爲系列的第一篇文章,說說樓主爲何須要來專門的閱讀RecyclerView
的源碼,主要從三大方面提及。一是RecyclerView
在實際開發很是的重要,如今幾乎每一個app都會展現不少的數據,列表展現天然是很是好的方式,而在RecyclerView
在列表中佔據着舉足輕重的做用,因此RecyclerView
在實際開發中,是常常見的,咱們得之魚,還必須得之漁;二是如今網上分析RecyclerView的文章滿篇飛,可是文章大多都比較零碎,沒有系統的分析RecyclerView
,本文打算系統的分析RecyclerView
,也能夠說是集百家之長;三是樓主自己對RecycleView
的使用也是很是的頻繁,可是沒有深刻的瞭解它的原理,因此這也算是對自身的一個提高。bash
閱讀源碼自己是一件很是枯燥和耗時間的事情,對樓主自身來講,也是亞歷山大,懼怕本身自身的經驗不足,誤導前來學習的同窗,因此若是文章中有錯誤的地方,請各位大佬指點。微信
本系列文章樓主打算從幾個地方提及。先是將RecyclerView
當成一個普通的View
,分別分析它的三大流程、事件傳遞(包括嵌套滑動);而後是分析RecyclerView
的緩存原理,這也是RecyclerView
的精華所在;而後分析的是RecyclerView
的Adapter
、LayoutManager
、ItemAnimator
和ItemDecoration
。最後就是RecyclerView
的擴展,包括LayoutManager
的自定義和使用RecyclerView
常見的坑等。架構
看到上面所寫的列表,本身也不由留下冷汗,原來RecyclerView
有這麼多的內容,真擔憂本身不能完成任務😂。app
在分析RecyclerView
源碼以前,咱們仍是對RecyclerView
有一個初步的瞭解,簡單的瞭解它是什麼,它的基本結構有哪些。ide
RecyclerView
是Google爸爸在2014年的IO大會提出來(看來RecyclerView
的年齡仍是比較大了😂),具體目的是否是用來替代ListView
的,樓主也不知道,由於那時候樓主還在讀高二。可是在實際開發中,自從有了RecyclerView
,ListView
和GridView
就不多用了,因此咱們暫且認爲RecyclerView
的目的是替代ListView
和GridView
。佈局
RecyclerView
自己是一個展現大量數據的控件,相比較ListView
,RecyclerView
的4級緩存(也有人說是3級緩存,這些都不重要😂)就表現的很是出色,在性能方面相比於ListView
提高了很多。同時因爲LayoutManager
的存在,讓RecyclerView
不只有ListView
的特色,同時兼有GridView
的特色。這多是RecyclerView
受歡迎的緣由之一吧。性能
RecyclerView
在設計方面上也是很是的靈活,不一樣的部分承擔着不一樣的職責。其中Adapter
負責提供數據,包括建立ViewHolder
和綁定數據,LayoutManager
負責ItemView
的測量和佈局,ItemAnimator
負責每一個ItemView
的動畫,ItemDecoration
負責每一個ItemView
的間隙。這種插拔式的架構使得RecyclerView
變得很是的靈活,每個人均可以根據自身的需求來定義不一樣的部分。學習
正由於這種插拔式的設計,使得RecyclerView
在使用上相比較於其餘的控件稍微難那麼一點點,不過這都不算事,誰叫RecyclerView
這麼惹人愛呢😂。動畫
好了,好像廢話有點多,如今咱們正式來分析源碼吧,本文的重點是RecyclerView
的三大流程。
本文參考文章:
注意,本文RecyclerView
源碼均來自於27.1.1
無論RecyclerView
是多麼神奇,它也是一個View
,因此分析它的三大流程是很是有必要的。同時,若是瞭解過RecyclerView
的同窗應該都知道,RecyclerView
的三大流程跟普通的View
比較,有很大的不一樣。
首先,咱們來看看measure過程,來看看RecyclerView
的onMeasure
方法。
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
方法,咱們繼續來看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
方法來設置寬高。咱們來看看:
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
方法表達的意思比較簡單,就是經過RecyclerView
的測量mode來獲取不一樣的值,這裏就不詳細的解釋了。
到此,第一種狀況就分析完畢了。由於當LayoutManager
爲空時,那麼當RecyclerView
處於onLayout
階段時,會調用dispatchLayout
方法。而在dispatchLayout
方法裏面有這麼一行代碼:
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
複製代碼
因此,當LayoutManager
爲空時,不顯示任何數據是理所固然的。
如今咱們來看看第二種狀況,也就是正常的狀況。
在分析這種狀況以前,咱們先對了解幾個東西。
RecyclerView
的測量分爲兩步,分別調用dispatchLayoutStep1
和dispatchLayoutStep2
。同時,瞭解過RecyclerView
源碼的同窗應該知道在RecyclerView
的源碼裏面還一個dispatchLayoutStep3
方法。這三個方法的方法名比較接近,因此容易讓人搞混淆。本文會詳細的講解這三個方法的做用。
因爲在這種狀況下,只會調用dispatchLayoutStep1
和dispatchLayoutStep2
這兩個方法,因此這裏會重點的講解這兩個方法。而dispatchLayoutStep3
方法的調用在RecyclerView
的onLayout
方法裏面,因此在後面分析onLayout
方法時再來看dispatchLayoutStep3
方法。
咱們在分析以前,先來看一個東西--mState.mLayoutStep
。這個變量有幾個取值狀況。咱們分別來看看:
取值 | 含義 |
---|---|
State.STEP_START | mState.mLayoutStep 的默認值,這種狀況下,表示RecyclerView還未經歷dispatchLayoutStep1 ,由於dispatchLayoutStep1 調用以後mState.mLayoutStep 會變爲State.STEP_LAYOUT 。 |
State.STEP_LAYOUT | 當mState.mLayoutStep 爲State.STEP_LAYOUT 時,表示此時處於layout階段,這個階段會調用dispatchLayoutStep2 方法layout RecyclerView 的children 。調用dispatchLayoutStep2 方法以後,此時mState.mLayoutStep 變爲了State.STEP_ANIMATIONS 。 |
State.STEP_ANIMATIONS | 當mState.mLayoutStep 爲State.STEP_ANIMATIONS 時,表示RecyclerView 處於第三個階段,也就是執行動畫的階段,也就是調用dispatchLayoutStep3 方法。當dispatchLayoutStep3 方法執行完畢以後,mState.mLayoutStep 又變爲了State.STEP_START 。 |
從上表中,咱們瞭解到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);
}
}
複製代碼
我將這段代碼分爲三步。咱們來看看:
- 調用
LayoutManager
的onMeasure
方法進行測量。對於onMeasure
方法,我也感受到很是的迷惑,發現傳統的LayoutManager
都沒有實現這個方法。後面,咱們會將簡單的看一下這個方法。- 若是
mState.mLayoutStep
爲State.STEP_START
的話,那麼就會執行dispatchLayoutStep1
方法,而後會執行dispatchLayoutStep2
方法。- 若是須要第二次測量的話,會再一次調用
dispatchLayoutStep2
方法。
以上三步,咱們一步一步的來分析。首先,咱們來看看第一步,也是看看onMeasure
方法。
LayoutManager
的onMeasure
方法究竟爲咱們作什麼,咱們來看看:
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
複製代碼
默認是調用的RecyclerView
的defaultOnMeasure
方法,至於defaultOnMeasure
方法裏面究竟作了什麼,這在前面已經介紹過了,這裏就再也不介紹了。
View
的onMeasure
方法的做用通產來講有兩個。一是測量自身的寬高,從RecyclerView
來看,它將本身的測量工做託管給了LayoutManager
的onMeasure
方法。因此,咱們在自定義LayoutManager
時,須要注意onMeasure
方法存在,不過從官方提供的幾個LayoutManager
,都沒有重寫這個方法。因此不到萬得已,最好不要重寫LayoutManager
的onMeasure
方法;二是測量子View
,不過到這裏咱們尚未看到具體的實現。
接下來,咱們來分析第二步,看看dispatchLayoutStep1
方法和dispatchLayoutStep2
方法究竟作了什麼。
在正式分析第二步以前,咱們先對這三個方法有一個大概的認識。
方法名 | 做用 |
---|---|
dispatchLayoutStep1 | 三大dispatchLayoutStep 方法第一步。本方法的做用主要有三點:1.處理Adapter 更新;2.決定是否執行ItemAnimator ;3.保存ItemView 的動畫信息。本方法也被稱爲preLayout(預佈局),當Adapter 更新了,這個方法會保存每一個ItemView 的舊信息(oldViewHolderInfo) |
dispatchLayoutStep2 | 三大dispatchLayoutStep 方法第二步。在這個方法裏面,真正進行children 的測量和佈局。 |
dispatchLayoutStep3 | 三大dispatchLayoutStep 方法第三步。這個方法的做用執行在dispatchLayoutStep1 方法裏面保存的動畫信息。本方法不是本文的介紹重點,後面在介紹ItemAnimator 時,會重點分析這個方法。 |
咱們回到onMeasure方法裏面,先看看整個執行過程。
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();
複製代碼
若是mState.mLayoutStep == State.STEP_START
時,纔會調用 dispatchLayoutStep1
方法,這裏與咱們前面介紹mLayoutStep
對應起來了。如今咱們看看dispatchLayoutStep1
方法
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) {
// 找到沒有被remove的ItemView,保存OldViewHolder信息,準備預佈局
}
if (mState.mRunPredictiveAnimations) {
// 進行預佈局
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
複製代碼
本文只簡單分析一下這個方法,由於這個方法跟ItemAnimator
有莫大的關係,後續在介紹ItemAnimator
時會詳細的分析。在這裏,咱們將重點放在processAdapterUpdatesAndSetAnimationFlags
裏面,由於這個方法計算了mRunSimpleAnimations
和mRunPredictiveAnimations
。
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
第一次加載數據時,是不會執行的動畫。換句話說,每一個ItemView
尚未layout
完畢,怎麼會進行動畫。這一點,咱們也能夠經過Demo來證實,這裏也就不展現了。
接下來咱們看看dispatchLayoutStep2
方法,這個方法是真正佈局children
。咱們來看看:
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);
}
複製代碼
在這裏,咱們重點的看兩行代碼。一是在這裏,咱們能夠看到Adapter
的getItemCount
方法被調用;二是調用了LayoutManager
的onLayoutChildren
方法,這個方法裏面進行對children
的測量和佈局,同時這個方法也是這裏的分析重點。
系統的LayoutManager
的onLayoutChildren
方法是一個空方法,因此須要LayoutManager
的子類本身來實現。從這裏,咱們能夠得出兩個點。
- 子類
LayoutManager
須要本身實現onLayoutChildren
方法,從而來決定RecyclerView
在該LayoutManager
的策略下,應該怎麼佈局。從這裏,咱們看出來RecyclerView
的靈活性。LayoutManager
相似於ViewGroup
,將onLayoutChildren
方法(ViewGroup
是onLayout
方法)公開出來,這種模式在Android中很常見的。
這裏,我先不對onLayoutChildren
方法進行展開,待會會詳細的分析。
接下來,咱們來分析第三種狀況--沒有開啓自動測量。
咱們先來看看這一塊的代碼。
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
複製代碼
例如上面的代碼,我將分爲2步:
- 若是
mHasFixedSize
爲true(也就是調用了setHasFixedSize
方法),將直接調用LayoutManager
的onMeasure
方法進行測量。- 若是
mHasFixedSize
爲false,同時此時若是有數據更新,先處理數據更新的事務,而後調用LayoutManager
的onMeasure
方法進行測量
經過上面的描述,咱們知道,若是未開啓自動測量,那麼確定會調用LayoutManager
的onMeasure
方法來進行測量,這就是LayoutManager
的onMeasure
方法的做用。
至於onMeasure
方法怎麼進行測量,那就得看LayoutManager
的實現類。在這裏,咱們就不進行深刻的追究了。
measure
過程分析的差很少了,接下來咱們就該分析第二個過程--layout
。咱們來看看onLayout
方法:
@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;
}
複製代碼
onLayout
方法自己沒有作多少的事情,重點仍是在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
。
同時,在後面的文章中,你會看到dispatchLayout
方法其實還爲RecyclerView
節省了不少步驟,也就是說,在RecyclerView
經歷一次完整的dispatchLayout
以後,後續若是參數有所變化時,可能只會經歷最後的1步或者2步。固然這些都是後話了😂。
對於dispatchLayoutStep1
和dispatchLayoutStep2
方法,咱們前面已經講解了,這裏就不作過多的解釋了。這裏,咱們就簡單的看一下dispatchLayoutStep3
方法吧。
private void dispatchLayoutStep3() {
// ······
mState.mLayoutStep = State.STEP_START;
// ······
}
複製代碼
爲何這裏只是簡單看一下dispatchLayoutStep3
方法呢?由於這個方法主要是作Item的動畫,也就是咱們熟知的ItemAnimator
的執行,而本文不對動畫進行展開,因此先省略動畫部分。
在這裏,咱們須要關注dispatchLayoutStep3
方法的是,它將mLayoutStep
重置爲了State.STEP_START
。也就是說若是下一次從新開始dispatchLayout
的話,那麼確定會經歷dispatchLayoutStep1
、dispatchLayoutStep2
、dispatchLayoutStep3
三個方法。
以上就是RecyclerView
的layout過程,是否是感受很是的簡單?RecyclerView
跟其餘ViewGroup
不一樣的地方在於,若是開啓了自動測量,在measure
階段,已經將Children
佈局完成了;若是沒有開啓自動測量,則在layout
階段才佈局Children
。
接下來,咱們來分析三大流程的最後一個階段--draw
。在正式分析draw過程以前,我先來對RecyclerView
的draw
作一個概述。
RecyclerView
分爲三步,咱們來看看:
- 調用
super.draw
方法。這裏主要作了兩件事:1. 將Children
的繪製分發給ViewGroup
;2. 將分割線的繪製分發給ItemDecoration
。- 若是須要的話,調用
ItemDecoration
的onDrawOver
方法。經過這個方法,咱們在每一個ItemView
上面畫上不少東西。- 若是
RecyclerView
調用了setClipToPadding
,會實現一種特殊的滑動效果--每一個ItemView能夠滑動到padding區域。
咱們來看看這部分的代碼:
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);
}
// 第三步
// 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.
// ······
}
複製代碼
熟悉三大流程的同窗,確定知道第一步會回調到onDraw
方法裏面,也就是說關於Children
的繪製和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);
}
}
複製代碼
onDraw
方法是否是很是的簡單?調用super.onDraw
方法將Children
的繪製分發給ViewGroup
執行;而後將ItemDecoration
的繪製分發到ItemDecoration
的onDraw
方法裏面去。從這裏,咱們能夠看出來,RecyclerView
的設計實在是太靈活了!
至於其他兩步都比較簡單,這裏就不詳細分析了。不過,從這裏,咱們終於明白了ItemDecoration
的onDraw
方法和onDrawOver
方法的區別。
從總體來講,RecyclerView
的三大流程仍是比較簡單,不過在整個過程當中,咱們彷佛忽略了一個過程--那就是RecyclerView
究竟是怎麼layout children
的?
前面在介紹dispatchLayoutStep2
方法時,只是簡單的介紹了,RecyclerView
經過調用LayoutManager
的onLayoutChildren
方法。LayoutManager
自己對這個方法沒有進行實現,因此必須得看看它的子類,這裏咱們就來看看LinearLayoutManager
。
因爲LinearLayoutManager
的onLayoutChildren
方法比較長,這裏不可能貼出完整的代碼,因此這裏我先對這個方法作一個簡單的概述,方便你們理解。
- 肯定錨點的信息,這裏面的信息包括:1.
Children
的佈局方向,有start和end兩個方向;2.mPosition
和mCoordinate
,分別表示Children
開始填充的position和座標。- 調用
detachAndScrapAttachedViews
方法,detach
掉或者remove
掉RecyclerView
的Children
。這一點原本不在本文的講解範圍內,可是爲了後續對RecyclerView
的緩存機制有更好的瞭解,這裏特別的提醒一下。- 根據錨點信息,調用
fill
方法進行Children
的填充。這個過程當中根據錨點信息的不一樣,可能會調用兩次fill
方法。
接下來,咱們看看代碼:
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.
// create layout state
// ······
// 第一步
final View focused = getFocusedChild();
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;
}
// ······
// 第二步
detachAndScrapAttachedViews(recycler);
mLayoutState.mIsPreLayout = state.isPreLayout();
// 第三步
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
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
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = 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.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// 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;
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;
}
}
// ······
}
複製代碼
相信從上面的代碼均可以找出每一步的執行。如今,咱們來詳細分析每一步。首先來看第一步--肯定錨點的信息
。
要想看錨點信息的計算過程,咱們能夠從updateAnchorInfoForLayout
方法裏面來找出答案,咱們來看看updateAnchorInfoForLayout
方法:
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
// 第一種計算方式
if (updateAnchorFromPendingData(state, anchorInfo)) {
return;
}
// 第二種計算方式
if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
return;
}
// 第三種計算方式
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
複製代碼
我相信經過上面的代碼註釋,你們都能明白updateAnchorInfoForLayout
方法到底幹了嘛,這裏我簡單分析一下這三種肯定所作的含義,具體是怎麼作的,這裏就不討論,由於這裏面的細節太多了,深刻的討論容易將咱們聰明無比的大腦搞暈😂。
- 第一種計算方式,表示含義有兩種:1.
RecyclerView
被重建,期間回調了onSaveInstanceState
方法,因此目的是爲了恢復上次的佈局;2.RecyclerView
調用了scrollToPosition
之類的方法,因此目的是讓RecyclerView
滾到準確的位置上去。因此,錨點的信息根據上面的兩種狀況來計算。- 第二種計算方法,從
Children
上面來計算錨點信息。這種計算方式也有兩種狀況:1. 若是當前有擁有焦點的Child
,那麼有當前有焦點的Child的位置來計算錨點;2. 若是沒有child擁有焦點,那麼根據佈局方向(此時佈局方向由mLayoutFromEnd
來決定)獲取可見的第一個ItemView
或者最後一個ItemView
。- 若是前面兩種方式都計算失敗了,那麼採用第三種計算方式,也就是默認的計算方式。
以上就是updateAnchorInfoForLayout
方法所作的事情,這裏就不詳細糾結每種計算方式的細節,有興趣的同窗能夠看看。
至於第二步,調用detachAndScrapAttachedViews
方法對全部的ItemView
進行回收,這部分的內容屬於RecyclerView
緩存機制的部分,本文先在這裏埋下一個伏筆,後續專門講解RecyclerView
會詳細的分析它,因此這裏就不講解了。
接下來咱們來看看第三步,也就是調用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
方法。
因爲layoutChunk
方法比較長,這裏我就不完整的展現,爲了方便理解,我對這個方法作一個簡單的概述,讓你們有一個大概的理解。
- 調用
LayoutState
的next
方法得到一個ItemView
。千萬別小看這個next
方法,RecyclerView
緩存機制的起點就是從這個方法開始,可想而知,這個方法到底爲咱們作了多少事情。- 若是
RecyclerView
是第一次佈局Children的話(layoutState.mScrapList == null
爲true),會先調用addView,將View
添加到RecyclerView
裏面去。- 調用
measureChildWithMargins
方法,測量每一個ItemView
的寬高。注意這個方法測量ItemView的寬高考慮到了兩個因素:1.margin屬性;2.ItemDecoration
的offset
。- 調用
layoutDecoratedWithMargins
方法,佈局ItemView
。這裏也考慮上面的兩個因素的。
至於每一步具體幹了嘛,這裏就不詳細的解釋,都是一些基本操做,有興趣的同窗能夠看看。
綜上所述,即是LayoutManager
的onLayoutChildren
方法整個執行過程,思路仍是比較簡單的。
本文到此就差很少了,在最後,我作一個簡單的總結。
RecyclerView
的measure
過程分爲三種狀況,每種狀況都有執行過程。一般來講,咱們都會走自動測量的過程。- 自動測量裏面須要分清楚
mState.mLayoutStep
狀態值,由於根據不一樣的狀態值調用不一樣的dispatchLayoutStep
方法。layout
過程也根據mState.mLayoutStep
狀態來調用不一樣的dispatchLayoutStep
方法draw
過程主要作了四件事:1.繪製ItemDecoration
的onDraw
部分;2.繪製Children
;3.繪製ItemDecoration
的drawOver
部分;4. 根據mClipToPadding
的值來判斷是否進行特殊繪製。