俗話說,好看的皮囊千篇一律,有趣的靈魂萬里挑一。但是對於我們這些俗人來說,肯定是選擇好看的皮囊,咱們的用戶也是如此。你看看應用市場上那些花枝招展的APP,哪個不是用上了五花八門的動畫效果,就算你的內在安全省電性能好,沒點兒花招可留不住花心的用戶。所以我們今天就來看看怎麼實現讓用戶眼前一亮的動畫,當然原理也很重要,因此源碼分析必不可少,本文的源碼分析主要聚焦於動畫是怎麼觸發的,以及動畫是怎麼實現的。
一、動畫的觸發與實現
當Adapter中的數據發生變化時,我們通過notifyItemXXX()
等方法通知RecyclerView來改變數據的展示,這個過程必然伴隨新的layout()
。如果在layout()
後直接顯示新數據,效果比較僵硬,因此需要通過動畫來製造良好的用戶體驗。
那麼,爲了實現動畫,RecyclerView又額外做了哪些工作呢?抽象上來講,RecyclerView實現動畫的步驟如下。
① 數據發生改變時,保存當前的item信息爲preInfo
② 根據新的數據Layout
③ Layout完畢,保存當前的item信息爲postInfo
④ 根據preInfo和postInfo判斷動畫類型並交給ItemAnimator執行
可以發現,前3步保存了執行動畫所需要的信息,最後整體交給ItemAnimator來執行動畫。前3步涉及到內容較爲複雜,我們先從簡單的開始分析,來看ItemAnimator是怎麼實現動畫的。
1.1 動畫的實現
由於RecyclerView設計時的低耦合性,ItemAnimator只需要關注怎麼執行動畫即可,其邏輯並不複雜。RecyclerView爲我們實現了DefaultItemAnimator,在不設置動畫的情況下默認使用它,其中實現了4個針對item的動畫,分別爲Remove、Move、Add和Change。以Remove動畫爲例,當一個item被移出RecyclerView時,DefaultItemAnimator中的animateRemove(holder)
方法就會被調用,但是並沒有馬上開始執行動畫,而是將動畫添加到了mPendingRemovals中,這是一個待執行的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<>();
當RecyclerView要執行動畫時,ItemAnimator的runPendingAnimations()
方法會被調用,DefaultItemAnimator重寫後的方法如下,爲了便於閱讀,添加了註釋並省略了部分代碼。
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
// 判斷是否有動畫需要執行
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
return;
}
// 執行Remove動畫
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder); // 實際執行Remove動畫的方法
}
mPendingRemovals.clear();
// Move動畫
if (movesPending) {
// 注意: Move動畫並不是馬上執行,會放入一個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動畫結束之後執行Move動畫
// 如果沒有Remove動畫就馬上執行
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Change動畫,與Move動畫一起執行
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();
}
}
// 最後執行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動畫都完成之後開始執行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();
}
}
}
我們發現動畫的執行是有順序的,Remove動畫首先執行,之後Move和Change動畫同時開始,等這3個動畫全部結束之後開始執行Add動畫。以RecyclerView刪除一個item爲例,動畫如下。
我們發現動畫有2段,首先是被刪除item的Remove動畫,等到完全不可見之後,下方的多個item同時執行向上的Move動畫。相對的,如果向RecyclerView中添加一個item,會先執行item向下的Move動畫,再執行插入item的Add動畫。
在runPendingAnimations()
中真正執行動畫的是animateRemoveImpl()
這樣的方法,來看一下它是怎麼實現的,這也是我們今後自定義動畫的關鍵。
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實現一個透明度到0的屬性動畫,不過要記得在動畫開始和結束時調用dispatchRemoveStarting()
和dispatchRemoveFinished()
方法。相對的,Add動畫就是透明度從0到1的屬性動畫,而Remove動畫就是修改itemView的translation進行移動。
以上3個動畫都只涉及1個item,而Change動畫會涉及到2個item,DefaultItemAnimator中的實現是讓原來的item執行透明度從1到0的動畫,讓新item執行透明度從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的基本邏輯,它主要做了兩件事:
① 統一安排動畫的執行順序
② 對4種item動畫具體實現
自定義ItemAnimator時,我們可以直接使用DefaultItemAnimator的runPendingAnimations()
方法來安排動畫的執行順序,只需要修改item的動畫即可。
1.2 動畫的觸發
接下來我們來看動畫的觸發流程,其關鍵就在於RecyclerView在執行動畫前已經計算出了每個item在動畫前後的位置等信息,隨後將這些信息傳給ItemAnimator統一執行。現在我們從notifyItemRemoved(int position)
開始分析動畫觸發的流程。
/**
* 通知註冊的觀察者,position上的item從數據集中移除了
* 之前在oldPosition上的item可能會出現在oldPosition - 1的位置上
*/
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
mObservable是一個AdapterDataObservable對象,是RecyclerView數據集的被觀察者。mObservable會通知所有的Observer數據發生了改變,RecyclerView有個默認的觀察者RecyclerViewDataObserver,來看一下在item被Remove後它做了什麼。
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
直接執行了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默認爲false,爲true時有優化性能的作用,可以減少requestLayout()
方法的調用,這裏先不展開,之後性能優化會提到它。
當mHasFixedSize爲false時進入else代碼塊,執行requestLayout()
方法,最終進入RecyclerView的onLayout()
方法。(PS: mHasFixedSize爲true時最終也會執行到onLayout()
方法)
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
onLayout()
中調用了dispatchLayout()
方法,在看它的代碼前不妨先看下方法介紹。介紹裏第一句就表示,這個方法用於處理由Layout產生的動畫,並且將動畫分爲了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()
代碼如下,它依次調用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的數據更新、決定應該運行哪種動畫、記錄當前所有ItemView的信息、進行預佈局pre-layout並保存其信息。簡化後的代碼如下,只保留了動畫相關的部分。
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;
}
首先找到沒有被Remove的Item並保存信息,保存時調用的是mViewInfoStore.addToPreLayout()
,可以理解爲保存的是執行動畫前的信息。
如果mState.mRunPredictiveAnimations
爲true就開始執行pre-layout,pre-layout使用的是Item的oldPosition,它會對所有的Item(包括被Remove的Item)進行佈局,並且爲動畫後顯示在屏幕上的Item提供位置。什麼意思呢?如果當前某個Item會Remove,原本屏幕外的Item就可能Move到屏幕上,這個Item的信息也需要被記錄,pre-layout就是爲這類Item提供了顯示動畫的能力。
來看下mViewInfoStore.addToPreLayout(holder, animationInfo)
做了什麼。可以發現它通過鍵值對<ViewHolder, InfoRecord>保存Item的信息,並且由於當前保存的是動畫前的Item信息,爲InfoRecord添加FLAG_PRE標識。
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的數據結構如下,在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)
對新的數據進行了佈局,隨後對是否支持動畫進行檢查並賦值。
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動畫會直接執行。對於其餘動畫來說,會先記錄動畫後的Item信息,記錄完畢後觸發動畫。
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()) {
// 執行CHANGE動畫
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// 如果1個Item CHANGED,但是更新後會消失,則執行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);
}
}
// 觸發動畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
// clean up...
}
這裏通過mViewInfoStore.addToPostLayout(...)
將Layout後的信息保存,再執行mViewInfoStore.process(mViewInfoProcessCallback)
觸發動畫,主要邏輯爲根據之前保存的item信息執行對應的回調。
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設置爲消失
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都在,執行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,出現在了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);
}
}
回調mViewInfoProcessCallback如下所示,基本就是執行對應的方法。
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對應的方法返回true的話就執行postAnimationRunner()
,該方法就是將mItemAnimatorRunner放到下一幀執行,而mItemAnimatorRunner實際調用了mItemAnimator.runPendingAnimations()
執行了一段時間內觸發的所有動畫。它們的代碼如下所示。
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放到下一幀執行
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
動畫觸發的大體邏輯就是這樣,不過爲了加深印象,我們再舉個栗子詳細描述下。還是以REMOVE動畫爲例,來逐步分析下方動畫執行時保存了哪些信息,又是怎麼判斷動畫類型的。
上面提到,dispatchLayoutStep1()
方法會將動畫執行前的ItemHolderInfo保存至ViewInfoStore,那麼上方的例子在動畫前會保存5個ItemHolderInfo,由於是VERTICAL佈局,只關注ItemHolderInfo的top和bottom即可。
下方的ViewHolder(0)等表示保存時的Key,括號中的數字爲該ViewHolder對應ItemView顯示的數據。
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()
調用mLayout.onLayoutChildren(mRecycler, mState)
進行佈局。佈局完畢後執行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)
隨後執行mViewInfoStore.process(mViewInfoProcessCallback)
開始動畫,這裏通過判斷preInfo和postInfo是否存在去執行對應的回調,下面的代碼只保留了本次例子相關的部分。
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都有,執行callback.processPersistent(...)
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// 有preInfo沒有postInfo,說明Item被移除了,執行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代碼塊,根據mItemAnimator.animatePersistence(...)
的返回值決定是否執行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(...)
執行的是SimpleItemAnimator中重寫的方法,如下所示。根據上面總結的ViewInfoStore中保存的值,對於ViewHolder(2)、ViewHolder(3)和ViewHolder(4)來說,preInfo.top和postInfo.top不相等,執行animateMove(...)
,最終會執行DefaultItemAnimator中重寫的方法,將MOVE動畫添加到待執行動畫列表中。而對於ViewHolder(0)來說,preInfo和postInfo中的值相等,就不用執行動畫。
@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()
,直接執行了animateDisappearance(...)
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
也是根據mItemAnimator.animateDisappearance()
的返回值決定是否執行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()
執行了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,因此執行animateRemove(viewHolder)
,最終執行DefaultItemAnimator中重寫的方法,將動畫添加到了執行隊列中。
到此,我們的例子也就講完了。
二、自定義動畫
DefaultItemAnimator提供的動畫效果還是比較完善的,如果還有其他需求的話,在animateRemoveImpl(...)
和animateAddImpl(...)
這樣的方法中修改動畫效果即可。方法內部通過ViewPropertyAnimator實現具體的動畫效果,修改起來比較簡單,例如我們可以將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…效果不怎麼好看,不過只要能設計出優美的動畫,自定義ItemAnimator是一件很簡單的事情。