RecyclerView包含以下幾個重要的組件:
1.LayoutManager: 測量和布局子View
2.Recycler: View的緩存、復用
3.ViewHolder: 對itemView及其元數據的封裝
4.ItemAnimator: 動畫
5.Adapter: 創建ViewHolder、綁定數據、通知數據變更
6.ItemDecoration: ItemView的裝飾
7.SmoothScroller: 平滑滾動
8.ViewFlinger: 功能未知...
先看看最基本也最重要的Adapter。
RecyclerView.Adapter
在RecyclerView中,Adapter是其內部的一個抽象類,咱們最熟悉的兩個方法: onCreateViewHolder 和 onBindViewHolder,分別在 createViewHolder 和 bindViewHolder 中被調用。
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
這里直接調用咱們復寫的onCreateViewHolder,傳進去兩個參數。
ViewGroup: 當前View綁定到Adapter的position后添加到的ViewGroup
ViewType: 當前View的類型(這個類型由咱們自己定義,有時候一個列表需要有各種奇形怪狀的item,有方的,有圓的,都是不同的類型)
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
這個也很簡單,先記錄了當前Holder的position和id(如果設了hasStableId,這個究竟是什么先按下不表,因為現在我還不知道...),然后設了Holder的Flag狀態,標記為綁定狀態,調用咱們的onBindViewHolder,置LayoutParams為Dirty(即數據已變更,需要重繪)。
除了上面兩個方法,還有比較容易理解的 getItemCount 和 getItemId, getItemViewType, 這幾個不提也罷。
onViewRecycled, 當被創建的一個view被復用的時候被調用。就是,LayoutManager認為這個View沒有價值了,比如在屏幕上不可見,就會復用這個View并且調用這個方法,可以在這里對該View進行資源釋放。
然后,還有一個詭異的setHasStableId,設這個為 true 可以在notifyDataChange的時候提升效率,至于為什么,要等到后面看看notify的邏輯才能知曉了。
最后還有一個重頭戲,就是notify的一系列方法。notify的方法可以歸結為兩種類型,一種是列表結構發生了變化,一種是單個item發生變化。當只有單個item的數據更新時,列表的位置沒有變化,此時的變化屬于后者,notifyItemChanged、notifyItemRangeChanged屬于此類。而當item位置發生改變,就是列表結構的變化了,notifyItemInserted、notifyItemRangeInserted、notifyItemMoved、notifyItemRemoved、notifyItemRangeRemoved以及notifyDataSetChange屬于此類。注意,少調用notifyDataSetChange可以提升響應速度,因為這個方法會假設所有數據即位置信息都發生了變化,會重新綁定所有數據,比較耗時,也無法執行Item變化的默認動畫。
下面要介紹一下Adapter中對觀察者模式的應用,即View監聽數據的變化。
在Adapter中有一個 Observable 全局變量,是 AdapterDataObservable 的實例。AdapterDataObservable 是一個標準的 Observable 實現,其中包含了notify的一系列方法,在方法中使用for循環通知Observer數據發生了變化。那么訂閱動作發生在哪里呢?
來看看RecyclerView的setAdapter方法
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
關鍵在這個setAdapterInternal里頭
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
先忽略別的操作,可以看到,在這里,先取消注冊了Observer,然后重新注冊。Observer是 RecyclerViewDataObserver 的實現。
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
在這個實現中,每一種變換都會先檢查當前沒有在布局或者滾動過程中,然后調用AdapterHelper的相應方法,最后 triggerUpdateProcessor. 在這個方法里可以看到,滿足三個條件會直接執行動畫,不滿足則需要重新布局。
POST_UPDATES_ON_ANIMATION: 當前動作是否執行動畫,定義是SDK大于16.
mHasFixedSize: 這是一個RecyclerView可以設置的參數,如果所有Item的大小一致,則可以直接置為true。如果沒有置true,則需要重新布局。
mIsAttached: 即RecyclerView是否attach到當前Window.
再看看這個執行動畫的mUpdateChildViewsRunnable
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
注解和代碼都很清楚,滿足三個條件就會執行consumePendingUpdateOperations方法。這個方法的意思是執行當前那些被推遲執行的更新操作,在里面調到了AdapterHelper的方法。
這里又引出來一個AdapterHelper,定義里赫然寫著:處理Adapter更新。
看看這個類的介紹,它為每一個adapter的數據變化創建一個UpdateOps,然后對他們進行預處理,決定哪些可以被推遲執行,哪些不可以。對于要求立即執行的UpdateOps,AdapterHelper會在第一次layout前根據上一個被推遲操作來對其進行更改。由于操作的順序在這個過程中改變了,所以它也處理被推遲的UpdateOps.
即使操作被以不同的順序轉發給LayoutManager,但數據是保證絕對正確的。
那這些數據變更是怎么被轉發給LayoutManager的呢?
這里關鍵是它的內部CallBack接口
/**
* Contract between AdapterHelper and RecyclerView.
*/
interface Callback {
RecyclerView.ViewHolder findViewHolder(int position);
void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
void onDispatchFirstPass(UpdateOp updateOp);
void onDispatchSecondPass(UpdateOp updateOp);
void offsetPositionsForAdd(int positionStart, int itemCount);
void offsetPositionsForMove(int from, int to);
}
流程是這樣的,consumePendingUpdateOperations 的描述是:滾動過程中的數據變更可能會導致Crash,滾動動作會假定沒有數據變更。此方法消滅掉所有的延時變更來避免這個問題。
在這個方法中會調用AdapterHelper的preProcess預處理方法,preProcess會調用不同的apply方法(applyAdd, applyRemove等),調到dispatchAndUpdateViewHolders,最終調用到RecyclerView中實現的Callback的dispatchUpdate方法來分發更新事件,在這里調用LayoutManager的不同方法,對UI做出更改。
void dispatchUpdate(AdapterHelper.UpdateOp op) {
switch (op.cmd) {
case AdapterHelper.UpdateOp.ADD:
mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
break;
case AdapterHelper.UpdateOp.REMOVE:
mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
break;
case AdapterHelper.UpdateOp.UPDATE:
mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
op.payload);
break;
case AdapterHelper.UpdateOp.MOVE:
mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
break;
}
}
那么以上就是RecyclerView使用觀察者模式,在數據變更時通知UI更新的流程。
OK,趁熱打鐵(其實已經過了一晚上了...),咱們來看看ViewHolder吧(由于昨夜精神太好所以就Solo了一下,挑個簡單的提提神)。
ViewHolder
ViewHolder是一個對itemView、item數據、item類型的封裝。
同樣的,ViewHolder也是RecyclerView的一個內部抽象類。先看看它為自己定義的狀態:
FLAG_BOUND——ViewHolder已經綁定到某個位置,mPosition、mItemId、mItemViewType都有效
FLAG_UPDATE——ViewHolder綁定的View對應的數據過時需要重新綁定,mPosition、mItemId還是一致的
FLAG_INVALID——ViewHolder綁定的View對應的數據無效,需要完全重新綁定不同的數據
FLAG_REMOVED——ViewHolder對應的數據已經從數據集移除
FLAG_NOT_RECYCLABLE——ViewHolder不能復用
FLAG_RETURNED_FROM_SCRAP——這個狀態的ViewHolder會加到scrap list被復用。
FLAG_CHANGED——ViewHolder內容發生變化,通常用于表明有ItemAnimator動畫
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能復用
FLAG_TMP_DETACHED——ViewHolder從父RecyclerView臨時分離的標志,便于后續移除或添加回來
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道對應的Adapter的位置,直到綁定到一個新位置
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)調用時設置
因為牽扯到View的復用,ViewHolder的狀態是很復雜的,后面在追到不同地方的時候會慢慢看到這些狀態的實際應用。
在這里頭,position是個搞腦子的東西。在之前版本的RecyclerView中,ViewHolder只有一個getPosition方法,后來被Deprecate了,注釋道:這個方法很模糊,position在某些情況下是會沖突的,然后給了兩個新方法,getLayoutPosition和getAdapterPosition. 注釋還說,RecyclerView在下一次布局繪制前不會處理任何Adapter的更新,這就造成了一個問題:比如現在有一個Item的position是0,此時調用notifyItemInserted(0),Adapter實際已經更新了,getAdapterPosition返回1,但是RecyclerView還沒有進行重繪,那么它不處理任何接收到的更新,此時getLayoutPosition還是返回0. 就造成了Adapter的position和用戶實際看到的position不匹配的時間窗口,這個窗口注釋聲稱小于16ms,那么這里如果調了錯誤的方法就可能出bug. 因為LayoutManager是管理UI的,所以應該調用的是getLayoutPosition,必須保證對UI的處理與用戶看到的一致。而對用戶事件的處理則應該調getAdapterPosition,不然就可能出現用戶明明點的是第0位的item,第1位的item響應了事件的bug.
ViewHolder值得說的也就這了...
下面看看LayoutManager吧,本來想直接看Recycler的緩存機制的,考慮到這個緩存機制跟LayoutManager息息相關,所以咱們一個一個來,把LayoutManager解決先。
LayoutManager
LayoutManager應該是RecyclerView中最復雜的組件了,作為一個抽象內部類,洋洋灑灑三千行代碼...如開篇所說,LayoutManager處理RecyclerView UI相關的功能,比如測量布局items、滾動頁面等等。
幸運的是,v7包默認已經幫咱們實現了三個LayoutManager,看著具體實現的話,追源碼就不會那么累了。這三個分別是 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager. 分別實現了線性布局、table布局和一個不知道什么鬼東西的布局。Stagger本身有動態的意思,所以...動態table?...
先不扯實現了,來看看LayoutManager這個抽象內部類吧。
/**
* A <code>LayoutManager</code> is responsible for measuring and positioning item views
* within a <code>RecyclerView</code> as well as determining the policy for when to recycle
* item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
* a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
* a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
* layout managers are provided for general use.
*/
照顧一下英語不好的童鞋: LayoutManager用于測量和擺放itemViews,同時負責決定在什么時間點可以復用那些不被用戶看到的itemViews. 更改LayoutManager可以實現豎的、橫的、grid的等等各種奇形怪狀的列表。
咱們都知道,RecyclerView也是一個ViewGroup(廢話),它在擺放子View的時候會有測量和布局的流程,而這兩個操作都已經托付給LayoutManager來完成了,那么肯定會有這兩個事件的傳遞過程。
先來看看測量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || 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);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
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;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
看上去好像干了很多事情,我們就以這個方法為主干,分析測量流程。
開始之前先看一下mState,看名字就猜到這是用來記錄狀態的。注釋里把它稱為data bus,可見除了記錄狀態,它還有記錄信息的作用。RecyclerView各個組件間信息的傳遞也靠它了。里頭有一個mLayoutStep,這個值有三個狀態,分別是STEP_START、STEP_LAYOUT、STEP_ANIMATIONS,mLayoutStep初始值為STEP_START.
首先判了一下空,mLayout就是LayoutManager的實例。如果為空,使用默認的測量機制。這個不管。
然后接觸到第一個LayoutManager的可設參數,AutoMeasure,也就是自動測量。 注釋寫了一大段中學生作文,總結一下就是:這個屬性決定了RecyclerView如何進行測量。如果開啟AutoMeasure,那么RecyclerView將支持WRAP_CONTENT屬性,以包裹內容為終極目標來執行測量。如果不開啟,就要復寫LayoutManager的onMeasure方法來自定義測量方案。框架提供的三個LayoutManager實現都是用的這個自動測量機制。
關于這個自動測量機制是如何實現的,后面會專門分一塊出來細讀,現在咱還是緊跟步伐,假設我們現在有一個寬高固定的RecyclerView,默認進入自動測量,然后調了LayoutManager的onMeasure. 咱們以LinearLayoutManager為例,它并未實現onMeasure,在抽象類中onMeasure調用了defaultOnMeasure,這個方法僅僅把寬度加上paddings,高度加上paddings,然后就setMeasuredDimension了,非常直截了當。
然后由于寬高確定,所以直接返回。onLayout的調用緊隨其后。
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();
}
此方法第一次被調用時會依次執行dispatchLayoutStep1() 、dispatchLayoutStep2() 和 dispatchLayoutStep3().
好,下面來挨個兒仔細看看layout的這三個步驟。
dispatchLayoutStep1
注釋:
1.處理adapter的更新
2.確定要執行的動畫
3.保存當前views的信息
4.如果必要,運行可預測布局,并保存其信息
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
eatRequestLayout();
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) {
//...
}
if (mState.mRunPredictiveAnimations) {
//...
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
在此方法中置mState.mIsMeasuring = false,然后處理adapter的更新。
來看看處理adapter更新的部分。
/**
* 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();
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();
}
上來就是一個判斷,問數據是否已經更改,如果已更改,那么處理這些items就沒有意義了,直接reset. mDataSetHasChangedAfterLayout這個參數會在兩種情況下被置為true,分別是更換adapter的時候,以及調用notifyDataSetChange的時候,這也解釋了上面講的調用這個方法會導致性能損失的現象。
然后判斷可預期item動畫是否開啟,這里再展開講一下可預期動畫。這個要返回true要滿足兩個條件,ItemAnimator不為空,且LayoutManager支持可預期動畫。LayoutManager默認返回false,即不支持。注意,如果RecyclerView的ItemAnimator不為空,LayoutManager的supportsPredictiveItemAnimations返回false,那么會自動開啟simple item animations,添加或移除views只進行簡單的淡入淡出動畫。如果ItemAnimator不為空且supportsPredictiveItemAnimations返回true,那么onLayoutChildren(Recycler, State)會被調用兩次,目的是為了記錄需要的信息來更智能地預測什么樣的動畫需要怎樣被執行。
回過頭來,如果開啟了可預期item動畫,就會執行adapterHelper的preProcess預處理方法,上面分析Adapter的時候已經講過,這個方法最終會調到RecyclerView實現的AdapterHelper的Callback接口的分發更新方法,最終執行更新動畫。
如果未開啟可預期動畫,則調用adapterHelper的consumeUpdatesInOnePass,不對更新操作進行預處理,直接回調到RecyclerView里的更新事件,傳遞給LayoutManager.
然后就是計算需要執行的動畫了,這里要看一下兩個狀態,mRunSimpleAnimations 和 mRunPredictiveAnimations。
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
可以看到,mRunSimpleAnimations在滿足以下條件時為true:
1.mFirstLayoutComplete為true(onLayout第一次執行完后被置為true)
2.mItemAnimator不為空
3.Layout后數據發生了變化 或 有item被移除或添加 或 LayoutManager請求執行simple animations
4.Layout后數據不發生變化 或 mAdapter有穩定的ID
而運行預期動畫mRunPredictiveAnimations則在以下條件被滿足時返回true:
1.mRunSimpleAnimations為true
2.有item添加或移除
3.Layout后數據未發生變化
4.預期Item動畫被開啟
回到step1,在記錄了一些狀態后,記錄了adapter當前items的count,置狀態mInPreLayout為執行預期動畫的值,第一次調用為false,記錄第一個和最后一個子View的位置信息。然后判斷是否運行simple animations 和 預期item動畫。由于第一次Layout尚未完成,所以不會執行。
step1的最后把State的mLayoutStep置為了STEP_LAYOUT.
layoutStep1執行完畢后,調用了mLayout.setExactMeasureSpecsFrom(this);
,把當前RecyclerView的絕對大小告知了LayoutManager. 然后馬上調用step2.
dispatchLayoutStep2:
注釋:
布局第二步對views進行真正的最終布局,確定最終狀態。
如果有必要會被調用多次。
/**
* 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() {
eatRequestLayout();
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();
resumeRequestLayout(false);
}
在這里首先調了mAdapterHelper的consumeUpdatesInOnePass,此方法跳過了預處理的階段,直接對adapter的更新進行處理。然后再次記錄了adapter的itemCount. 再然后,置狀態mInPreLayout標識為false,調用LayoutManager的onLayoutChildren(mRecycler, mState)方法執行真正的Layout.
LayoutManager完成了布局子View后,dispatchLayoutStep2置狀態mLayoutStep為STEP_ANIMATIONS. 至此,LayoutStep2執行完畢。
dispatchLayoutStep3
注釋:
Layout的最后一步,在這里保存views的信息用于動畫。
觸發動畫,并且做好善后工作。
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
eatRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
//...
}
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = 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();
resumeRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
recoverFocusFromState();
resetFocusInfo();
}
挑重要的看。置mLayoutStep為STEP_START, 因為mRunSimpleAnimation依然為false,所以不執行判斷內的代碼。然后reset了一堆狀態,記錄當前itemCount為mPreviousLayoutItemCount. 最后,調用了LayoutManager的onLayoutCompleted,通知LayoutManager當前已經完成了Layout操作。
至此,Step3執行完畢。
以上就是第一次onLayout被調用的執行流程。
那么如果是已經完成了布局的RecyclerView,調用notifyItemRemoved()移除一個item時又是怎么走流程的呢?因為一個item被移除了,預期它要執行一個淡出動畫,然后后面的Item上移這樣一個簡單的動作。
我們從notifyItemRemoved開始追,走一遍數據變更的完整流程。
觸發Item移除的操作后,首先跟到notifyItemRemoved方法內部。
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
再到Observable看看:
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
跟預期是一致的,通知觀察者當前數據發生了變更。
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
由于我設置了setHasFixedSize為true,所以直接運行mUpdateChildViewsRunnable.
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
void consumePendingUpdateOperations() {
if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
return;
}
if (!mAdapterHelper.hasPendingUpdates()) {
return;
}
// if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
// of the visible items is affected and if not, just ignore the change.
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
eatRequestLayout();
onEnterLayoutOrScroll();
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
resumeRequestLayout(true);
onExitLayoutOrScroll();
TraceCompat.endSection();
} else if (mAdapterHelper.hasPendingUpdates()) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
}
在這里會直接調用dispatchLayout,然后走布局三部曲。注意,mRunSimpleAnimations這個參數的條件已經滿足了(如果忘了就到上面去看看需要哪些條件,懶得看就假裝它被滿足了吧),之前走第一遍流程的時候我們略過了跟它相關的代碼,這次來看看。
dispatchLayoutStep1
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);
}
}
}
直接就是一個for循環,遍歷當前所有可見的子View的ViewHolder,注意,是可---見---的view的ViewHolder. 這個可見View的count來自于ChildHelper,來看看ChildHelper的getChildCount:
int getChildCount() {
return mCallback.getChildCount() - mHiddenViews.size();
}
這個Callback實現當然是在RecyclerView里頭,這里返回的是RecyclerView的子View減去隱藏view的數量。看好了,RecyclerView的childCount并不是所有數據Item count,而是當前RecyclerView可見的Views的數量。比如當前,我的主頁只可見一個item,那么在這個時間點,RecyclerView的childCount就為1. 這就是為什么如果要獲取列表數量,要調用Adapter的getChildCount,不能調RecyclerView的getChildCount,因為后者是一個隨時在變化的動態值。
我的Demo中只有一個item可見,總共四個items. 所以只走了一次循環,把這個holder添加到了preLayoutList,就出去了。
mRunPredictiveAnimations同樣滿足條件。
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();
}
注解都已經說了,運行preLayout(預布局),會使用items的舊的positions. LayoutManager應該Layout所有的東西,包括已經被移除的items.
這里也是一個循環,在循環前調用了LayoutManager的onLayout方法執行了一次布局,在布局的時候動了手腳。在LinearLayoutManager的實現中,偷偷地根據ViewHolder的標記位向RecyclerView添加了childView.
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
這個addView調用的是抽象父類的addView方法。在抽象父類addView的實現中有這么一句:
mChildHelper.addView(child, index, false);
ChildHelper的addView:
mCallback.addView(child, offset);
RecyclerView中對Callback的實現:
@Override
public void addView(View child, int index) {
if (VERBOSE_TRACING) {
TraceCompat.beginSection("RV addView");
}
RecyclerView.this.addView(child, index);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
dispatchChildAttached(child);
}
是他是他就是他,就是在這里把子View添加到RecyclerView當中的。
void dispatchChildAttached(View child) {
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildAttachedToWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewAttachedToWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
}
哈,看見熟面孔了么,onChildAttachedToWindow就是在這兒調用的。
好,跑題有點遠。剛剛說到,Step1的預期布局判斷的for循環前調用了LayoutManager的onLayout,在這里面向RecyclerView添加了即將要顯示的子View. 這個循環會進行兩次,拿到的第一個ViewHolder是上一次已經被加到PreLayout的第0位View,第二個ViewHolder是新添加的,并沒有被hide,所以調的是addToAppearedInPreLayoutHolders.
這里打斷一下,需要看一下這些holders都被塞到什么地方了。這里有一個ViewInfoStore類,注釋說,這個類抽象了所有運行動畫所需的View信息。在這里維護了兩個集合,一個mLayoutHolderMap,用于存儲那些即將執行動畫的holders和它們相對應的信息的映射,另一個mOldChangedHolders,存儲已存在的發生了改變的ViewHolder.
來看看Step1調用的是什么方法。在第一個mRunSimpleAnimation的判斷中,調用的是addToPreLayout和addToOldChangeHolders兩個方法。
void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
將第0位item加到了mLayoutHolderMap中,并記錄標記為FLAG_PRE,代表的應該是當前holder即將執行動畫。
void addToOldChangeHolders(long key, ViewHolder holder) {
mOldChangedHolders.put(key, holder);
}
這個沒什么可說的。來看看mRunPredictiveAnimation判斷的代碼,調用的是addToAppearedInPreLayoutHolders.
void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.flags |= FLAG_APPEAR;
record.preInfo = info;
}
這里也是存入了mLayoutHolderMap集合,但是置標記為FLAG_APPEAR,意思應該是即將執行出現的動畫。
好了,對于數據變更時Step1所執行的操作應該了然于心了,Step2實質上只是又進行了一次布局。直接看Step3.
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);
}
這里先嘗試從ViewInfoStore中取出保存的這個Holder的OldHolder,由于現在布局中存在的僅僅是第1位holder,而我們之前加到oldChangeHolders的只有第0位,所以拿到的是空,直接到addToPostLayout. 這個方法跟上面的幾個類似,添加到mLayoutHolderMap,置標記為FLAG_POST. 然后執行mViewInfoProcessCallback.
process方法根據flag判斷調用callback的哪個具體方法。這里首先調用的是processDisappeared, 執行消失,在這里調用animateDisappearance.
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
addAnimatingView(holder);
holder.setIsRecyclable(false);
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
addAnimationView:
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if(!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
呦,還良心地講解了這個機制是如何執行的,好好好,這就可以一邊看著代碼一邊看著注釋兌著灌了。
首先,RecyclerView的onMeasure被調用,如果MeasureSpec為EXACT,則不進行測量,直接返回(寬高已經被訂好了)。否則,開始在onMeasure中處理布局流程。它會處理所有的待處理adapter更新,并決定是不是要進行預布局。如果要進行預布局,會將state.preLayout()置為true,然后調用onLayoutChildren(Recycler, State). 此時,getWidth與getHeight依然返回上一次layout的結果。
預處理完畢后,會設state.preLayout為false,state.isMeasuring為true,此時,LayoutManager就可以通過getHeight、getHeightMode來獲取測量的specs了。
layout計算完后,RecyclerView為子view們計算邊界盒(加上padding的大小),設置測量過的height和width. LayoutManager可以通過復寫setMeasuredDimension(Rect, int, int)來選擇不同的值。比如,GridLayoutManager復寫這個值來處理三列布局時單排顯示兩個items的width計算。
此之后onMeasure的所有調用都會把狀態置為isMeasuring. RecyclerView管理view的增刪改操作,LayoutManager啥都不用管,只要把每個onLayoutChildren調用當做最后一次調用就可以了。
測量結束后,RecyclerView的onLayout(boolean, int, int, int, int)被調用。RecyclerView檢查在測量過程中是否進行了布局計算,如果是,則重用其相關的信息。如果最后一次的measure spec與最終的大小不匹配,或adapter的數據在measure和layout過程中被更改,它也可能再次調用onLayoutChildren.
最后的最后,計算動畫然后執行。
上面就是對測量流程的粗略描述。下面看下代碼。
onMeasure被調用時,mState.mLayoutStep默認為STEP_START,開始布局。調到dispatchLayoutStep1() 分發布局步驟1. 這個方法的注釋表示:第一步執行以下操作:
1.處理adapter的更新
2.確定要執行的動畫
3.保存當前views的信息
4.如果必要,運行可預測布局,并保存其信息