本文继上篇 ItemDecoration 以后,是深刻理解 RecyclerView 系列的第二篇,关注于 ItemAnimator,主要是分析 RecyclerView Animators 这个库的原理,而后总结如何本身编写自定义的 ItemAnimator。本文涉及到的完整代码能够在 Github 获取。java
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 就是这样处理的。git
例如 animateRemove 的实现以下:github
@Override public boolean animateRemove(final ViewHolder holder) { endAnimation(holder); preAnimateRemove(holder); mPendingRemovals.add(holder); return true; }
那么下面重点看看 runPendingAnimations。app
@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(); } } // Next, change stuff, to run in parallel with move animations if (