RecyclerView包含以下幾個重要的組件:
1.LayoutManager: 測量和布局子View
2.Recycler: View的緩存瓮栗、復(fù)用
3.ViewHolder: 對itemView及其元數(shù)據(jù)的封裝
4.ItemAnimator: 動畫
5.Adapter: 創(chuàng)建ViewHolder蚣抗、綁定數(shù)據(jù)、通知數(shù)據(jù)變更
6.ItemDecoration: ItemView的裝飾
7.SmoothScroller: 平滑滾動
8.ViewFlinger: 功能未知...
先看看最基本也最重要的Adapter融蹂。
RecyclerView.Adapter
在RecyclerView中,Adapter是其內(nèi)部的一個抽象類,咱們最熟悉的兩個方法: onCreateViewHolder 和 onBindViewHolder迄本,分別在 createViewHolder 和 bindViewHolder 中被調(diào)用。
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;
}
這里直接調(diào)用咱們復(fù)寫的onCreateViewHolder课竣,傳進去兩個參數(shù)嘉赎。
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(如果設(shè)了hasStableId赃份,這個究竟是什么先按下不表,因為現(xiàn)在我還不知道...),然后設(shè)了Holder的Flag狀態(tài)抓韩,標記為綁定狀態(tài)纠永,調(diào)用咱們的onBindViewHolder,置LayoutParams為Dirty(即數(shù)據(jù)已變更谒拴,需要重繪)尝江。
除了上面兩個方法,還有比較容易理解的 getItemCount 和 getItemId, getItemViewType, 這幾個不提也罷英上。
onViewRecycled, 當被創(chuàng)建的一個view被復(fù)用的時候被調(diào)用炭序。就是,LayoutManager認為這個View沒有價值了苍日,比如在屏幕上不可見惭聂,就會復(fù)用這個View并且調(diào)用這個方法,可以在這里對該View進行資源釋放相恃。
然后辜纲,還有一個詭異的setHasStableId,設(shè)這個為 true 可以在notifyDataChange的時候提升效率拦耐,至于為什么耕腾,要等到后面看看notify的邏輯才能知曉了。
最后還有一個重頭戲杀糯,就是notify的一系列方法扫俺。notify的方法可以歸結(jié)為兩種類型,一種是列表結(jié)構(gòu)發(fā)生了變化固翰,一種是單個item發(fā)生變化牵舵。當只有單個item的數(shù)據(jù)更新時,列表的位置沒有變化倦挂,此時的變化屬于后者,notifyItemChanged担巩、notifyItemRangeChanged屬于此類方援。而當item位置發(fā)生改變,就是列表結(jié)構(gòu)的變化了涛癌,notifyItemInserted犯戏、notifyItemRangeInserted、notifyItemMoved拳话、notifyItemRemoved先匪、notifyItemRangeRemoved以及notifyDataSetChange屬于此類。注意弃衍,少調(diào)用notifyDataSetChange可以提升響應(yīng)速度呀非,因為這個方法會假設(shè)所有數(shù)據(jù)即位置信息都發(fā)生了變化,會重新綁定所有數(shù)據(jù),比較耗時岸裙,也無法執(zhí)行Item變化的默認動畫猖败。
下面要介紹一下Adapter中對觀察者模式的應(yīng)用,即View監(jiān)聽數(shù)據(jù)的變化降允。
在Adapter中有一個 Observable 全局變量恩闻,是 AdapterDataObservable 的實例。AdapterDataObservable 是一個標準的 Observable 實現(xiàn)剧董,其中包含了notify的一系列方法幢尚,在方法中使用for循環(huán)通知Observer數(shù)據(jù)發(fā)生了變化。那么訂閱動作發(fā)生在哪里呢翅楼?
來看看RecyclerView的setAdapter方法
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
關(guān)鍵在這個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 的實現(xiàn)褂微。
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();
}
}
}
在這個實現(xiàn)中功蜓,每一種變換都會先檢查當前沒有在布局或者滾動過程中,然后調(diào)用AdapterHelper的相應(yīng)方法宠蚂,最后 triggerUpdateProcessor. 在這個方法里可以看到式撼,滿足三個條件會直接執(zhí)行動畫,不滿足則需要重新布局求厕。
POST_UPDATES_ON_ANIMATION: 當前動作是否執(zhí)行動畫著隆,定義是SDK大于16.
mHasFixedSize: 這是一個RecyclerView可以設(shè)置的參數(shù),如果所有Item的大小一致呀癣,則可以直接置為true美浦。如果沒有置true,則需要重新布局项栏。
mIsAttached: 即RecyclerView是否attach到當前Window.
再看看這個執(zhí)行動畫的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();
}
};
注解和代碼都很清楚浦辨,滿足三個條件就會執(zhí)行consumePendingUpdateOperations方法。這個方法的意思是執(zhí)行當前那些被推遲執(zhí)行的更新操作沼沈,在里面調(diào)到了AdapterHelper的方法流酬。
這里又引出來一個AdapterHelper,定義里赫然寫著:處理Adapter更新列另。
看看這個類的介紹芽腾,它為每一個adapter的數(shù)據(jù)變化創(chuàng)建一個UpdateOps,然后對他們進行預(yù)處理页衙,決定哪些可以被推遲執(zhí)行摊滔,哪些不可以。對于要求立即執(zhí)行的UpdateOps,AdapterHelper會在第一次layout前根據(jù)上一個被推遲操作來對其進行更改惭载。由于操作的順序在這個過程中改變了旱函,所以它也處理被推遲的UpdateOps.
即使操作被以不同的順序轉(zhuǎn)發(fā)給LayoutManager,但數(shù)據(jù)是保證絕對正確的描滔。
那這些數(shù)據(jù)變更是怎么被轉(zhuǎn)發(fā)給LayoutManager的呢棒妨?
這里關(guān)鍵是它的內(nèi)部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 的描述是:滾動過程中的數(shù)據(jù)變更可能會導(dǎo)致Crash含长,滾動動作會假定沒有數(shù)據(jù)變更券腔。此方法消滅掉所有的延時變更來避免這個問題。
在這個方法中會調(diào)用AdapterHelper的preProcess預(yù)處理方法拘泞,preProcess會調(diào)用不同的apply方法(applyAdd, applyRemove等)纷纫,調(diào)到dispatchAndUpdateViewHolders,最終調(diào)用到RecyclerView中實現(xiàn)的Callback的dispatchUpdate方法來分發(fā)更新事件陪腌,在這里調(diào)用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使用觀察者模式诗鸭,在數(shù)據(jù)變更時通知UI更新的流程染簇。
OK,趁熱打鐵(其實已經(jīng)過了一晚上了...)强岸,咱們來看看ViewHolder吧(由于昨夜精神太好所以就Solo了一下锻弓,挑個簡單的提提神)。
ViewHolder
ViewHolder是一個對itemView蝌箍、item數(shù)據(jù)青灼、item類型的封裝。
同樣的妓盲,ViewHolder也是RecyclerView的一個內(nèi)部抽象類杂拨。先看看它為自己定義的狀態(tài):
FLAG_BOUND——ViewHolder已經(jīng)綁定到某個位置,mPosition悯衬、mItemId扳躬、mItemViewType都有效
FLAG_UPDATE——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)過時需要重新綁定,mPosition甚亭、mItemId還是一致的
FLAG_INVALID——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)無效,需要完全重新綁定不同的數(shù)據(jù)
FLAG_REMOVED——ViewHolder對應(yīng)的數(shù)據(jù)已經(jīng)從數(shù)據(jù)集移除
FLAG_NOT_RECYCLABLE——ViewHolder不能復(fù)用
FLAG_RETURNED_FROM_SCRAP——這個狀態(tài)的ViewHolder會加到scrap list被復(fù)用击胜。
FLAG_CHANGED——ViewHolder內(nèi)容發(fā)生變化亏狰,通常用于表明有ItemAnimator動畫
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能復(fù)用
FLAG_TMP_DETACHED——ViewHolder從父RecyclerView臨時分離的標志偶摔,便于后續(xù)移除或添加回來
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道對應(yīng)的Adapter的位置暇唾,直到綁定到一個新位置
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)調(diào)用時設(shè)置
因為牽扯到View的復(fù)用,ViewHolder的狀態(tài)是很復(fù)雜的,后面在追到不同地方的時候會慢慢看到這些狀態(tài)的實際應(yīng)用策州。
在這里頭瘸味,position是個搞腦子的東西。在之前版本的RecyclerView中够挂,ViewHolder只有一個getPosition方法旁仿,后來被Deprecate了,注釋道:這個方法很模糊孽糖,position在某些情況下是會沖突的枯冈,然后給了兩個新方法,getLayoutPosition和getAdapterPosition. 注釋還說办悟,RecyclerView在下一次布局繪制前不會處理任何Adapter的更新尘奏,這就造成了一個問題:比如現(xiàn)在有一個Item的position是0,此時調(diào)用notifyItemInserted(0)病蛉,Adapter實際已經(jīng)更新了炫加,getAdapterPosition返回1,但是RecyclerView還沒有進行重繪铺然,那么它不處理任何接收到的更新俗孝,此時getLayoutPosition還是返回0. 就造成了Adapter的position和用戶實際看到的position不匹配的時間窗口,這個窗口注釋聲稱小于16ms探熔,那么這里如果調(diào)了錯誤的方法就可能出bug. 因為LayoutManager是管理UI的驹针,所以應(yīng)該調(diào)用的是getLayoutPosition,必須保證對UI的處理與用戶看到的一致诀艰。而對用戶事件的處理則應(yīng)該調(diào)getAdapterPosition柬甥,不然就可能出現(xiàn)用戶明明點的是第0位的item,第1位的item響應(yīng)了事件的bug.
ViewHolder值得說的也就這了...
下面看看LayoutManager吧其垄,本來想直接看Recycler的緩存機制的苛蒲,考慮到這個緩存機制跟LayoutManager息息相關(guān),所以咱們一個一個來绿满,把LayoutManager解決先臂外。
LayoutManager
LayoutManager應(yīng)該是RecyclerView中最復(fù)雜的組件了,作為一個抽象內(nèi)部類喇颁,洋洋灑灑三千行代碼...如開篇所說漏健,LayoutManager處理RecyclerView UI相關(guān)的功能,比如測量布局items橘霎、滾動頁面等等蔫浆。
幸運的是,v7包默認已經(jīng)幫咱們實現(xiàn)了三個LayoutManager姐叁,看著具體實現(xiàn)的話瓦盛,追源碼就不會那么累了洗显。這三個分別是 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager. 分別實現(xiàn)了線性布局、table布局和一個不知道什么鬼東西的布局原环。Stagger本身有動態(tài)的意思挠唆,所以...動態(tài)table?...
先不扯實現(xiàn)了嘱吗,來看看LayoutManager這個抽象內(nèi)部類吧玄组。
/**
* 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,同時負責(zé)決定在什么時間點可以復(fù)用那些不被用戶看到的itemViews. 更改LayoutManager可以實現(xiàn)豎的柜与、橫的巧勤、grid的等等各種奇形怪狀的列表。
咱們都知道弄匕,RecyclerView也是一個ViewGroup(廢話)颅悉,它在擺放子View的時候會有測量和布局的流程,而這兩個操作都已經(jīng)托付給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,看名字就猜到這是用來記錄狀態(tài)的亡哄。注釋里把它稱為data bus枝缔,可見除了記錄狀態(tài),它還有記錄信息的作用蚊惯。RecyclerView各個組件間信息的傳遞也靠它了愿卸。里頭有一個mLayoutStep,這個值有三個狀態(tài)截型,分別是STEP_START趴荸、STEP_LAYOUT、STEP_ANIMATIONS宦焦,mLayoutStep初始值為STEP_START.
首先判了一下空发钝,mLayout就是LayoutManager的實例。如果為空波闹,使用默認的測量機制证逻。這個不管闭树。
然后接觸到第一個LayoutManager的可設(shè)參數(shù)胀莹,AutoMeasure铝侵,也就是自動測量。 注釋寫了一大段中學(xué)生作文锄码,總結(jié)一下就是:這個屬性決定了RecyclerView如何進行測量夺英。如果開啟AutoMeasure,那么RecyclerView將支持WRAP_CONTENT屬性滋捶,以包裹內(nèi)容為終極目標來執(zhí)行測量痛悯。如果不開啟,就要復(fù)寫LayoutManager的onMeasure方法來自定義測量方案重窟≡孛龋框架提供的三個LayoutManager實現(xiàn)都是用的這個自動測量機制。
關(guān)于這個自動測量機制是如何實現(xiàn)的巡扇,后面會專門分一塊出來細讀扭仁,現(xiàn)在咱還是緊跟步伐,假設(shè)我們現(xiàn)在有一個寬高固定的RecyclerView厅翔,默認進入自動測量乖坠,然后調(diào)了LayoutManager的onMeasure. 咱們以LinearLayoutManager為例,它并未實現(xiàn)onMeasure刀闷,在抽象類中onMeasure調(diào)用了defaultOnMeasure顽分,這個方法僅僅把寬度加上paddings翻默,高度加上paddings和泌,然后就setMeasuredDimension了,非常直截了當。
然后由于寬高確定忠烛,所以直接返回冤议。onLayout的調(diào)用緊隨其后恕酸。
onLayout操作直接調(diào)給了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();
}
此方法第一次被調(diào)用時會依次執(zhí)行dispatchLayoutStep1() 凉翻、dispatchLayoutStep2() 和 dispatchLayoutStep3().
好铺罢,下面來挨個兒仔細看看layout的這三個步驟缩滨。
dispatchLayoutStep1
注釋:
1.處理adapter的更新
2.確定要執(zhí)行的動畫
3.保存當前views的信息
4.如果必要,運行可預(yù)測布局,并保存其信息
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();
}
上來就是一個判斷繁扎,問數(shù)據(jù)是否已經(jīng)更改糊闽,如果已更改梳玫,那么處理這些items就沒有意義了,直接reset. mDataSetHasChangedAfterLayout這個參數(shù)會在兩種情況下被置為true,分別是更換adapter的時候钓账,以及調(diào)用notifyDataSetChange的時候梆暮,這也解釋了上面講的調(diào)用這個方法會導(dǎo)致性能損失的現(xiàn)象啦粹。
然后判斷可預(yù)期item動畫是否開啟偿荷,這里再展開講一下可預(yù)期動畫。這個要返回true要滿足兩個條件唠椭,ItemAnimator不為空跳纳,且LayoutManager支持可預(yù)期動畫。LayoutManager默認返回false贪嫂,即不支持寺庄。注意,如果RecyclerView的ItemAnimator不為空力崇,LayoutManager的supportsPredictiveItemAnimations返回false斗塘,那么會自動開啟simple item animations,添加或移除views只進行簡單的淡入淡出動畫亮靴。如果ItemAnimator不為空且supportsPredictiveItemAnimations返回true馍盟,那么onLayoutChildren(Recycler, State)會被調(diào)用兩次,目的是為了記錄需要的信息來更智能地預(yù)測什么樣的動畫需要怎樣被執(zhí)行茧吊。
回過頭來贞岭,如果開啟了可預(yù)期item動畫,就會執(zhí)行adapterHelper的preProcess預(yù)處理方法饱狂,上面分析Adapter的時候已經(jīng)講過曹步,這個方法最終會調(diào)到RecyclerView實現(xiàn)的AdapterHelper的Callback接口的分發(fā)更新方法,最終執(zhí)行更新動畫休讳。
如果未開啟可預(yù)期動畫讲婚,則調(diào)用adapterHelper的consumeUpdatesInOnePass,不對更新操作進行預(yù)處理俊柔,直接回調(diào)到RecyclerView里的更新事件筹麸,傳遞給LayoutManager.
然后就是計算需要執(zhí)行的動畫了活合,這里要看一下兩個狀態(tài),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第一次執(zhí)行完后被置為true)
2.mItemAnimator不為空
3.Layout后數(shù)據(jù)發(fā)生了變化 或 有item被移除或添加 或 LayoutManager請求執(zhí)行simple animations
4.Layout后數(shù)據(jù)不發(fā)生變化 或 mAdapter有穩(wěn)定的ID
而運行預(yù)期動畫mRunPredictiveAnimations則在以下條件被滿足時返回true:
1.mRunSimpleAnimations為true
2.有item添加或移除
3.Layout后數(shù)據(jù)未發(fā)生變化
4.預(yù)期Item動畫被開啟
回到step1,在記錄了一些狀態(tài)后酵紫,記錄了adapter當前items的count告嘲,置狀態(tài)mInPreLayout為執(zhí)行預(yù)期動畫的值,第一次調(diào)用為false奖地,記錄第一個和最后一個子View的位置信息橄唬。然后判斷是否運行simple animations 和 預(yù)期item動畫。由于第一次Layout尚未完成参歹,所以不會執(zhí)行仰楚。
step1的最后把State的mLayoutStep置為了STEP_LAYOUT.
layoutStep1執(zhí)行完畢后,調(diào)用了mLayout.setExactMeasureSpecsFrom(this);
,把當前RecyclerView的絕對大小告知了LayoutManager. 然后馬上調(diào)用step2.
dispatchLayoutStep2:
注釋:
布局第二步對views進行真正的最終布局犬庇,確定最終狀態(tài)僧界。
如果有必要會被調(diào)用多次。
/**
* 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);
}
在這里首先調(diào)了mAdapterHelper的consumeUpdatesInOnePass臭挽,此方法跳過了預(yù)處理的階段捂襟,直接對adapter的更新進行處理。然后再次記錄了adapter的itemCount. 再然后埋哟,置狀態(tài)mInPreLayout標識為false笆豁,調(diào)用LayoutManager的onLayoutChildren(mRecycler, mState)方法執(zhí)行真正的Layout.
LayoutManager完成了布局子View后,dispatchLayoutStep2置狀態(tài)mLayoutStep為STEP_ANIMATIONS. 至此赤赊,LayoutStep2執(zhí)行完畢闯狱。
dispatchLayoutStep3
注釋:
Layout的最后一步,在這里保存views的信息用于動畫抛计。
觸發(fā)動畫哄孤,并且做好善后工作。
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瘦陈,所以不執(zhí)行判斷內(nèi)的代碼。然后reset了一堆狀態(tài)波俄,記錄當前itemCount為mPreviousLayoutItemCount. 最后晨逝,調(diào)用了LayoutManager的onLayoutCompleted,通知LayoutManager當前已經(jīng)完成了Layout操作懦铺。
至此捉貌,Step3執(zhí)行完畢。
以上就是第一次onLayout被調(diào)用的執(zhí)行流程。
那么如果是已經(jīng)完成了布局的RecyclerView趁窃,調(diào)用notifyItemRemoved()移除一個item時又是怎么走流程的呢牧挣?因為一個item被移除了,預(yù)期它要執(zhí)行一個淡出動畫醒陆,然后后面的Item上移這樣一個簡單的動作瀑构。
我們從notifyItemRemoved開始追,走一遍數(shù)據(jù)變更的完整流程刨摩。
觸發(fā)Item移除的操作后寺晌,首先跟到notifyItemRemoved方法內(nèi)部。
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);
}
}
跟預(yù)期是一致的澡刹,通知觀察者當前數(shù)據(jù)發(fā)生了變更折剃。
@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();
}
}
由于我設(shè)置了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();
}
在這里會直接調(diào)用dispatchLayout像屋,然后走布局三部曲。注意边篮,mRunSimpleAnimations這個參數(shù)的條件已經(jīng)滿足了(如果忘了就到上面去看看需要哪些條件己莺,懶得看就假裝它被滿足了吧),之前走第一遍流程的時候我們略過了跟它相關(guān)的代碼戈轿,這次來看看凌受。
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循環(huán),遍歷當前所有可見的子View的ViewHolder思杯,注意胜蛉,是可---見---的view的ViewHolder. 這個可見View的count來自于ChildHelper,來看看ChildHelper的getChildCount:
int getChildCount() {
return mCallback.getChildCount() - mHiddenViews.size();
}
這個Callback實現(xiàn)當然是在RecyclerView里頭色乾,這里返回的是RecyclerView的子View減去隱藏view的數(shù)量誊册。看好了暖璧,RecyclerView的childCount并不是所有數(shù)據(jù)Item count案怯,而是當前RecyclerView可見的Views的數(shù)量。比如當前澎办,我的主頁只可見一個item嘲碱,那么在這個時間點,RecyclerView的childCount就為1. 這就是為什么如果要獲取列表數(shù)量局蚀,要調(diào)用Adapter的getChildCount麦锯,不能調(diào)RecyclerView的getChildCount,因為后者是一個隨時在變化的動態(tài)值琅绅。
我的Demo中只有一個item可見扶欣,總共四個items. 所以只走了一次循環(huán),把這個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();
}
注解都已經(jīng)說了,運行preLayout(預(yù)布局)术陶,會使用items的舊的positions. LayoutManager應(yīng)該Layout所有的東西凑懂,包括已經(jīng)被移除的items.
這里也是一個循環(huán),在循環(huán)前調(diào)用了LayoutManager的onLayout方法執(zhí)行了一次布局梧宫,在布局的時候動了手腳接谨。在LinearLayoutManager的實現(xiàn)中,偷偷地根據(jù)ViewHolder的標記位向RecyclerView添加了childView.
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
這個addView調(diào)用的是抽象父類的addView方法塘匣。在抽象父類addView的實現(xiàn)中有這么一句:
mChildHelper.addView(child, index, false);
ChildHelper的addView:
mCallback.addView(child, offset);
RecyclerView中對Callback的實現(xiàn):
@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就是在這兒調(diào)用的。
好驰徊,跑題有點遠笤闯。剛剛說到,Step1的預(yù)期布局判斷的for循環(huán)前調(diào)用了LayoutManager的onLayout棍厂,在這里面向RecyclerView添加了即將要顯示的子View. 這個循環(huán)會進行兩次颗味,拿到的第一個ViewHolder是上一次已經(jīng)被加到PreLayout的第0位View,第二個ViewHolder是新添加的牺弹,并沒有被hide浦马,所以調(diào)的是addToAppearedInPreLayoutHolders.
這里打斷一下,需要看一下這些holders都被塞到什么地方了张漂。這里有一個ViewInfoStore類晶默,注釋說,這個類抽象了所有運行動畫所需的View信息航攒。在這里維護了兩個集合荤胁,一個mLayoutHolderMap,用于存儲那些即將執(zhí)行動畫的holders和它們相對應(yīng)的信息的映射屎债,另一個mOldChangedHolders仅政,存儲已存在的發(fā)生了改變的ViewHolder.
來看看Step1調(diào)用的是什么方法。在第一個mRunSimpleAnimation的判斷中盆驹,調(diào)用的是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,代表的應(yīng)該是當前holder即將執(zhí)行動畫躯喇。
void addToOldChangeHolders(long key, ViewHolder holder) {
mOldChangedHolders.put(key, holder);
}
這個沒什么可說的辫封。來看看mRunPredictiveAnimation判斷的代碼硝枉,調(diào)用的是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倦微,意思應(yīng)該是即將執(zhí)行出現(xiàn)的動畫妻味。
好了,對于數(shù)據(jù)變更時Step1所執(zhí)行的操作應(yīng)該了然于心了欣福,Step2實質(zhì)上只是又進行了一次布局责球。直接看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,由于現(xiàn)在布局中存在的僅僅是第1位holder拓劝,而我們之前加到oldChangeHolders的只有第0位雏逾,所以拿到的是空,直接到addToPostLayout. 這個方法跟上面的幾個類似郑临,添加到mLayoutHolderMap栖博,置標記為FLAG_POST. 然后執(zhí)行mViewInfoProcessCallback.
process方法根據(jù)flag判斷調(diào)用callback的哪個具體方法。這里首先調(diào)用的是processDisappeared, 執(zhí)行消失厢洞,在這里調(diào)用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);
}
}
呦仇让,還良心地講解了這個機制是如何執(zhí)行的,好好好躺翻,這就可以一邊看著代碼一邊看著注釋兌著灌了妹孙。
首先,RecyclerView的onMeasure被調(diào)用获枝,如果MeasureSpec為EXACT,則不進行測量骇笔,直接返回(寬高已經(jīng)被訂好了)省店。否則,開始在onMeasure中處理布局流程笨触。它會處理所有的待處理adapter更新懦傍,并決定是不是要進行預(yù)布局。如果要進行預(yù)布局芦劣,會將state.preLayout()置為true粗俱,然后調(diào)用onLayoutChildren(Recycler, State). 此時,getWidth與getHeight依然返回上一次layout的結(jié)果虚吟。
預(yù)處理完畢后寸认,會設(shè)state.preLayout為false,state.isMeasuring為true串慰,此時偏塞,LayoutManager就可以通過getHeight、getHeightMode來獲取測量的specs了邦鲫。
layout計算完后灸叼,RecyclerView為子view們計算邊界盒(加上padding的大猩裥凇),設(shè)置測量過的height和width. LayoutManager可以通過復(fù)寫setMeasuredDimension(Rect, int, int)來選擇不同的值古今。比如屁魏,GridLayoutManager復(fù)寫這個值來處理三列布局時單排顯示兩個items的width計算。
此之后onMeasure的所有調(diào)用都會把狀態(tài)置為isMeasuring. RecyclerView管理view的增刪改操作捉腥,LayoutManager啥都不用管氓拼,只要把每個onLayoutChildren調(diào)用當做最后一次調(diào)用就可以了。
測量結(jié)束后但狭,RecyclerView的onLayout(boolean, int, int, int, int)被調(diào)用披诗。RecyclerView檢查在測量過程中是否進行了布局計算,如果是立磁,則重用其相關(guān)的信息呈队。如果最后一次的measure spec與最終的大小不匹配,或adapter的數(shù)據(jù)在measure和layout過程中被更改唱歧,它也可能再次調(diào)用onLayoutChildren.
最后的最后宪摧,計算動畫然后執(zhí)行。
上面就是對測量流程的粗略描述颅崩。下面看下代碼几于。
onMeasure被調(diào)用時,mState.mLayoutStep默認為STEP_START沿后,開始布局沿彭。調(diào)到dispatchLayoutStep1() 分發(fā)布局步驟1. 這個方法的注釋表示:第一步執(zhí)行以下操作:
1.處理adapter的更新
2.確定要執(zhí)行的動畫
3.保存當前views的信息
4.如果必要,運行可預(yù)測布局尖滚,并保存其信息