原文 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
禁止转载
   不把阅读英文文档当做翻译事业的程序员不是好厨子
   
 
   