补间动画

  • 补间动画的作用对象是 View,它支持4种动画效果,分别是平移动画、缩放动画、旋转动画、透明度动画。补间动画的四种变化效果由Animation的四个子类:TranslateAnimation、SvaleAnimation、RotateAnimation、AlphaAnimation 来实现。这四种动画既可以通过XML来定义,也可以通过代码来创建。
  • 补间动画既可以是单独运行,也可以组合运行。当组合运行时,使用 AnimationSet 实现复杂的运动效果。
  • interpolator 动画集合所采用的插值器,影响动画的速度(斜率)。

自定义补间动画

  • 自定义补间动画。新动画只需要继承 Animation 这个抽象类,然后重写它的initialize()和applyThansformation()。在 initialize() 中做一些初始化工作,在 applyTransformation() 中进行相对的矩阵变化。很多时候需要采用Camera来简化矩阵变化的过程。

帧动画

  • 帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于补间动画,系统提供了一个另外一个类 AnimationDrawable 来使用帧动画。帧动画的使用比较容易引起OOM,所以在使用帧动画时尽量应避免使用大多尺寸较大的图片。

补间动画的特殊使用场景

例如:在ViewGroup中可以控制子元素的出场效果,在Activity中可以实现不同Activity的之间的切换效果。

  • 子view的出场效果: LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样它的子view出现时都会具有这种动画效果。同时, 它既可以使用 xml 的形式实现,也可以通过 LayoutAnimationContoller 来实现。
  • Activity的切换效果:通过overridePendingTransition(int enterAnim, int exitAnim),这个方法必须在*startActivity()或者finish()*之后被调用才能生效。
  • Fragment的切换动画:我们可以通过 FragmentTransaction 中的 *setCustomAniamtions()*来添加切换动画。

属性动画

概念在一段时间间隔内完成对象的某一个属性(位置,透明度等),由一个属性值向另一个属性值的改变。

属性动画要求对象必须为指定的属性提供 set()get(),否则就会失败,这里我们可以想一下为什么?

属性动画是 API11 新加入的特性(nineoldandroids开源库实现向下兼容)。我们知道,补间动画只能作用于 View,而属性动画可以为 任意对象 添加动画效果;另一方面,属性动画支持更多的效果,而不限于补间动画的四则变换:平移,旋转,缩放,透明度。

比较常用的API有 ValueAnimatorObjectAnimatorAnimatorSet。ObjectAnimator继承自ValueAnimation。AnimatorSet是属性动画的集合类。

  • 插值器和估值器

    • 时间插值器:TimeInterpolator,它的作用是:根据时间流逝的百分比以及所选的插值器来计算出当前属性值改变的百分比。类比到现实中,就是斜率。

      • 线性插值器:匀速 LinearInterpolator
      • 加速插值器:匀加速 AccelerateInterpolator
      • 减速插值器:匀减速 DecelerateInterpolator
      • 变速插值器:先加速,后减速 AccelerateDecelerateInterpolator
    • 类型估值器:TypeEvaluator, 根据当前属性值改变的百分比来计算改变后的属性值。
      • 针对整型属性 IntEvaluator
      • 针对浮点型属性 FloatEvaluator
      • 针对Color属性 ArgbEvaluator
  • 属性动画的监听器

    • 属性动画提供了监听器用于监听动画的播放过程,主要有两个API接口,ValueAnimator.AnimatorUpdateListenerAnimator.AnimatorListener
    • 我们知道动画是有许多帧组成的,如果想在每一帧播放后都得到监听,则需要使用ValueAnimator.AnimatorUpdateListener,每播放一帧,onAnimationUpdate就会调用一次。注意:一帧动画的时间是非常短的,大约10ms,如果我们在 onAnimationUpdate 中创建对象,就可能会引起内存抖动(短时间内创建和销毁大量的对象)。
  • 斜率

斜率表示一条直线(曲线的切线)与横坐标轴的夹角的正切值(y/x)。平行于X轴的直线的斜率为0,平行于y轴的直线的斜率不存在。已知两个点(x1,y1)和(x2,y2)的直线,若x1!=x2, 则直线的斜率为 k=(y2-y1)/(x2-x1).

曲线上某点的斜率反映了此曲线在此点处变化的快慢程度。

实例

属性动画默认时间间隔为 300ms,默认的帧率 10ms/帧

  1. 改变一个对象(mObject)的 translationY 属性,让其沿着Y轴向上平移一段距离
ObjectAnimator.ofFloat(mObject, "translationY", -mObject.getHeight()).start()
  1. 在3秒内改变View的背景色, 并将动画设置为无限循环且进行反转。
