俗話說沛申,好看的皮囊千篇一律,有趣的靈魂萬里挑一耘拇。但是對于我們這些俗人來說撵颊,肯定是選擇好看的皮囊,咱們的用戶也是如此惫叛。你看看應(yīng)用市場上那些花枝招展的APP倡勇,哪個不是用上了五花八門的動畫效果,就算你的內(nèi)在安全省電性能好嘉涌,沒點兒花招可留不住花心的用戶译隘。所以我們今天就來看看怎么實現(xiàn)讓用戶眼前一亮的動畫,當(dāng)然原理也很重要洛心,因此源碼分析必不可少固耘,本文的源碼分析主要聚焦于動畫是怎么觸發(fā)的,以及動畫是怎么實現(xiàn)的词身。
一厅目、動畫的觸發(fā)與實現(xiàn)
當(dāng)Adapter中的數(shù)據(jù)發(fā)生變化時,我們通過notifyItemXXX()
等方法通知RecyclerView來改變數(shù)據(jù)的展示法严,這個過程必然伴隨新的layout()
损敷。如果在layout()
后直接顯示新數(shù)據(jù),效果比較僵硬深啤,因此需要通過動畫來制造良好的用戶體驗拗馒。
那么,為了實現(xiàn)動畫溯街,RecyclerView又額外做了哪些工作呢诱桂?抽象上來講洋丐,RecyclerView實現(xiàn)動畫的步驟如下。
① 數(shù)據(jù)發(fā)生改變時挥等,保存當(dāng)前的item信息為preInfo
② 根據(jù)新的數(shù)據(jù)Layout
③ Layout完畢友绝,保存當(dāng)前的item信息為postInfo
④ 根據(jù)preInfo和postInfo判斷動畫類型并交給ItemAnimator執(zhí)行
可以發(fā)現(xiàn),前3步保存了執(zhí)行動畫所需要的信息肝劲,最后整體交給ItemAnimator來執(zhí)行動畫迁客。前3步涉及到內(nèi)容較為復(fù)雜,我們先從簡單的開始分析辞槐,來看ItemAnimator是怎么實現(xiàn)動畫的掷漱。
1.1 動畫的實現(xiàn)
由于RecyclerView設(shè)計時的低耦合性,ItemAnimator只需要關(guān)注怎么執(zhí)行動畫即可榄檬,其邏輯并不復(fù)雜卜范。RecyclerView為我們實現(xiàn)了DefaultItemAnimator,在不設(shè)置動畫的情況下默認(rèn)使用它丙号,其中實現(xiàn)了4個針對item的動畫,分別為Remove缰冤、Move犬缨、Add和Change。以Remove動畫為例棉浸,當(dāng)一個item被移出RecyclerView時怀薛,DefaultItemAnimator中的animateRemove(holder)
方法就會被調(diào)用,但是并沒有馬上開始執(zhí)行動畫迷郑,而是將動畫添加到了mPendingRemovals中枝恋,這是一個待執(zhí)行的Romove動畫List。
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
mPendingRemovals.add(holder);
return true;
}
看一下DefaultItemAnimator的成員變量嗡害,原來有4個List分別存儲4種動畫焚碌。
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
當(dāng)RecyclerView要執(zhí)行動畫時,ItemAnimator的runPendingAnimations()
方法會被調(diào)用霸妹,DefaultItemAnimator重寫后的方法如下十电,為了便于閱讀,添加了注釋并省略了部分代碼叹螟。
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
// 判斷是否有動畫需要執(zhí)行
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
return;
}
// 執(zhí)行Remove動畫
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder); // 實際執(zhí)行Remove動畫的方法
}
mPendingRemovals.clear();
// Move動畫
if (movesPending) {
// 注意: Move動畫并不是馬上執(zhí)行鹃骂,會放入一個Runnable
Runnable mover = new Runnable() {
@Override
public void run() {
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
}
};
// 如果有Remove動畫,就在Remove動畫結(jié)束之后執(zhí)行Move動畫
// 如果沒有Remove動畫就馬上執(zhí)行
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Change動畫罢绽,與Move動畫一起執(zhí)行
if (changesPending) {
// ......
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
}
};
if (removalsPending) {
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
} else {
changer.run();
}
}
// 最后執(zhí)行Add動畫
if (additionsPending) {
// ......
Runnable adder = new Runnable() {
@Override
public void run() {
for (RecyclerView.ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
// 在Remove畏线、Move、Change動畫都完成之后開始執(zhí)行Add動畫
if (removalsPending || movesPending || changesPending) {
long removeDuration = removalsPending ? getRemoveDuration() : 0;
long moveDuration = movesPending ? getMoveDuration() : 0;
long changeDuration = changesPending ? getChangeDuration() : 0;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = additions.get(0).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
} else {
adder.run();
}
}
}
我們發(fā)現(xiàn)動畫的執(zhí)行是有順序的良价,Remove動畫首先執(zhí)行寝殴,之后Move和Change動畫同時開始蒿叠,等這3個動畫全部結(jié)束之后開始執(zhí)行Add動畫。以RecyclerView刪除一個item為例杯矩,動畫如下栈虚。
我們發(fā)現(xiàn)動畫有2段,首先是被刪除item的Remove動畫史隆,等到完全不可見之后魂务,下方的多個item同時執(zhí)行向上的Move動畫。相對的泌射,如果向RecyclerView中添加一個item粘姜,會先執(zhí)行item向下的Move動畫,再執(zhí)行插入item的Add動畫熔酷。
在runPendingAnimations()
中真正執(zhí)行動畫的是animateRemoveImpl()
這樣的方法孤紧,來看一下它是怎么實現(xiàn)的,這也是我們今后自定義動畫的關(guān)鍵拒秘。
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mRemoveAnimations.add(holder);
animation.setDuration(getRemoveDuration()).alpha(0).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
view.setAlpha(1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
Remove動畫很簡單号显,通過ViewPropertyAnimator實現(xiàn)一個透明度到0的屬性動畫,不過要記得在動畫開始和結(jié)束時調(diào)用dispatchRemoveStarting()
和dispatchRemoveFinished()
方法躺酒。相對的押蚤,Add動畫就是透明度從0到1的屬性動畫,而Remove動畫就是修改itemView的translation進行移動羹应。
以上3個動畫都只涉及1個item揽碘,而Change動畫會涉及到2個item,DefaultItemAnimator中的實現(xiàn)是讓原來的item執(zhí)行透明度從1到0的動畫园匹,讓新item執(zhí)行透明度從0到1的動畫雳刺。
void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
final View view = holder == null ? null : holder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view != null) {
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
getChangeDuration());
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(Animator animator) {
oldViewAnim.setListener(null);
view.setAlpha(1);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
}
if (newView != null) {
final ViewPropertyAnimator newViewAnimation = newView.animate();
mChangeAnimations.add(changeInfo.newHolder);
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
.alpha(1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.newHolder, false);
}
@Override
public void onAnimationEnd(Animator animator) {
newViewAnimation.setListener(null);
newView.setAlpha(1);
dispatchChangeFinished(changeInfo.newHolder, false);
mChangeAnimations.remove(changeInfo.newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
}
以上就是DefaultItemAnimator的基本邏輯,它主要做了兩件事:
① 統(tǒng)一安排動畫的執(zhí)行順序
② 對4種item動畫具體實現(xiàn)
自定義ItemAnimator時裸违,我們可以直接使用DefaultItemAnimator的runPendingAnimations()
方法來安排動畫的執(zhí)行順序掖桦,只需要修改item的動畫即可。
1.2 動畫的觸發(fā)
接下來我們來看動畫的觸發(fā)流程供汛,其關(guān)鍵就在于RecyclerView在執(zhí)行動畫前已經(jīng)計算出了每個item在動畫前后的位置等信息滞详,隨后將這些信息傳給ItemAnimator統(tǒng)一執(zhí)行。現(xiàn)在我們從notifyItemRemoved(int position)
開始分析動畫觸發(fā)的流程紊馏。
/**
* 通知注冊的觀察者料饥,position上的item從數(shù)據(jù)集中移除了
* 之前在oldPosition上的item可能會出現(xiàn)在oldPosition - 1的位置上
*/
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
mObservable是一個AdapterDataObservable對象,是RecyclerView數(shù)據(jù)集的被觀察者朱监。mObservable會通知所有的Observer數(shù)據(jù)發(fā)生了改變岸啡,RecyclerView有個默認(rèn)的觀察者RecyclerViewDataObserver,來看一下在item被Remove后它做了什么赫编。
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
直接執(zhí)行了triggerUpdateProcessor()
方法巡蘸。
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
這里有3個判斷條件扇调,POST_UPDATES_ON_ANIMATION在SDK>=16時為true旬迹;mHasFixedSize默認(rèn)為false啤贩,為true時有優(yōu)化性能的作用混萝,可以減少requestLayout()
方法的調(diào)用,這里先不展開搬味,之后性能優(yōu)化會提到它境氢。
當(dāng)mHasFixedSize為false時進入else代碼塊,執(zhí)行requestLayout()
方法碰纬,最終進入RecyclerView的onLayout()
方法萍聊。(PS: mHasFixedSize為true時最終也會執(zhí)行到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()
中調(diào)用了dispatchLayout()
方法,在看它的代碼前不妨先看下方法介紹悦析。介紹里第一句就表示寿桨,這個方法用于處理由Layout產(chǎn)生的動畫,并且將動畫分為了5種類型强戴。很顯然這個方法就是今天的主角亭螟,下面來重點分析。
/**
* Wrapper around layoutChildren() that handles animating changes caused by layout.
* Animations work on the assumption that there are five different kinds of items
* in play:
* PERSISTENT: items are visible before and after layout
* REMOVED: items were visible before layout and were removed by the app
* ADDED: items did not exist before layout and were added by the app
* DISAPPEARING: items exist in the data set before/after, but changed from
* visible to non-visible in the process of layout (they were moved off
* screen as a side-effect of other changes)
* APPEARING: items exist in the data set before/after, but changed from
* non-visible to visible in the process of layout (they were moved on
* screen as a side-effect of other changes)
*/
dispatchLayout()
代碼如下骑歹,它依次調(diào)用dispatchLayoutStep1()
预烙、dispatchLayoutStep2()
、dispatchLayoutStep3()
陵刹,我們來逐個分析默伍。
void dispatchLayout() {
// 判空......
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()
先來講dispatchLayoutStep1()
欢嘿,它的主要工作有:處理Adapter的數(shù)據(jù)更新衰琐、決定應(yīng)該運行哪種動畫、記錄當(dāng)前所有ItemView的信息炼蹦、進行預(yù)布局pre-layout并保存其信息羡宙。簡化后的代碼如下,只保留了動畫相關(guān)的部分掐隐。
private void dispatchLayoutStep1() {
// ......
if (mState.mRunSimpleAnimations) {
// 找到所有沒有被Remove的Item
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());
// 將Item信息保存到mViewInfoStore
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
// ......
}
}
}
if (mState.mRunPredictiveAnimations) {
// 開始pre-layout狗热,此時使用的是oldPositions
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)) {
// 這里保存原本在屏幕外的Item的信息,代碼省略......
}
}
// 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;
}
首先找到?jīng)]有被Remove的Item并保存信息虑省,保存時調(diào)用的是mViewInfoStore.addToPreLayout()
匿刮,可以理解為保存的是執(zhí)行動畫前的信息。
如果mState.mRunPredictiveAnimations
為true就開始執(zhí)行pre-layout探颈,pre-layout使用的是Item的oldPosition熟丸,它會對所有的Item(包括被Remove的Item)進行布局,并且為動畫后顯示在屏幕上的Item提供位置伪节。什么意思呢光羞?如果當(dāng)前某個Item會Remove绩鸣,原本屏幕外的Item就可能Move到屏幕上,這個Item的信息也需要被記錄纱兑,pre-layout就是為這類Item提供了顯示動畫的能力呀闻。
來看下mViewInfoStore.addToPreLayout(holder, animationInfo)
做了什么∏鄙鳎可以發(fā)現(xiàn)它通過鍵值對<ViewHolder, InfoRecord>保存Item的信息捡多,并且由于當(dāng)前保存的是動畫前的Item信息,為InfoRecord添加FLAG_PRE標(biāo)識勘纯。
void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
InfoRecord的數(shù)據(jù)結(jié)構(gòu)如下局服,在dispatchLayoutStep1()
中保存的是preInfo
static class InfoRecord {
static final int FLAG_DISAPPEARED = 1;
static final int FLAG_APPEAR = 1 << 1;
static final int FLAG_PRE = 1 << 2;
static final int FLAG_POST = 1 << 3;
static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
int flags;
@Nullable
RecyclerView.ItemAnimator.ItemHolderInfo preInfo;
@Nullable
RecyclerView.ItemAnimator.ItemHolderInfo postInfo;
}
dispatchLayoutStep2()
再來看dispatchLayoutStep2()
,它主要通過mLayout.onLayoutChildren(mRecycler, mState)
對新的數(shù)據(jù)進行了布局驳遵,隨后對是否支持動畫進行檢查并賦值淫奔。
private void dispatchLayoutStep2() {
// ......
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
dispatchLayoutStep3()
dispatchLayoutStep3()
對Change動畫進行了特殊處理,如果是Change動畫會直接執(zhí)行堤结。對于其余動畫來說唆迁,會先記錄動畫后的Item信息,記錄完畢后觸發(fā)動畫竞穷。
private void dispatchLayoutStep3() {
// 初始化...
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()) {
// 執(zhí)行CHANGE動畫
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// 如果1個Item CHANGED唐责,但是更新后會消失,則執(zhí)行disappear動畫
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 {
// 將Layout后的信息保存到mViewInfoStore中
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// 觸發(fā)動畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
// clean up...
}
這里通過mViewInfoStore.addToPostLayout(...)
將Layout后的信息保存瘾带,再執(zhí)行mViewInfoStore.process(mViewInfoProcessCallback)
觸發(fā)動畫鼠哥,主要邏輯為根據(jù)之前保存的item信息執(zhí)行對應(yīng)的回調(diào)。
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final RecyclerView.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) {
// 被LayoutManager設(shè)置為消失
if (record.preInfo == null) {
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) {
// preLayout和postLayout都在看政,執(zhí)行callback.processPersistent
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// 在pre-layout中朴恳,但是不在post-layout中,因此item消失了
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// 不在pre-layout允蚣,出現(xiàn)在了post-layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
}
InfoRecord.recycle(record);
}
}
回調(diào)mViewInfoProcessCallback如下所示于颖,基本就是執(zhí)行對應(yīng)的方法。
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) {
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);
}
};
我們以animateDisappearance(...)
為例來分析嚷兔,如果mItemAnimator對應(yīng)的方法返回true的話就執(zhí)行postAnimationRunner()
森渐,該方法就是將mItemAnimatorRunner放到下一幀執(zhí)行,而mItemAnimatorRunner實際調(diào)用了mItemAnimator.runPendingAnimations()
執(zhí)行了一段時間內(nèi)觸發(fā)的所有動畫冒晰。它們的代碼如下所示同衣。
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
addAnimatingView(holder);
holder.setIsRecyclable(false);
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
// 將mItemAnimatorRunner放到下一幀執(zhí)行
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
動畫觸發(fā)的大體邏輯就是這樣,不過為了加深印象壶运,我們再舉個栗子詳細描述下耐齐。還是以REMOVE動畫為例,來逐步分析下方動畫執(zhí)行時保存了哪些信息,又是怎么判斷動畫類型的蚪缀。
上面提到秫逝,dispatchLayoutStep1()
方法會將動畫執(zhí)行前的ItemHolderInfo保存至ViewInfoStore,那么上方的例子在動畫前會保存5個ItemHolderInfo询枚,由于是VERTICAL布局违帆,只關(guān)注ItemHolderInfo的top和bottom即可。
下方的ViewHolder(0)等表示保存時的Key金蜀,括號中的數(shù)字為該ViewHolder對應(yīng)ItemView顯示的數(shù)據(jù)刷后。
ViewHolder(0) : InfoRecord->preInfo(top: 0, bottom: 131)
ViewHolder(1) : InfoRecord->preInfo(top: 131, bottom: 262)
ViewHolder(2) : InfoRecord->preInfo(top: 262, bottom: 393)
ViewHolder(3) : InfoRecord->preInfo(top: 393, bottom: 524)
ViewHolder(4) : InfoRecord->preInfo(top: 524, bottom: 655)
隨后dispatchLayoutStep2()
調(diào)用mLayout.onLayoutChildren(mRecycler, mState)
進行布局。布局完畢后執(zhí)行dispatchLayoutStep3()
渊抄,開始保存Layout之后的ItemHolderInfo尝胆,此時有4個Item,它們的信息會被保存至InfoRecord的postInfo中护桦,最終ViewInfoStore中mLayoutHolderMap的信息如下所示含衔。
ViewHolder0 : InfoRecord->preInfo(top: 0, bottom: 131), postInfo(top: 0, bottom: 131)
ViewHolder1 : InfoRecord->preInfo(top: 131, bottom: 262), postInfo(null)
ViewHolder2 : InfoRecord->preInfo(top: 262, bottom: 393), postInfo(top: 131, bottom: 262)
ViewHolder3 : InfoRecord->preInfo(top: 393, bottom: 524), postInfo(top: 262, bottom: 393)
ViewHolder4 : InfoRecord->preInfo(top: 524, bottom: 655), postInfo(top: 393, bottom: 524)
隨后執(zhí)行mViewInfoStore.process(mViewInfoProcessCallback)
開始動畫,這里通過判斷preInfo和postInfo是否存在去執(zhí)行對應(yīng)的回調(diào)二庵,下面的代碼只保留了本次例子相關(guān)的部分贪染。
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
// ......
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// ......
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// ......
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// preInfo和postInfo都有,執(zhí)行callback.processPersistent(...)
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// 有preInfo沒有postInfo催享,說明Item被移除了杭隙,執(zhí)行callback.processDisappeared()
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// ......
} else if ((record.flags & FLAG_APPEAR) != 0) {
// ......
}
InfoRecord.recycle(record);
}
}
先來看callback.processPersistent()
,由于整個dataSet并未改變因妙,因此進入else代碼塊痰憎,根據(jù)mItemAnimator.animatePersistence(...)
的返回值決定是否執(zhí)行postAnimationRunner()
@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();
}
}
而mItemAnimator.animatePersistence(...)
執(zhí)行的是SimpleItemAnimator中重寫的方法,如下所示攀涵。根據(jù)上面總結(jié)的ViewInfoStore中保存的值铣耘,對于ViewHolder(2)、ViewHolder(3)和ViewHolder(4)來說汁果,preInfo.top和postInfo.top不相等涡拘,執(zhí)行animateMove(...)
玲躯,最終會執(zhí)行DefaultItemAnimator中重寫的方法据德,將MOVE動畫添加到待執(zhí)行動畫列表中。而對于ViewHolder(0)來說跷车,preInfo和postInfo中的值相等棘利,就不用執(zhí)行動畫。
@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
return animateMove(viewHolder,
preInfo.left, preInfo.top, postInfo.left, postInfo.top);
}
dispatchMoveFinished(viewHolder);
return false;
}
再來看callback.processDisappeared()
朽缴,直接執(zhí)行了animateDisappearance(...)
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
也是根據(jù)mItemAnimator.animateDisappearance()
的返回值決定是否執(zhí)行postAnimationRunner()
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
addAnimatingView(holder);
holder.setIsRecyclable(false);
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
mItemAnimator.animateDisappearance()
執(zhí)行了SimpleItemAnimator中重寫的方法善玫。
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
int oldLeft = preLayoutInfo.left;
int oldTop = preLayoutInfo.top;
View disappearingItemView = viewHolder.itemView;
int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
disappearingItemView.layout(newLeft, newTop,
newLeft + disappearingItemView.getWidth(),
newTop + disappearingItemView.getHeight());
}
return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
} else {
return animateRemove(viewHolder);
}
}
由于viewHolder.isRemoved()
返回true,因此執(zhí)行animateRemove(viewHolder)
密强,最終執(zhí)行DefaultItemAnimator中重寫的方法茅郎,將動畫添加到了執(zhí)行隊列中蜗元。
到此,我們的例子也就講完了系冗。
二奕扣、自定義動畫
DefaultItemAnimator提供的動畫效果還是比較完善的,如果還有其他需求的話掌敬,在animateRemoveImpl(...)
和animateAddImpl(...)
這樣的方法中修改動畫效果即可惯豆。方法內(nèi)部通過ViewPropertyAnimator實現(xiàn)具體的動畫效果,修改起來比較簡單奔害,例如我們可以將DefaultItemAnimator的Remove動畫修改為:Item縮小至消失楷兽。
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
// ......
animation.setDuration(getRemoveDuration()).scaleX(0).scaleY(0).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
view.setScaleX(1);
view.setScaleY(1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
真的超簡單啊有沒有!;佟芯杀!來看下效果。
emmmm...效果不怎么好看雅潭,不過只要能設(shè)計出優(yōu)美的動畫瘪匿,自定義ItemAnimator是一件很簡單的事情。