Honeycomb 引入的新特性之一是全新的动画系统 android.animation,它比以前更容易实现对象动画(objects animation)和属性动画(properties animation)。 Honeycomb之前的动画 Honeycomb 之前,动画由 android.view.animation 实现,比如 视图的移动 move、缩放 scale、旋转 rotate、渐变 fade; 通过 AnimationSet 组合安排多个动画; 把动画指定给 LayoutAnimationController,当容器排列子视图时,会自动错开所有子视图动画的开始时间(文末有注释); 使用 Interpolator(插值器,用来定义动画改变的速率,文末有注释),比如 AccelerateInterpolator 和 BounceInterpolator,使动画不再匀速改变,从而显得更自然。 如上述,honeycomb 之前可以实现视图动画,但也仅止于此,因为 honeycomb 之前动画只能操作视图对象(View Objects),缺乏一些关键功能的支持,对诸如 Drawable 的位置、背景色等视图属性无能为力。 之前的动画仅仅改变目标视图的视觉效果(看起来的样子),而不会改变视图对象的属性。比如通过动画 TranslateAnimation 和 setFillAfter(true) 改变按钮的位置,动画仅仅在新的位置重绘按钮,而无法改变按钮在父视图中的位置。也就是说在新位置点击按钮无效。 基于上述(还有其它没有提到的)原因,Honeycomb 提供了全新的动画机制,新机制建立于属性动画(property animation)的概念之上。 Honeycomb中的属性动画 新动画机制不仅仅针对视图对象,也不仅仅针对对象的某些属性,也不局限于视觉效果。事实上,它所有的一切都是关于一段时间后值的变化,并把这些变化的值设置给任何目标对象和属性。 因此你可以完成很多事情,诸如:移动或者渐变视图;移动视图内的 Drawable;动态地改变 Drawable 的背景色;甚至动态地改变任何数据类型的值,只需要告诉新机制:动画时长、自定义类型的动画过程值的计算方法、动画的起止值,新机制就可以计算动画过程值并设置给目标对象和属性。 所以,通过新机制移动按钮是真的移动了按钮的位置。 接下来我会简单地介绍新机制中的几个关键类,适当时给出示例代码。想了解新机制工作的更多细节,就去研读 SDK sample ApiDemos.(译注:参考我这篇文章 从 Android Sample ApiDemos 中学习 android.animation API 的用法) Animator Animator 是新动画机制中的超类。子类 ValueAnimator 是核心计时引擎,子类 AnimatorSet 用来把多个动画编排成一个动画。一般不会直接使用 Animator,但它的一些属性和方法为子类所共有,诸如持续时间duration、开始延迟startDelay、监听器listener. 当你想在动画结束时执行一些操作,监听器就显得很重要了。为了监听动画的生命周期事件,需要实现接口 AnimatorListener 并注册给 animator。比如, anim.addListener(new Animator.AnimatorListener() { public void onAnimationStart(Animator animation) {} public void onAnimationEnd(Animator animation) { // do something when the animation is done } public void onAnimationCancel(Animator animation) {} public void onAnimationRepeat(Animator animation) {} });登录后复制 当然,考虑到你可能只需要监听某一个事件,意味着接口的其它方法不需要覆写,却占着空间可能会让「代码简洁控」抓狂,所以新机制提供了适配器类 AnimatorListenerAdapter,让你只需覆写关心的方法: anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { // do something when the animation is done } });登录后复制 ValueAnimator ValueAnimator 是整个新机制的「主力」。它运行的内部计时循环(timing loop 文末有注释),使程序内的所有动画,在每次计时脉冲(timing pulse 文末有注释)发生时,根据当前的时间计算动画过程值,并设置给目标对象和属性。 它拥有的一些核心特性可以帮助它做到这一点: 知道每一个动画的计时详情(诸如起/止时间、持续时间、当前执行了多少时间); 知道动画是否重复; 当前动画过程值算出来后,回调注册给它的监听器(AnimatorUpdateListener); 拥有计算不同类型值的能力(TypeEvaluator)。 属性动画包含两个步骤:计算动画过程值,然后把这些值设置给目标对象和属性。ObjectAnimator(稍后讲) 直接继承自 ValueAnimator,由它实现属性动画较为容易。但以下几种情况使用 ValueAnimator 反而更方便: 当对象没有提供某个属性的 setter 方法时;(译注:有疑惑不要紧,稍后讲到 ObjectAnimator 时就知道原因了。) 当你仅有一个动画,并想把动画过程值设置给多个属性时; 或者仅仅是想使用一个简单的计时机制; 怎么使用 ValueAnimator 呢?比如,在 500ms 内从 0 变到 1: ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(500); anim.start();登录后复制 如上述,新的动画机制不局限于视觉效果,而所有的一切都是关于一段时间后值的变化。那么通过什么方式才能知道动画是否发生了呢?——答案是监听器:实现一个监听器 AnimatorUpdateListener 并注册给 ValueAnimator 实例,就可以在每一个动画帧(animation frame 文末有注释)被回调到,从而获取当前的动画值: anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { Float value = (Float) animation.getAnimatedValue(); // do something with value... } });登录后复制 除了浮点数,还可以给其它类型的值添加动画,比如整数: ValueAnimator anim = ValueAnimator.ofInt(0, 100);登录后复制 XML 文件也可以实现相同的动画: 登录后复制 新机制知道如何计算整数和浮点数的动画过程值,这是因为其内置了相关的计算方法。而对于其它类型的数据,比如 Point, Rect, 甚至自定义数据,新机制不知如何计算,但通过 TypeEvaluator(稍后讲),转手就把计算动画过程值的责任交给了你: Point p0 = new Point(0, 0); Point p1 = new Point(100, 200); ValueAnimator anim = ValueAnimator.ofObject(pointEvaluator, p0, p1);登录后复制 除了动画时长外,还可以设置的动画参数有: setStartDelay(long):播放延迟的时间; setRepeatCount(int):播放重复的次数,0(默认值)不重复,正整数或者 Animation.INFINITE; setRepeatMode(int):播放重复的模式,Animation.REVERSE 从结束位置重复,Animation.RESTART 从开始位置重复; setInterpolator(TimeInterpolator):设置插值器,用来定义动画改变的速率。TimeInterpolator 是 Interpolator 的父接口,因此这里也可以使用 Interpolator 相关的实现。 ObjectAnimator ObjectAnimator 继承自 ValueAnimator。除了可以设置动画的时间参数和值参数外(这些 ValueAnimator 能做到的),它还可以设置目标对象和属性。比如,如果想渐隐某个对象 myObject,可以对属性 alpha 施加动画: ObjectAnimator.ofFloat(myObject, "alpha", 0f).start();登录后复制 这个例子中,虽然只设置了动画的终点值,但起始值默认是属性的当前值。 XML 文件也可以定义相同的动画: 登录后复制 但不能在 xml 中设置目标对象,只能在加载动画资源后,由代码设置: ObjectAnimator anim = AnimatorInflator.loadAnimator(context, resID); anim.setTarget(myObject); anim.start();登录后复制 在使用 ObjectAnimator 之前,必须要理解一个隐含的假定,关乎属性及其 set/get 方法。这个假定是说:当对某个对象的某个属性施加动画时,动画机制假定这个对象具有和该属性名相关的 public set 方法,并且属性具有合适的数据类型。还有,在构造 ObjectAnimator 时如果只设置了终点值,比如上述例子,动画机制必须要知道属性的当前值,因此,该对象也应该具有相应的 public get 方法以返回合适类型的数据。 因此,上述关于 alpha 的示例代码若想执行成功,对象 myObject 必须要有两个 public 方法: public void setAlpha(float value); public float getAlpha();登录后复制 简言之,某个对象若想为它的某个属性使用 ObjectAnimator,该对象必须满足动画机制「没有明说」的一个条件:提供该属性的 public set/get 方法。 特别说明:set/get 方法在 animation 运行时调用,因此它们和动画之间是非常弱的关系。而如果你的程序中又没有任何地方显示地调用这些 set/get 方法,而你恰好又使用了 ProGuard(代码混淆工具)或者其它代码优化工具(code stripping 代码剥离),那么这些工具便不会知道 set/get 在运行时被调用,很有可能就被剥离掉了。所以当你使用这些工具时,你有义务确保这些 set/get 方法的存在。 View properties 敏锐的读者可能已经发现了新动画机制的「瑕疵」:围绕属性做文章的新机制,如何处理那些连一个 public set/get 属性方法都没有的视图对象呢?好问题!继续读下去。 类 View(查阅文档中 Animation 相关的说明) 在 Honeycomb 中得到了升级,增添了很多新属性,可以通过 set/get 方法访问这些属性,使得新动画机制可以应用于视图对象: translationX 和 translationY rotation, rotationX, 和 rotationY scaleX 和 scaleY pivotX 和 pivotY x 和 y alpha AnimatorSet AnimatorSet 用来把多个动画编排成一个动画(作用类似于3.0以前的 AnimationSet)。比如你想编排一个这样的动画:先渐隐一个视图,结束后从侧边滑入另一个视图,滑入的同时渐显出来。为了实现这样的编排,首先需要拆分成几个独立的动画,接下来就有好几种选择了:(1) 在正确的时间手动播放对应的动画,或者给每个动画设置合适的播放延迟,总之你需要显示地、主动地设置合适的时间;(2) 如果觉得时间不好掌控,或者嫌麻烦,就使用 AnimatorSet,它提供的 APIs 使这些变得很简单: 同时播放:playTogether(Animator...); 顺序播放:playSequentially(Animator...); 通过 AnimatorSet.Builder 设置动画间的相对关系:with(), before(), after(); play(Animator) 会创建一个 Builder; 因此上述动画可以这样实现: ObjectAnimator fadeOut = ObjectAnimator.ofFloat(v1, "alpha", 0f); ObjectAnimator mover = ObjectAnimator.ofFloat(v2, "translationX", -500f, 0f); ObjectAnimator fadeIn = ObjectAnimator.ofFloat(v2, "alpha", 0f, 1f); AnimatorSet animSet = new AnimatorSet().play(mover).with(fadeIn).after(fadeOut);; animSet.start();登录后复制 也可以在 XML 文件中实现上述动画。 TypeEvaluator 上述 ValueAnimator 一节有说过一段话:「新机制知道如何计算整数和浮点数的动画过程值,这是因为其内置了相关的计算方法。而对于其它类型的数据,比如 Point, Rect, 甚至自定义数据,新机制不知如何计算,但通过 TypeEvaluator,转手就把计算动画过程值的责任交给了你。」 而 TypeEvaluator 是只定义了一个方法的接口: public interface TypeEvaluator { public T evaluate(float fraction, T startValue, T endValue);}登录后复制 内置的处理浮点数的 FloatEvaluator 继承了该接口,而它的方法仅仅是 y = kx + b 的实现,非常简单: public class FloatEvaluator implements TypeEvaluator { public Object evaluate(float fraction, Object startValue, Object endValue) { float startFloat = ((Number) startValue).floatValue(); return startFloat + fraction * (((Number) endValue).floatValue() - startFloat); } }登录后复制 那么这个方法是如何被调用到的呢? // ValueAnimator.class void animateValue(float fraction) { // 篡改播放进度 turns the elapsed fraction into an interpolated fraction. fraction = mInterpolator.getInterpolation(fraction); for (int i = 0; i 登录后复制 如果不是浮点数和整数,或者内置的处理整数的 IntEvaluator 和处理浮点数的 FloatEvaluator 不能满足你的要求,则必须显示地设置 TypeEvaluator,可以调用 setEvaluator(TypeEvaluator) 或者通过构造器 ValueAnimator.ofObject(TypeEvaluator, Object...),比如计算 Point 的动画过程值: public class PointEvaluator implements TypeEvaluator { public Object evaluate(float fraction, Object startValue, Object endValue) { Point startPoint = (Point) startValue; Point endPoint = (Point) endValue; return new Point(startPoint.x + fraction * (endPoint.x - startPoint.x), startPoint.y + fraction * (endPoint.y - startPoint.y)); } }登录后复制 现在使用它把在 ValueAnimator 这节内容中提到的例子补充完整: Point p0 = new Point(0, 0); Point p1 = new Point(100, 200); ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), p0, p1);登录后复制 译注:这篇文章发布于 2011.02,针对于 API level 11 的 3.0 系统,此后 L18 加入了 RectEvaluator, L21 加入了 PointFEvaluator、IntArrayEvaluator、FloatArrayEvaluator。所以上述几次提到的「不知道如何计算 Rect 的动画过程值」也并不是错误的,要特别注意技术文章的时效,查阅最新的文档和代码,以相互佐证。查阅 TypeEvaluator Known Indirect Subclasses。 BUT WAIT, THERE'S MORE! 还有非常多的新特性可以说,但是限于文章篇幅和时间,这里就不再继续下去了。现在你应该和 ApiDemos「耍一耍」,潜心研究代码。 动画重复的一些特性; 动画生命周期事件的监听器; 在两个值以上做动画; 使用 Keyframe 定制更复杂的时值序列(time/value); 使用 PropertyValuesHolder 指定多个属性并行动画; 使用 LayoutTransition 定制简单的布局动画; ...... 译注:从代码角度理解一些特定的英文词组 关于 animation system 动画机制,文中大多简称「新机制」,是指 3.0 引入的 android.animation。感觉此处译作「动画系统」不太合适,因为简称做系统时,很容易和 Android 系统混淆。所以「新机制」在本文的语境下特指 android.animation. 关于 timing loop 计时循环。 android.animation.ValueAnimator 定义了一个静态内部类 AnimationHandler,它实现了 Runnable 以维护计时循环,从而产生计时脉冲(timing pulse),动画之所以能「动」,就是计时脉冲在起作用,可认为是动画的「心脏」,功能类似于单片机的晶振。每次计时脉冲,每一个动画都会根据动画的当前时间、Interpolator 和 TypeEvaluator,计算出动画的过程值。更多内容,查阅 http://li2.me/2016/01/android-animation-interpolator.html 关于 animation frame animation frame,at each frame,on each frame动画帧(这里肯定不能把 frame 当做框架来理解)。 上述讲到 timing loop 时提到了计时脉冲,每次计时脉冲都会计算出一个新的动画值。有了新值,就意味着动画发生了变化,所以,可以理解为一帧一帧的动画。 关于 animated values 动画过程值;动画中间值。 属性动画的本质是「值的变化」, 通过构造器(或者 xml)指定动画的起止值; Interpolator 表征了「时间」的变化规律; TypeEvaluator 表征了「值」的变化规律;(「值」即对象的物质属性,或者是透明度,或者是背景色,或者是运动轨迹) 那么在整个动画的生命周期中,每个动画帧都会计算出一个值,这些值就是「animated values」,因此对象的「时」「空」就发生了变化,因此就有了动画。 Interpolator 和 TypeEvaluator 的区别和联系 Interpolator 插值器:把处于某个区间(有起点值和终点值)的一个值,按照某种算法,映射为另一个值。 从上述定义的角度来看,Interpolator 和 TypeEvaluator 的作用是一样的。非要说区别的话,应该是新的动画机制对它俩的「职责」做了定性,从名字也能看出来端倪:一个管 time value(Interpolator 实现了 TimeInterpolator的接口),一个管 type value。 具体到各自的方法:Interpolator 的 float getInterpolation(float fraction) 虽然只有一个入口参数,是因为它的调用者已经把 @input 限定为 [0, 1.0]。而这个方法的作用是把动画的当前进度映射成另一个值,可谓是「篡改」,因此函数的名字「interpolation 篡改」起的非常合适。而 TypeEvaluator 的 T evaluate(float fraction, T startValue, T endValue) 则是根据动画的初值、终值,和被篡改的播放进度,计算动画过程值,因此函数的名字「evaluate 评估」也是非常合适。 因此,从数学意义讲,二者有共性;从物理意义讲,二者有区别。更多内容,查阅 http://li2.me/2016/01/android-animation-interpolator.html 关于 staggered animation automatically staggered animation start times:自动错开动画的开始时间。 staggered adj. 错列的;吃惊的。 LayoutAnimationController 用于实现容器布局动画,容器内的子视图动画相同,但开始时间不同。所以这里的 staggered 应是「错开的」意思。 其它一些单词和词组 usher in 领进,引进。 choreograph 设计舞蹈动作;为...编舞。 choreograph multiple animations 为动画编舞;把多个动画优雅地编排成一个。(译注:真要好好学习动画,不辜负类名 Choreograph)。 play with the API demos 和...玩耍。可以 play with 什么东西,小朋友之间也可以 play with,但是成人之间不要 play with 噢 (~﹃~)~zZ。这里有一个歪果仁特意提到了: 中国人总是用错“play”这个单词,哈哈哈,我来解释一下。 文中提到的一个哲学问题 If a tree falls in a forest 相关博文 Android属性动画完全解析(上),初识属性动画的基本用法 -- 郭霖的CSDN专栏 Android属性动画完全解析(中),初识属性动画的基本用法 -- 郭霖的CSDN专栏 Honeycomb 中引入的新 Animation —— Property Animation (这是全文翻译) byweiyi.li li2.me [email protected] ~ 2016-02-01禁止转载 不把阅读英文文档当做翻译事业的程序员不是好厨子 09-14 17:32