ValueAnimator valueAnim = ObjectAnimator.ofInt(mView, "backgroundColor", 0XFFFF8080, 0XFF8080FF),
valueAnim.setDuration(3000);
valueAnim.setEvaluator(new ArgbEvaluator());
valueAnim.setRepeatCount(ValueAnimator.INFINITE);
valueAnim.setRepeatMode(ValueAnimator.REVERSE);
valueAnim.start();
  1. 动画集合的使用
AnimatorSet set = new AnimatorSet();
set.playTogetheer(ObjectAnimator.ofFloat(mView, "rotationX", 0, 360),ObjectAnimator.ofFloat(mView, "rotationY", 0, 180),ObjectAnimator.ofFloat(mView, "rotation", 0, -90),ObjectAnimator.ofFloat(mView, "translationX", 0, 90),ObjectAnimator.ofFloat(mView, "translationY", 0, 90),ObjectAnimator.ofFloat(mView, "scaleX", 1, 1.5F),ObjectAnimator.ofFloat(mView, "scaleY", 1, 0.5F),ObjectAnimator.ofFloat(mView, "alpha", 1, 0.25F, 1)
);
set.setDuration(5000).start();

AnimatorSet 与 AnimationSet

  • AnimationSet

默认支持一组Animation 同时播放。如果要实现动画的顺序播放,需要计算每个动画的延迟时间 startOffset。

// 继承至 Animation ,说明可以使用 view.startAnimation 启动一组动画
public class AnimationSet extends Animation {// 使用数组存储需要播放的 animation// 数组支持随机访问,在查找和更新方面的时间复杂度为O(1),而在删除和添加方面的时间复杂度为O(n)(由于要维护数组的顺序),而这里经常使用到的是查找,所以使用数组private ArrayList<Animation> mAnimations = new ArrayList();private long[] mStoredOffsets;// duration + startOffsetprivate long mLastEnd;// shareInterpolator一般为false,即每个 animation 使用自己的 插值器public AnimationSet(boolean shareInterpolator){setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);init()}// 设置重复模式, 与 repeatCount 联合使用。会覆盖每个 animation 的设置。public void setRepeatMode(int repeatMode){mFlags |= PROPERTY_REPEAT_MODE_MASK;super.setRepeatMode(repeatMode);}
}
  • AnimatorSet

按执行顺序排列想要执行的动画,并支持同时与顺序播放该组动画。

