安卓動畫(三)Recyclerview ItemAnimator(下)
前面講了補間動畫、Transition動畫,屬性動畫,這一篇講一講和Recyclerview相關的動畫。
Recyclerview裏面有一個類ItemAnimator專門用來管理每個item變化時所對應的動畫,當item被刪除、添加、位置發生變化時,相應的動畫就會被調用,從而給用戶更好的體驗,這一篇就原理、流程做一個大概的介紹。
由於ItemAnimator的流程還是比較複雜的,因爲和recyclerview的layout過程是有一定耦合的而且涉及到ViewHolder的複用,因此只能簡單的講一下ItemAnimator的封裝過程。ItemAnimator這個類裏面有這麼幾個重要的方法:
public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
@NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
上面幾個方法看名字和源碼註釋就可以確定分別和view的出現、消失、改變等狀態對應,比如當一個item要被remove掉,那麼就要對這個ViewHolder調用animateDisappearance
方法。然後還有運行動畫的方法
public abstract void runPendingAnimations();
這是一個抽象方法,需要實現,主要是觸發動畫的開始,由於ItemAnimator
這個類比較抽象,安卓爲我們封裝了一個類SimpleItemAnimator
繼承自RecyclerView.ItemAnimator
,而且還拋出了幾個抽象方法,需要我們實現:
public abstract boolean animateAdd(RecyclerView.ViewHolder holder);
public abstract boolean animateRemove(RecyclerView.ViewHolder holder);
public abstract boolean animateChange(RecyclerView.ViewHolder oldHolder,
RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
public abstract boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY,
int toX, int toY);
然而這幾個方法使用起來還是不好使用,所以系統又基於SimpleItemAnimator
擴展了DefaultItemAnimator
,幸好有這個類,否則實現自定義動畫幾乎不可能,我們看系統的這個DefaultItemAnimator
大致流程是什麼,以添加一個item爲例:
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0);
mPendingAdditions.add(holder);
return true;
}
animateAdd
方法被調用時,animation其實還沒開始,可以看做是add動畫進行之前的一個初始化過程,可以看出animateAdd
方法將view的alpha值設置成了0,這也是爲什麼默認添加一個item時,item是有一個逐漸變爲不透明的效果的。具體add的動畫是在animateAddImpl
中開啓的
animation.alpha(1).setDuration(getAddDuration()
截取關鍵代碼,上面有alpha(1),這句話就是要把view從當前狀態變成一個alpha爲1的動畫,這也證實了默認動畫的效果了。
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) {
// nothing to animate
return;
}
// First, remove stuff
//最先開啓的是remove動畫
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList<MoveInfo> moves = new ArrayList<>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
Runnable mover = new Runnable() {
@Override
public void run() {
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
moves.clear();
mMovesList.remove(moves);
}
};
//如果有remove動畫,那麼等remove動畫結束,再開始move動畫
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Next, change stuff, to run in parallel with move animations
if (changesPending) {
final ArrayList<ChangeInfo> changes = new ArrayList<>();
changes.addAll(mPendingChanges);
mChangesList.add(changes);
mPendingChanges.clear();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
mChangesList.remove(changes);
}
};
//如果有remove動畫,那麼等remove動畫結束,再開始change動畫
if (removalsPending) {
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
} else {
changer.run();
}
}
// Next, add stuff
if (additionsPending) {
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
@Override
public void run() {
for (RecyclerView.ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
//最後等所有動畫結束之後,再開始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動畫其次(可同時進行)、等前面所有動畫結束之後,再開始add動畫,爲什麼默認的animator要這樣寫,我估計是爲了刷新界面的時候不至於太亂,其實我們可以選擇性的使動畫同時進行,沒必要使move動畫非要在remove動畫完成之後再開始,在後面我們自定義animator時會講。
DefaultItemAnimator
的主要細節就講完了,現在結合recyclerview的layout過程來看看動畫和layout的順序,recyclerview有三個方法:
dispatchLayoutStep1
dispatchLayoutStep2
dispatchLayoutStep3
step1爲preLayout階段,在這一步裏面收集各個item的信息,調用ItemAnimator.recordPreLayoutInformation
對每一個item記錄信息,默認只記錄四條邊的位置信息,放在ItemHolderInfo
這個類裏面,然後Step2進行真正的layout過程,layout完成之後,Step3中對每一個item調用mItemAnimator.recordPostLayoutInformation
再次記錄信息,這樣動畫才能知道每個item的最終狀態。這時已經有了layout前後兩次viewholde的信息,此時就要對比每一個viewholder決定對viewholder是做add、remove還是其他動畫,調用mViewInfoStore.process(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) {
// 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);
}
};
最終就調用到上面DefaultItemAnimator
中的方法了,然後開啓動畫,關於Recyclerview ItemAnimator的整體流程就分析到這裏,下一篇結合相應的例子改進DefaultItemAnimator
這個類