RecyclerView的强大之处相信大家已经体验到了,在上一篇RecyclerView详解 —— 自定义分割线我们学习了如何定义分割线,本篇将介绍如何自定义动画。
Google为我们提供了一个默认的动画实现(DefaultItemAnimator),当数据添加、删除、更新时,会触发默认的动画效果:

通过本篇的学习,我们可以得到下面的效果:

如果觉得自定义比较繁琐,Github上也有相关的动画实现:https://github.com/wasabeef/recyclerview-animators

接下来我们将一步步分析这个实现类,最后在它的基础上修改默认的动画效果。

先来看DefaultItemAnimator中的几个重要的方法:

  1. void runPendingAnimations():当有动画需要执行时调用。
  2. boolean isRunning():返回当前是否有动画正在运行。
  3. boolean animateAdd():添加元素时调用,通常返回true。
  4. boolean animateRemove():移除数据时调用。
  5. boolean animateMove():列表项位置移动时调用。
  6. boolean animateChange():列表项数据发生改变时调用。
  7. void endAnimation():当某个动画需要被立即停止时调用,这里一般做视图的状态恢复。
  8. void endAnimations() 作用同上,区别是停止多个动画时调用。

认识了八个主要的方法,我们再来看看具体的动画是如何实现的,我们从添加元素动画开始(其他动画的运行流程基本类似),当有新的元素添加进来时,首先调用animateAdd(ViewHolder holder)

@Override
public boolean animateAdd(final ViewHolder holder) {resetAnimation(holder);    // 该方法最终会调用endAnimation()ViewCompat.setAlpha(holder.itemView, 0);mPendingAdditions.add(holder);return true;
}

开始动画之前需要取消之前正在播放的动画,同时将Item的状态设置为动画的终结状态:

@Override
public void endAnimation(ViewHolder item) {// 省略部分代码...// 遍历添加动画的队列,逐个移除队列,同时恢复Item的状态for (int i = mAdditionsList.size() - 1; i >= 0; i--) {ArrayList<ViewHolder> additions = mAdditionsList.get(i);if (additions.remove(item)) {ViewCompat.setAlpha(view, 1);    // 使用ViewCompat是为了向下兼容dispatchAddFinished(item);if (additions.isEmpty()) {mAdditionsList.remove(i);}}}}
}

由于这是一个淡出动画,所以在Item出现之前需要设置Alpha为0,然后将需要播放动画的对象传入动画播放队列,紧接着调用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) {return;}// 省略部分代码...if (additionsPending) {final ArrayList<ViewHolder> additions = new ArrayList<>();additions.addAll(mPendingAdditions);mAdditionsList.add(additions);mPendingAdditions.clear();// 在另一个线程中开启动画Runnable adder = new Runnable() {public void run() {// 遍历动画队列,依次执行动画for (ViewHolder holder : additions) {animateAddImpl(holder);    // 动画实现方法}additions.clear();mAdditionsList.remove(additions);}};// 在开始动画之前,需要判断当前是否有其他动画正在播放,如果有则等待其他动画播放完毕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();}}
}

我们接着看RunnableanimateAddImpl(holder)这个方法的实现:

