原文 2011-02-24 animation in honeycomb 发表于 2011.02(和3.0发布的时间同步),作者是 chet haase,一个致力于图形和动画研究的 android 开发者,可以从他的 个人博客graphics-geek.blogspot.com 阅读更多相关主题的博文。
译者说:
这篇文章讨论了一个问题,已经有了能实现 move, scale, rotate, and fade 这些视图动画的 android.view.animation,为什么还要在 3.0 引入新 apis android.animation? 新 apis 带来了哪些新特性? 然后进一步展示了这些新特性的强大便利之处。
虽然是11年2月发布的,而现在已经是2016年的1月底了,虽然步子慢了五年,但3.x的动画依然没有过时,这篇文章风采依旧,非常值得初学者(我)学习。
本文非全文翻译,亦非逐字逐句翻译,作为我自己的学习笔记,可谓是「总结式翻译」,梳理、备忘。
强烈建议阅读原文,文档和代码要结合阅读,以相互佐证和理解,同时运行 apidemos 中相关的示例,观察动画效果,修改参数再观察动画效果。
系列文章:
如何学习 android animation?
从 android sample apidemos 中学习 android.animation api 的用法
android animation interpolator - android 动画插值器分析
weiyi.li li2.me 2016-01-28 ~ 2016-02-01
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 < numvalues; ++i) { // 根据篡改后的进度计算动画过程值 turn the interpolated fraction into an animated value. // 继续追代码,会追到 propertyvaluesholder 和 keyframes 两个类,深究不下去了 orz todo mvalues[i].calculatevalue(fraction); } }
如果不是浮点数和整数,或者内置的处理整数的 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 (这是全文翻译)
by
weiyi.li li2.me weiyi.just2@gmail.com
2016-01-28 ~ 2016-02-01
禁止转载
不把阅读英文文档当做翻译事业的程序员不是好厨子