??距離上一篇RecyclerView源碼分析的文章已經(jīng)過去了10多天,今天我們將來(lái)看看RecyclerView的動(dòng)畫機(jī)制。不過,本文不會(huì)分析ItemAnimator相關(guān)的知識(shí),而是理解RecyclerView怎么執(zhí)行ItemAnimator的,有關(guān)ItemAniamtor的知識(shí),后面我會(huì)寫專門的文章來(lái)分析。
??本文參考資料:
??注意,本文所有的代碼都來(lái)自于27.1.1。
1. 概述
??RecyclerView之所以受歡迎,有一部分的原因得歸功于它的動(dòng)畫機(jī)制。我們可以通過RecyclerView的setItemAnimator方法來(lái)給每個(gè)Item設(shè)置在不同行為下,執(zhí)行不同的動(dòng)畫,非常的簡(jiǎn)單。盡管我們知道怎么給RecyclerView設(shè)置動(dòng)畫,但是RecyclerView是怎么通過ItemAnimator來(lái)給每個(gè)Item實(shí)現(xiàn)動(dòng)畫,這里面的原理值得我們?nèi)パ芯亢蛯W(xué)習(xí)。
??在正式分析RecyclerView的動(dòng)畫機(jī)制之前,我們先對(duì)幾個(gè)詞語(yǔ)有一個(gè)概念,我們來(lái)看看:
| 詞語(yǔ) | 含義 |
|---|---|
| Disappearance | 表示在動(dòng)畫之前,ItemView是可見的,動(dòng)畫之后就可不見了。這里的操作包括,remove操作和普通的滑動(dòng)導(dǎo)致ItemView劃出屏幕 |
| Appearance | 表示動(dòng)畫之前,ItemView是不可見,動(dòng)畫之后就可見了。這里的操作包括,add操作和普通的滑動(dòng)導(dǎo)致ItemView劃入屏幕 |
| Persistence | 表示動(dòng)畫前后,狀態(tài)是不變的。這里面的操作包括,無(wú)任何操作 |
| change | 表示動(dòng)畫前后,狀態(tài)是不變的。這里面的操作包括,change操作。 |
??還有注意的一點(diǎn)就是,ViewHolder不是用來(lái)記錄ItemView的位置信息,而是進(jìn)行數(shù)據(jù)綁定的,所以在動(dòng)畫中,關(guān)于位置信息的記錄不是依靠ViewHolder來(lái)實(shí)現(xiàn)的,而是依靠一個(gè)叫ItemHolderInfo的類實(shí)現(xiàn)的,在這個(gè)類里面,有四個(gè)成員變量,分別記錄ItemView的left、top、right和bottom四個(gè)位置信息。
??最后還需要注意一點(diǎn)就是,我們從RecyclerView的三大流程中可以得到,在RecyclerView的內(nèi)部,dispatchLayout分為三步,其中dispathchLayoutStep1被稱為預(yù)布局,在這里主要是保存ItemView的OldViewHolder,同時(shí)還會(huì)記錄下每個(gè)ItemView在動(dòng)畫之前的位置信息;與之對(duì)應(yīng)的dispathchLayoutStep3被稱為后布局,主要結(jié)合真正布局和預(yù)布局的相關(guān)信息來(lái)實(shí)現(xiàn)進(jìn)行動(dòng)畫,當(dāng)然前提是RecyclerView本身支持動(dòng)畫。
??本文打算從兩個(gè)角度來(lái)分析RecyclerView的動(dòng)畫,一是從普通三大的流程來(lái)看,這是動(dòng)畫機(jī)制的核心所在;而是從Adapeter的角度上來(lái)看,看看我們每次在調(diào)用Adapter的notify相關(guān)方法之后,是怎么進(jìn)行執(zhí)行動(dòng)畫的(實(shí)際上也是回到三大流程里面)。
1. 再來(lái)看RecyclerView的三大流程
??取這個(gè)題目,我感覺有特別的含義。首先,本次分析動(dòng)畫機(jī)制就是重新來(lái)看看三大流程,當(dāng)然本次分三大流程肯定沒有之前的那么仔細(xì),其次側(cè)重點(diǎn)也不同;其次,本次再來(lái)看RecyclerView的三大流程,還可以填之前在分析RecyclerView的三大流程留下的坑。
??本次的分析重點(diǎn)在于dispathchLayoutStep1和dispathchLayoutStep3,不會(huì)分析完整的三大流程,所以,還有不懂RecyclerView三大流程的同學(xué),可以參考我的文章:RecyclerView 源碼分析(一) - RecyclerView的三大流程。
??我們先來(lái)看看dispatchLayoutStep1方法:
private void 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);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
??我將dispatchLayoutStep1方法分為2步(實(shí)際上從谷歌爸爸的注釋,我們也可以得出來(lái))。
- 找到每個(gè)沒有被remove 掉的ItemView,將它的ViewHolder(OldViewHolder)放在
ViewInfoStore里面,同時(shí)還將它預(yù)布局的位置放在ViewInfoStore里面。這兩個(gè)信息在后面做動(dòng)畫時(shí)都會(huì)用到。- 如果當(dāng)前
RecyclerView的LayoutManager支持predictive item animations(supportsPredictiveItemAnimations方法返回true,我覺得用英語(yǔ)描述這種動(dòng)畫挺好的,因?yàn)槲也恢涝趺捶g),會(huì)真正的進(jìn)行預(yù)布局。在這一步,會(huì)先調(diào)用LayoutManager的onLayoutChildren進(jìn)行一次布局,不過這次布局知識(shí)預(yù)布局,也就是說(shuō)不是真正的布局,只是先確定每個(gè)ItemView的位置。預(yù)布局之后,此時(shí)取到的每個(gè)ItemView的ViewHolder和ItemHolderInfo,便是每個(gè)ItemView的最終信息。
??第二步的信息與第一步的信息相互呼應(yīng),第一步是變化前的信息,第二步是變化后的信息。這些都是為dispatchLayout3階段的動(dòng)畫做準(zhǔn)備。其中,我們發(fā)現(xiàn)相對(duì)于第一步,第二步變得復(fù)雜了很多。不過,我們可以發(fā)現(xiàn),不管怎么復(fù)雜,都是通過調(diào)用addToOldChangeHolders方法來(lái)保存當(dāng)前ItemView的ViewHolder(在LayoutManager的onLayoutChildren方法前后,在同一個(gè)位置上,不一定是同一個(gè)ItemView,也不一定是同一個(gè)ViewHolder),然后調(diào)用addXXXLayout方法將位置信息(ItemHolderInfo)保存起來(lái)。
??然后,我們?cè)賮?lái)看看dispatchLayoutStep3階段:
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
// 將相關(guān)信息取到,然后添加到ViewInfoStore
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
// 觸發(fā)動(dòng)畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
// 清理工作階段
}
??我將上面的代碼分為3階段。
- 獲得相關(guān)的位置信息(ItemHolderInfo),然后通過
addToPostLayout方法將位置保存在ViewInfoStore里面。- 調(diào)用
ViewInfoStore的process方法觸發(fā)動(dòng)畫。- 進(jìn)行相關(guān)的清理工作。
??這里,我們重點(diǎn)關(guān)注前兩步就行了。
??其中第一步非常容易理解,先是獲到當(dāng)前ItemView的位置信息,保存在ViewInfoStore里面。其中,我們?cè)谶@里發(fā)現(xiàn),如果OldViewHolder不為空的話,會(huì)特別處理,為什么會(huì)這樣處理的呢?其實(shí)這里考慮到change操作,因?yàn)閏hange操作會(huì)涉及到兩個(gè)ItemView的動(dòng)畫變化,所以,我們發(fā)現(xiàn),如果一個(gè)ItemView調(diào)用的是animateChange方法進(jìn)行動(dòng)畫開始,而不是走通用的邏輯(將位置信息通過addToPostLayout方法保存起來(lái),然后調(diào)用process方法進(jìn)行統(tǒng)一的調(diào)用)。
??然后就是第二步。我們來(lái)看看ViewInfoStore的process方法,不過在我們?cè)谶@方法之前,我們我們先看看ProcessCallback接口的幾個(gè)方法。
| 方法 | 作用 |
|---|---|
| processDisappeared | 一個(gè)ItemView從可見到不可見會(huì)回調(diào)這個(gè)方法,主要是執(zhí)行這種情況下的動(dòng)畫 |
| processAppeared | 一個(gè)ItemView從不可見到可見會(huì)回調(diào)這個(gè)方法。 |
| processPersistent | 一個(gè)ItemView動(dòng)畫前后狀態(tài)為改變,這里面包括:本身未發(fā)生任何操作的ItemView、change操作的ItemView |
| unused | 一個(gè)ItemView的變化不支持動(dòng)畫會(huì)回調(diào)此方法,這里包括比如一個(gè)ItemView先是Appeared然后disappeared,這種情況RecyclerView找不到合適的動(dòng)畫;還有當(dāng)前ItemView缺少preInfo,也就是在預(yù)布局未記錄位置信息,也會(huì)調(diào)用此方法,這種情況經(jīng)常是ItemView進(jìn)行remove操作,但是Adapter調(diào)用的是notifyDataSetChanged方法 |
??現(xiàn)在,我們正式的來(lái)看看process方法:
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
// Appeared then disappeared. Not useful for animations.
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
if (record.preInfo == null) {
// similar to appear disappear but happened between different layout passes.
// this can happen when the layout manager is using auto-measure
callback.unused(viewHolder);
} else {
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
}
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// Persistent in both passes. Animate persistence
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// Was in pre-layout, never been added to post layout
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
} else if (DEBUG) {
throw new IllegalStateException("record without any reasonable flag combination:/");
}
InfoRecord.recycle(record);
}
}
??其實(shí)process方法非常簡(jiǎn)單,就是通過相關(guān)的flag來(lái)調(diào)用ProcessCallback相關(guān)的方法。我們現(xiàn)在來(lái)同一個(gè)看看ProcessCallback的每個(gè)方法都怎么實(shí)現(xiàn)的。
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
@Override
public void processPersistent(ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
viewHolder.setIsRecyclable(false);
if (mDataSetHasChangedAfterLayout) {
// since it was rebound, use change instead as we'll be mapping them from
// stable ids. If stable ids were false, we would not be running any
// animations
if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
postInfo)) {
postAnimationRunner();
}
} else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
@Override
public void unused(ViewHolder viewHolder) {
mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}
};
??其實(shí)說(shuō)到底,就是調(diào)用了animateXXX方法來(lái)實(shí)現(xiàn),而animateXXX方法里面做了啥?其實(shí)沒啥,就是調(diào)用了ViewCompat的postOnAnimation方法往任務(wù)隊(duì)列后面post一個(gè)Runnable。代碼如下:
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
??其中,上面的代碼中,我們需要注意的是,postAnimationRunner每次只會(huì)被調(diào)用一次。那么如果在某一次操作中,會(huì)執(zhí)行多個(gè)動(dòng)畫,怎么辦呢?ProcessCallback每個(gè)回調(diào)方法都會(huì)調(diào)用animateXXX方法,而animateXXX方法會(huì)調(diào)用ItemAnimator對(duì)應(yīng)的方法,在ItemAnimator里面,會(huì)將當(dāng)前動(dòng)畫添加到一個(gè)數(shù)組里面,然后通過mItemAnimatorRunner調(diào)用ItemAnimator的runPendingAnimations方法,runPendingAnimations方法就是所有動(dòng)畫開始的起點(diǎn)。這里,我們就不討論ItemAnimator內(nèi)部的實(shí)現(xiàn),后面有專門的文章來(lái)分析它。
2. 從Adapter角度來(lái)看動(dòng)畫執(zhí)行的機(jī)制
??我們知道,調(diào)用Adapter的notifyDataSetChanged方法,RecyclerView是不會(huì)執(zhí)行動(dòng)畫的;而調(diào)用notifyItemRemoved之類的方法是有動(dòng)畫,這里我們從Adapter的角度來(lái)分析動(dòng)畫。跟ItemAnimator一樣,這里我們也不會(huì)去分析Adapter,后面會(huì)有專門的文章分析它。
??在分析Adapter之前,我們先來(lái)看一個(gè)東西,就是RecyclerView和Adapter怎么進(jìn)行通信。
(1).通過觀察者模式來(lái)實(shí)現(xiàn)RecyclerView 和Adapter的通信
??我們思考這個(gè)問題之前,首先應(yīng)該排除Addapter和RecyclerView是強(qiáng)耦合的,也就是說(shuō),Adapter內(nèi)部持有一個(gè)RecyclerView對(duì)象。RecyclerView本身就是插拔式設(shè)計(jì),如果Adapter和RecyclerView是強(qiáng)耦合,就違背了插拔式的設(shè)計(jì)思想。那么它倆究竟是怎么進(jìn)行通信的呢?答案已經(jīng)非常的明顯了,兩者是通過觀察者模式來(lái)進(jìn)行通信。
??這其中,Adapter作為被觀察者,RecyclerView作為觀察者,當(dāng)Adapter的數(shù)據(jù)發(fā)生改變時(shí),會(huì)通知它的每個(gè)觀察者。
??RecyclerView本身設(shè)計(jì)又比較特殊,RecyclerView沒有去實(shí)現(xiàn)Observer(這里暫且這么稱呼)接口,而是內(nèi)部持有一個(gè)Observer(RecyclerViewDataObserver)對(duì)象,進(jìn)而監(jiān)聽Adapter的狀態(tài)變化;當(dāng)然Adapter也是如此,并沒有去實(shí)現(xiàn)Observable接口,也是在內(nèi)部持有一個(gè)Observable(AdapterDataObservable)對(duì)象。
??我們來(lái)看Adapter的notify方法跟Observer的方法是怎么進(jìn)行對(duì)應(yīng)的。
| Adapter的notify方法 | 與之對(duì)應(yīng)的Observer的方法 |
|---|---|
| notifyItemRemoved | notifyItemRangeRemoved |
| notifyItemChanged | notifyItemRangeChanged |
| notifyItemInserted | notifyItemRangeInserted |
| notifyItemMoved | notifyItemMoved |
??調(diào)用到Observer的方法時(shí),Observer會(huì)調(diào)用AdapterHelper相關(guān)的方法,在AdapterHelper內(nèi)部會(huì)為每個(gè)操作創(chuàng)建一個(gè)UpdateOp對(duì)象,并且添加到一個(gè)PendingUpdate數(shù)組。我們來(lái)看看相關(guān)代碼(以add為例):
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
??如果onItemRangeInserted返回為true,就調(diào)用triggerUpdateProcessor方法。為什么這里需要判斷是否調(diào)用triggerUpdateProcessor方法,其實(shí)是為了避免多次調(diào)用,比如一個(gè)操作,可能會(huì)導(dǎo)致多種動(dòng)畫執(zhí)行,所以這里保證triggerUpdateProcessor方法只會(huì)被調(diào)用一次。
??然后,我們來(lái)看看triggerUpdateProcessor方法:
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
??其實(shí)不管是if的執(zhí)行語(yǔ)句,還是else里面,最終還是調(diào)用了requestLayout方法,重新走一遍三大流程。
??可見而知,RecyclerView的三大流程到底多么重要。這次,我們看三大流程中的dispatchLayoutStep2方法。我們知道,在Observer階段,每個(gè)操作其實(shí)都創(chuàng)建了一個(gè)UpdateOp對(duì)象,添加到PendingUpdate數(shù)組。那么數(shù)組里面的操作都是什么時(shí)候執(zhí)行的呢?其實(shí)就是在dispatchLayoutStep2方法階段:
private void dispatchLayoutStep2() {
// ······
mAdapterHelper.consumeUpdatesInOnePass();
// ······
}
??真正執(zhí)行PendingUpdate的操作是在AdapterHelper的consumeUpdatesInOnePass方法里面,我們來(lái)瞧瞧:
void consumeUpdatesInOnePass() {
// we still consume postponed updates (if there is) in case there was a pre-process call
// w/o a matching consumePostponedUpdates.
consumePostponedUpdates();
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
break;
case UpdateOp.REMOVE:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.onDispatchSecondPass(op);
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
break;
case UpdateOp.MOVE:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
recycleUpdateOpsAndClearList(mPendingUpdates);
mExistingUpdateTypes = 0;
}
??雖然代碼不少,但是我們發(fā)現(xiàn)了,最終的操作都是調(diào)用到了Callback接口里面了。而Callback做了什么呢?主要是做了兩件事:
- 可能會(huì)更新一些ViewHolder的position
- 會(huì)更新一些ViewHolder的flag,比如說(shuō),remove的flag或者update的flag。
??這部分的內(nèi)容,我們后面分析Adapter會(huì)詳細(xì)的分析,本文就不做過多的介紹了。
??到這里,每個(gè)ViewHolder的position都更新完畢,并且每個(gè)ViewHolder的flag也已經(jīng)更新完畢。這樣,到了dispatchLayoutStep3階段,就知道每個(gè)ViewHolder應(yīng)該做什么動(dòng)畫。
??然后,我們來(lái)看看為什么調(diào)用Adapter的notifyDataSetChanged方法不執(zhí)行動(dòng)畫呢?
(2). 為什么notifyDataSetChanged方法不會(huì)執(zhí)行動(dòng)畫呢?
??notifyDataSetChanged方法會(huì)回調(diào)到Observer的notifyChanged方法里面,我們看看notifyChanged方法干什么:
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
??在這個(gè)方法里面,我們需要特別關(guān)注processDataSetCompletelyChanged方法。我們來(lái)看看:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
??在processDataSetCompletelyChanged方法里面,調(diào)用了markKnownViewsInvalid方法所有的ViewHolder標(biāo)記為了FLAG_INVALID。這個(gè)操作直接導(dǎo)致了,我們?cè)陬A(yù)布局階段不能正確獲得每個(gè)ItemView的位置信息和OldViewHolder,進(jìn)而導(dǎo)致在后布局階段不能執(zhí)行動(dòng)畫。這就是notifyDataSetChanged方法為什么不執(zhí)行動(dòng)畫的原因。
3. 總結(jié)
??RecyclerView的動(dòng)畫機(jī)制還是比較簡(jiǎn)單的,這里我們對(duì)它做一個(gè)簡(jiǎn)單的總結(jié)。
RecyclerView執(zhí)行動(dòng)畫的機(jī)制在于,在預(yù)布局階段將每個(gè)ItemView的位置信息和ViewHolder保存起來(lái),在后布局階段,根據(jù)每個(gè)ItemView的ViewHolderflag狀態(tài)來(lái)判斷執(zhí)行什么動(dòng)畫,根據(jù)位置信息來(lái)判斷怎么做動(dòng)畫。- Adapter的notify方法之所以能夠執(zhí)行動(dòng)畫,是因?yàn)樗麄冊(cè)谌罅鞒讨薪o每個(gè)
ViewHolder打了響應(yīng)的flag,包括remove的flag或者update的flag等。而在后布局中,正是根據(jù)flag來(lái)執(zhí)行不同的動(dòng)畫的。notifyDataSetChanged方法之所以不支持動(dòng)畫,那是因?yàn)?code>notifyDataSetChanged方法會(huì)使每個(gè)ViewHolder失效(打了FLAG_INVALID標(biāo)記),所以導(dǎo)致在預(yù)布局階段,不能正確的獲得每個(gè)ItemView的位置信息和ViewHolder,進(jìn)而導(dǎo)致動(dòng)畫不能執(zhí)行。
??如果不出意外的話,下一篇文章將分析Adapter