private void animateAddImpl(final ViewHolder holder) {final View view = holder.itemView;final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);mAddAnimations.add(holder);animation.setDuration(getAddDuration()).alpha(1).setListener(new VpaListenerAdapter() {@Overridepublic void onAnimationStart(View view) {dispatchAddStarting(holder);    // 通知动画开始}/*** 如果是淡出的删除动画,需要在这里将Item的状态恢复正常* ViewCompat.setAlpha(view, 1)*/ @Overridepublic void onAnimationEnd(View view) {animation.setListener(null);dispatchAddFinished(holder);    // 通知动画结束mAddAnimations.remove(holder);    // 从动画队列中移除dispatchFinishedWhenDone();}}).start();
}/**
* 如果当前没有正在播放的动画,则通知所有动画播放完毕
*/
private void dispatchFinishedWhenDone() {if (!isRunning()) {dispatchAnimationsFinished();}
}

至此我们大致走了一遍添加元素动画的流程,回过头来看,如果说我们需要在这个基础添加一个从缩放动画只需要修改几个个地方即可:

/**
* 设置初始状态
*/
@Override
public boolean animateAdd(final ViewHolder holder) {resetAnimation(holder);    ViewCompat.setAlpha(holder.itemView, 0);// 设置动画播放之前的状态,当前设置大小为零,即不可见状态ViewCompat.setScaleX(holder.itemView, 0);ViewCompat.setScaleY(holder.itemView, 0);mPendingAdditions.add(holder);return true;
}
/**
* 设置终止状态
*/
private void animateAddImpl(final ViewHolder holder) {// 省略部分代码...        animation.setDuration(getAddDuration()).scaleX(1).scaleY(1) // 终止状态.alpha(1).setListener(new VpaListenerAdapter() {@Overridepublic void onAnimationStart(View view) {dispatchAddStarting(holder);}}         // 省略部分代码...
}

好了,我们看看运行的效果如何:

可以看到, 当第一个动画紧接着第一个动画播放时,第一个动画就停了,而且一直保持这个状态,我们再往下滑动看看:

因为Item的复用,导致下面的Item也出现同样的问题。
前面我们说过开始动画之前需要取消之前正在播放的动画,同时将Item的状态设置为动画的终结状态,我们增加缩放动画时设置初始状态和终止状态,并没有做其他处理,这里还需要再修改两个部分:

/**
* 在新的动画开始之前,将正在播放的动画移除,并将Item恢复至正常状态
*/
@Override
public void endAnimation(ViewHolder item) {// 省略部分代码...// 遍历添加动画的队列,逐个移除队列,同时恢复Item的状态for (int i = mAdditionsList.size() - 1; i >= 0; i--) {ArrayList<ViewHolder> additions = mAdditionsList.get(i);if (additions.remove(item)) {ViewCompat.setAlpha(view, 1);ViewCompat.setScaleX(view,1);ViewCompat.setScaleY(view,1);dispatchAddFinished(item);if (additions.isEmpty()) {mAdditionsList.remove(i);// 省略部分代码...}
}
/**
* 当动画被取消时,也需要恢复至正常状态
*/
private void animateAddImpl(final ViewHolder holder) {// 省略部分代码...        animation.setDuration(getAddDuration()).scaleX(1).scaleY(1) .alpha(1).setListener(new VpaListenerAdapter() {@Overridepublic void onAnimationCancel(View view) {ViewCompat.setAlpha(view, 1);// 恢复至原始大小ViewCompat.setScaleX(view, 1);ViewCompat.setScaleY(view, 1);}}         // 省略部分代码...
}

再次运行:

自定义删除、移动、更新动画的流程与上面大致相同,结合旋转,平移,缩放等可以实现丰富的动画效果。

源码下载

RecyclerView详解 —— 自定义动画相关推荐

  1. SVG 详解——自定义可点击的中国地图

    SVG 详解--自定义可点击的中国地图 SVG 定义 SVG 是一种图像文件格式,类似于 JPG.PNG.只不过 JPG 和 PNG 这种文件需要图像引擎加载,而 SVG 则是由画布来加载的. 它的英 ...

  2. 【转载】CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数)...

    CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数) 2017-08-19 胡恩伟 汽车电子expert成长之路 内容提要 ...

  3. ANDROID L——Material Design详解(动画篇)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...

  4. Linux systemctl 详解自定义 systemd unit

    Linux systemctl 详解&自定义 systemd unit systemctl 序 大家都知道,我们安装了很多服务之后,使用 systemctl 来管理这些服务,比如开启.重启.关 ...

  5. HTML+CSS教程(十)css3(3D属性详解及动画)

    一.3D 转换 1.左手坐标系 :伸出左手,让拇指和食指成"L"形,大拇指向右,食指向上,中指指向前方.这样我们就建立了一个左手坐标系,拇指,食指和中指分别代表X.Y.Z 轴的正方 ...

  6. 详解JQuery动画

    我这是我在逆战班学习js的第六周,今天让我来给大家解释一下jQuery框架中的jQuery动画的应用及方式 jQuery动画可以分为内置动画和自定义动画,下面让我们用代码来说明它的用法 一.jQuer ...

  7. Android RecyclerView详解

    介绍 RecyclerView用于在有限的窗口展现大量的数据,其实早已经有了类似的控件,如ListView.GridView,那么相比它们,RecyclerView有什么样优势呢? RecyclerV ...

  8. 详解Canvas动画部分

    基础篇: Html5中Canvas绘制.样式详解(不包含动画部分) 此篇为后续 目录 1. 状态的保存和恢复 2. translate移动 3. 旋转Rotating 4. 缩放Scaling​ 5. ...

  9. RecyclerView详解一,使用及缓存机制

    本文大致会先讲解RecyclerView的基础知识及使用,最后会深入讲解一点原理.当然,本人知识水平有限哈,太深入的东西我现在还没接触到,还请大家包容,阿里嘎多~ 一.RecyclerView的历史与 ...

最新文章

  1. 【周末阅读】5G时代新型基础设施建设白皮书
  2. Linux监控平台介绍、zabbix监控介绍、安装zabbix、忘记Admin密码如何做
  3. sql中小数位四舍五入控制
  4. 七牛云 直播 java_七牛云直播SDK之推流解析
  5. Html画布w3c,HTML canvas 标签
  6. java数组解析_Java - 数组解析
  7. mySQL用户和权限管理v1
  8. jquery is 用于查看选择的元素是否匹配选择器。
  9. 2012年度总结:内心宁静的2012
  10. 如何用LOTO示波器TDR方法测试电线长度?
  11. 黎曼Zeta函数,人类文明永恒的纪念
  12. 云呐|fsu动环监控单元是什么,fsu动环监控单元特点
  13. 2016公众号快速涨粉方法汇总—北京高端网站制作
  14. git重新设置用户名密码
  15. 最小二乘法和主成分分析的比较 matlab  儿子的papa
  16. 管理者如何抓共性问题进行组织优化运作
  17. 小米折叠屏MIX FOLD办公性能再强化,掌上PC模式正式上线
  18. 如何用开源飞控PIXHAWK进行二次开发?
  19. 我为什么选择Go语言(Golang)
  20. Keep It Mac版

热门文章

  1. PDF转HTML转换器哪个好用?快看看这里
  2. STM32硬件SPI驱动OLED
  3. 【草稿】DNS配置问题引起的java.net.UnknownHostException
  4. 整理一波国外前端学习网站
  5. 「SymPy」符号运算(3) (非)线性方程(组)求解、数列求和、连乘、求极限
  6. 【山河送书第五期】:《码上行动:利用Python与ChatGPT高效搞定Excel数据分析》参与活动,送书三本!!
  7. HTML5的画布:Stroke(笔触)和Fill(填充)
  8. crtmpserver系列(一):流媒体概述
  9. tensorflow.python.framework.errors_impl.NotFoundError
  10. 全球在用手机数量达40亿部覆盖60%人口