// AnimtorSet 本身是一个 动画,可以接收来自硬件发出的VSYNC信号,及时作出刷新
public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback{// Node 为一组动画之间添加依赖关系private static class Node implements Cloneable {Animator mAnimation; // 当前节点播放的动画ArrayList<Node> mChildNodes = null; // 当前节点动画播放结束后的动画节点ArrayList<Node> mSiblings; // 与当前节点动画同时播放的动画节点ArrayList<Node> mParents; // 当前节点动画播放之前的动画节点}// Builder 生成一组按添加顺序排序的动画节点public class Buidler {private Node mCurrentNode;Buidler(Animator anim) {mCurrentNode = getNodeForAnimation(anim);}// 添加与当前节点动画同时播放的动画public Buidler with(Animator anim) {Node node = getNodeForAnimation(anim);mCurrentNode.addSibling(node);return this;}// 添加在当前节点播放完成后再播放的动画。即:A.befor(B)表示 A在B之前播放public Builder before(Animator anim) {Node node = getNodeForAnimation(anim);mCurrentNode.addChild(node);return this;}// 添加在当前节点播放之前播放的动画public Buidler after(Animator anim) {Node node = getNodeForAnimation(anim);mCurrentNode.addParent(node);return this;}}private Node getNodeForAnimation(Animator anim) {Node node = mNodeMap.get(anim);if (node == null) {node = new Node(anim);mNodeMap.put(anim, node); // 添加动画与节点的映射关系mNodes.add(node); // 添加动画节点}return node;}// 记录正在播放的动画private ArrayList<Node> mPlayingSet = new ArrayList<Node>();// 记录所有动画节点的开始和结束事件,且所有事件均有序private ArrayList<AnimationEvent> mEvents = new ArrayList<>();// 记录所有的动画节点private ArrayList<Node> mNodes = new ArrayList<Node>();// 同时播放 itemspublic void playTogether(Animator... items) {if (items != null) {Builder builder = play(items[0]); // 生成Builder对象for (int i = 1; i < items.length; ++i) {builder.with(items[i]);}}}// 顺序播放 itemspublic void playSequentially(Animator... items) {if (items != null) {if (items.length == 1) {play(items[0]);} else {for (int i = 0; i < items.length - 1; ++i) {play(items[i]).before(items[i + 1]);}}}}// 开始播放动画private void start(boolean inReverse, boolean selfPulse) {// 动画必须在有Looper 的线程执行if(Looper.myLooper == null){throw new AndroidRuntimeException("Animators may only be run on Looper threads");}mStarted = true;// 1. 所有动画都不能异步执行int size = mNodes.size();for (int i = 0; i < size; i++) {Node node = mNodes.get(i);node.mEnded = false;node.mAnimation.setAllowRunningAsynchronously(false);}// 2. 初始化 1. 使用插值器(interpolator);2. 设置运行时长(during);3. 创建动画节点的有向图initAnimation();boolean isEmptySet = isEmptySet(this);// 3. 开始动画if(!isEmptySet) startAnimation(); if (mListeners != null) {ArrayList<AnimatorListener> tmpListeners =(ArrayList<AnimatorListener>) mListeners.clone();int numListeners = tmpListeners.size();for (int i = 0; i < numListeners; ++i) {tmpListeners.get(i).onAnimationStart(this, inReverse); // 回调开始监听}}if(isEmptySet) end(); // 处理异常 }private void startAnimation() {addAnimationCallback(0); // 添加 Choreographer 处理动画的回调if (mShouldResetValuesAtStart) { // Android O 之前需要在开始动画前重置所有数据***}// 开始执行动画流程handleAnimationEvents()}private void handleAnimationEvents(int startId, int latestId, long playTime) {// 省略了部分代码for (int i = startId + 1; i <= latestId; i++) {AnimationEvent event = mEvents.get(i);Node node = event.mNode;if (event.mEvent == AnimationEvent.ANIMATION_START) { // 处理开始动画事件mPlayingSet.add(event.mNode); // 向动画集合添加正在执行的动画if (node.mAnimation.isStarted()) {// 如果动画正在执行,则cancel掉node.mAnimation.cancel();}node.mEnded = false;node.mAnimation.startWithoutPulsing(false); // 递归执行 start(), pulseFrame(node, 0); // 开始心跳,心脏是 AnimatorSet, 血液是 Animators。 首先执行的是 AnimationSet 的 doAnimationFrame} else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) { // 处理动画结束事件pulseFrame(node, getPlayTimeForNode(playTime, node)); // 当所有动画都执行完,duration超时后自动结束}}}// 接收VSYNC信号,执行动画@Overridepublic boolean doAnimationFrame(long frameTime) {float durationScale = ValueAnimator.getDurationScale();if (durationScale == 0f) {forceToEnd();return true;}// 处理暂停与恢复if (mPaused) {mPauseTime = frameTime; // 记录在第几帧停止的removeAnimationCallback();return false;} else if (mPauseTime > 0) {mFirstFrame += (frameTime - mPauseTime); // 从暂停处恢复播放mPauseTime = -1; // 重置暂停帧}.....long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);mLastFrameTime = frameTime;int latestId = findLatestEventIdForTime(unscaledPlayTime);int startId = mLastEventId;// 执行 animator 的 start 方法handleAnimationEvents(startId, latestId, unscaledPlayTime);mLastEventId = latestId;// 计算当前帧是否需要停止for (int i = 0; i < mPlayingSet.size(); i++) {Node node = mPlayingSet.get(i);if (!node.mEnded) {pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));}}// 从 plyingSet 集合中移除所有结束的动画节点for (int i = mPlayingSet.size() - 1; i >= 0; i--) {if (mPlayingSet.get(i).mEnded) {mPlayingSet.remove(i);}}boolean finished = false;if (mReversing) {if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {// The only animation that is running is the delay animation.finished = true;} else if (mPlayingSet.isEmpty() && mLastEventId < 3) {// The only remaining animation is the delay animationfinished = true;}} else {// 如果播放列表为空且事件执行到最后一个,则需要停止所有动画finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;}if (finished) {endAnimation(); // 动画结束了return true;}return false;}private void endAnimation() {mStarted = false;mLastFrameTime = -1;mFirstFrame = -1;mLastEventId = -1;mPaused = false;mPauseTime = -1;mSeekState.reset();mPlayingSet.clear();// 移除监听硬件 VSYNC 信号的回调removeAnimationCallback();// Call end listenerif (mListeners != null) {ArrayList<AnimatorListener> tmpListeners =(ArrayList<AnimatorListener>) mListeners.clone();int numListeners = tmpListeners.size();for (int i = 0; i < numListeners; ++i) {tmpListeners.get(i).onAnimationEnd(this, mReversing); // 回调动画结束监听}}mSelfPulse = true;mReversing = false;}private void removeAnimationCallback() {if (!mSelfPulse) {return;}AnimationHandler handler = AnimationHandler.getInstance();handler.removeCallback(this);}private void addAnimationCallback(long delay) {if (!mSelfPulse) {return;}AnimationHandler handler = AnimationHandler.getInstance();handler.addAnimationFrameCallback(this, delay);}
}

