俗话说,好看的皮囊千篇一律,有趣的灵魂万里挑一。但是对于我们这些俗人来说,肯定是选择好看的皮囊,咱们的用户也是如此。你看看应用市场上那些花枝招展的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为例,动画如下。
gif-Remove动画.gif我们发现动画有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动画为例,来逐步分析下方动画执行时保存了哪些信息,又是怎么判断动画类型的。
gif-例子.gif上面提到,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();
}
真的超简单啊有没有!!!来看下效果。
gif-自定义.gifemmmm...效果不怎么好看,不过只要能设计出优美的动画,自定义ItemAnimator是一件很简单的事情。
网友评论