本文继上篇 ItemDecoration 之后,是深入理解 RecyclerView 系列的第二篇,关注于 ItemAnimator,主要是分析 RecyclerView Animators 这个库的原理,然后总结如何自己编写自定义的 ItemAnimator。本文涉及到的完整代码可以在 Github 获取。
DefaultItemAnimator
extends SimpleItemAnimator
extends RecyclerView.ItemAnimator
FadeInAnimator
extends BaseItemAnimator
extends SimpleItemAnimator
extends RecyclerView.ItemAnimator
RecyclerView.ItemAnimator
定义了一系列 API 用于开发 item view 的动效
animateDisappearance
, animateAppearance
, animatePersistence
, animateChange
这4个 API 用来对 item view 进行动画显示recordPreLayoutInformation
, recordPostLayoutInformation
这2个 API 用来记录 item view 在 layout 前后的状态信息,这些信息封装在 ItemHolderInfo
或者其子类中,并将传递给上述4个动画API中,以便进行动画展示runPendingAnimations
可以用来延迟动画到下一帧,此时就需要在上述4个 API 的实现中返回 true
,并且自行记录延迟的动画信息,以便在下一帧时执行dispatchAnimationStarted
和 dispatchAnimationFinished
是用来进行状态同步和事件通知的,子类必须在动画开始时调用 dispatchAnimationStarted,结束时调用 dispatchAnimationFinished,当然如果不展示动画,那就只需要直接调用 dispatchAnimationFinishedSimpleItemAnimator
则对 RecyclerView.ItemAnimator
的 API 进行了一次封装
animateRemove
, animateAdd
, animateMove
, animateChange
这4个,为什么这样?这一次封装就把对 preLayoutInfo 和 postLayoutInfo 的处理的公共代码封装了起来,把 ItemHolderInfo 转换为了 left, top, x, y 这样的位置信息,这样,大部分动画只需要根据位置变化信息的实现,专注实现自己的动画逻辑即可,一方面复用了代码,另一方面也更好的践行了单一职责原则RecyclerView.ItemAnimator
的相应动画 API 了dispatch***
和 on***
API,用于进行事件回调DefaultItemAnimator
是 RecyclerView 包中的一个默认实现,而 BaseItemAnimator
则是 RecyclerView Animators 库中 animator 的基类,它们都继承自 SimpleItemAnimator
,两者具有很大相似性,只分析后者BaseItemAnimator 实现了父类的 animateRemove
, animateAdd
, animateMove
, animateChange
这4个 API,而实现方式都是把参数包装一下,放入相应的 animation 列表中,并返回 true,然后在 runPendingAnimations 函数中集中显示动画。为什么要这样呢?因为 recycler view 的变化是随时都可能发生的,而这样的处理就可以把动画的显示按帧对其,即两帧之间的变化,都在下一帧开始时一起处理。但是这样做有什么优势呢?暂时不得而知,DefaultItemAnimator 就是这样处理的。
例如 animateRemove 的实现如下:
@Override
public boolean animateRemove(final ViewHolder holder) { endAnimation(holder); preAnimateRemove(holder); mPendingRemovals.add(holder); return true; }
那么下面重点看看 runPendingAnimations。
@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 for (ViewHolder holder : mPendingRemovals) { doAnimateRemove(holder); } mPendingRemovals.clear(); // Next, move stuff if (movesPending) { final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>(); 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); } }; if (removalsPending) { View view = moves.get(0).holder.itemView; ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); } else { mover.run(); }