总结:通过简单分析 AnimatorSet 的源码,可以得到以下几点:

  1. 属性动画必须在具有 Looper 的线程中执行,否则会抛出异常
  2. 属性动画集合通过 Node 和 Builder 管理动画的执行顺序(也就是根据有向图的顺序执行动画)
  3. 动画的执行是在接收到硬件层的 VSYNC 信号后,才开始执行下一帧动画的。另外,不只是动画,view刷新,事件输入等,也是通过监听硬件发出的 VSYNC 信号,在回调中处理逻辑的。
  4. 如果我们为属性动画添加了 updateListener ,则一定要小心出现内存抖动。

Android Animation 分析与总结相关推荐

  1. Android - Animation(二)

    Android - Animation(一) 一文总结了Android中的补间动画(View Animation/Tween Animation)和帧动画(Drawable Animation/Fra ...

  2. Android Animation动画详解(二): 组合动画特效

    前言 上一篇博客Android Animation动画详解(一): 补间动画 我已经为大家介绍了Android补间动画的四种形式,相信读过该博客的兄弟们一起都了解了.如果你还不了解,那点链接过去研读一 ...

  3. Android Animation (安卓动画)概念简介

    Android Animation Android 四种动画分别为逐帧动画和补间动画.属性动画.过渡动画: Frame Animation (逐帧动画) 实现方式:xml 和 Java代码 图片跳转的 ...

  4. android逆向分析概述_Android存储概述

    android逆向分析概述 Storage is this thing we are all aware of, but always take for granted. Not long ago, ...

  5. Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition

    Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition Property animation系统还提供了对ViewGroup中的View改变 ...

  6. android - Animation详解

    Drawable 最强大的功能是:显示Animation.AndroidSDK介绍了2种Animation: Tween Animation(渐变动画):通过对场景里的对象不断做图像变换(平移.缩放. ...

  7. Android JNI入门第五篇——Android.mk分析

    转载请标明出处: http://blog.csdn.net/michael1112/article/details/56671708 江东橘子的博客 Android.mk文件是在使用NDK编译C代码时 ...

  8. android封装多肽,深度探索C++对象模型之(四)...-Android.animation cts fail-Rails helper_169IT.COM...

    这两天查cts 的fail, android.animation中有3个fail. 分别为testCurrentPlayTime,testCancel,testSetCurrentPlayTime,在 ...

  9. Android多线程分析之二:Thread的实现

    Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一:使 ...

最新文章

  1. python实现字符串切片
  2. 第2题——DNA片段
  3. 剑指offer之先序非递归打印二叉树
  4. 少儿编程150讲轻松学Scratch(十二)-用Scratch制作石头剪子布游戏
  5. 菜鸟裹裹宣布:让数十万快递小哥月入过万成为常态
  6. SQLi LABS Less 27a 联合注入+布尔盲注+时间盲注
  7. Scala Package Package Objects
  8. 一个Windows C++的线程池类实现
  9. 《自己动手写操作系统》(一)
  10. 算法笔记练习 题解合集
  11. SubSonic 安装与使用
  12. IT杂谈(一):炫酷好玩网站汇总
  13. 2022/3/27 Java开发之Java web编程 第十一章 Ajax交互扩展
  14. 加州房价预测项目详细笔记(Regression)——(3)准备数据(数据的预处理)
  15. DTAS棣拓公差分析软件-公差仿真模拟软件-几何尺寸与公差软件-三维公差分析软件
  16. vue + 生成 下载 成 二维码
  17. Javascript实现秒杀倒计时(时间与服务器时间同步)
  18. Midjourney|文心一格prompt教程[基础篇]:注册使用教程、风格设置、参数介绍、隐私模式等
  19. 【opencv 4.5.1 samples】 3calibration.cpp -- 一起校准水平线上的 3 个摄像头
  20. 给计算机老师致歉信,关于给老师的道歉信四篇

热门文章

  1. 基于四网融合的上海新城对外客运枢纽构建路径
  2. 达梦8上安装ODBC
  3. GPRSsim800c
  4. ucenter base.php,UCenter之应用通信分析(一)
  5. oracle 五笔码函数,如何根据单元格汉字自动生成拼音码和五笔码
  6. java short相加_关于java:short加short是一个int
  7. mongodb安装、认证、添加用户
  8. PLC维修-禾川HCA8-32X32YT
  9. 计算机毕业设计Javavue架构云餐厅美食订餐系统(源码+系统+mysql数据库+lw文档)
  10. 大数据 就业 缺口_三年培养10万大数据人才,解决大数据人才缺口