本文是对Android动画API的官方资料的翻译。
原文连接:https://developer.android.com/guide/topics/graphics/prop-animation.html
以下正文开始:
属性动画
属性动画系统是一个强大的框架,几乎可以使任何对象产生动画。你可以随着时间改变任意的对象属性来定义动画效果,不管该属性是否显示在屏幕上。属性动画在指定的时间长度改变属性值。为了形成动画,你需要指定想要
变化的属性,比如对象在屏幕上的位置,
动画效果持续多久,还有
属性值的变化范围。
属性动画系统可以定义动画的以下属性:
- 持续时间(Duration):动画时长,默认为300 ms。
- 时间插值(Time interpolation):属性值随时间的函数变化关系。
- 重复计数与模式(Repeat count and behavior):可以指定当动画结束时是否需要重复动画以及重复次数;也可以指定动画逆向进行,也可以设置正向逆向重复直到达到重复次数。
- 动画集合(AnimationSet):你可以定义一组动画集合,同时执行或者顺序执行。
- 帧刷新延迟(Frame refresh delay):定义多久刷新一次动画帧。默认值每10ms刷新一次,但是刷新的速度最终取决于系统繁忙与否以及底层定时器的的速度。
属性动画如何工作
首先,我们用一个简单的例子来解释动画如何工作。图1所示,假设一个对象选取代表屏幕上水平位置的 X 属性作为动画属性,动画持续时间设为40 ms,运行距离为40 px。每过10 ms(Frame refresh delay 默认值),对象水平移动10 px。在40 ms结束的时候,对象在水平位置40,动画结束。这是一个线性插值的动画例子,也就是对象以恒定的速度移动。
图1 线性动画例子
你也可以指定非线性动画。图2表示一个假设的对象在动画开始的时候加速,在结束的时候减速。该对象仍然在40ms内移动40px,但是非线性的移动。一开始,动画加速到中点然后减速知道结束。如图2所示,开始和结束的阶段的运行距离小于中间的距离。
图2 非线性动画例子
我们仔细探究如上所述的属性动画计算动画的重要因素,图3表示主要classes之间相互工作原理。
图3 动画如何计算
ValueAnimator类记录动画定时,比如动画运行了多长时间以及属性当前值。
ValueAnimator类封装了一个接口TimeInterpolator,用来定义动画插值,还有一个接口TypeEvaluator,用来定义如何计算属性值。比如图2,使用的TimeInterpolar是AccelerateDecelerateInterpolar, TypeEvaluator是IntEvaluator。
要开始动画,先创建ValueAnimation对象,并给你要给产生动画的属性赋于在动画区间的初始值和结束值。当调用start()函数,动画开始。整个动画期间,ValueAnimator计算时间分数(
elapsed fraction,介于0,1之间),该分数基于动画持续时间和已经过去的时间,也就是完成动画的百分比,0即0%,1即100%。例如在图1中 t = 10 ms时,时间分时为 .25,因为持续时间为40ms。
当ValueAnimator完成时间分数的计算时,会调用TimeInterpolar来计算内插部分(
interpolated fraction)。将设定的时间插值考虑进来,内插部分和时间分数有一定的映射关系。例如,图2中动画缓慢加速,在 t = 10 ms时,内插分数为 0.15,小于时间分数0.25,而在图1中内插分数和时间分数始终保持一致。
属性动画与视图动画的区别
视图动画系统只能使视图对象产生动画,所以如果你要非视图对象产生动画,需要用自己的代码来实现。视图动画系统还受限于只能操作视图对象一小部分,比如视图的缩放与旋转,但是不能操作背景颜色。
视图动画的另一个缺点就是只能改变视图绘制的位置,而不是改变视图本身。比如说,你使一个按钮Button移动,button正确绘制,但是按钮可点击的实际位置不变,所以要实现自己的逻辑来控制。
对于属性动画,这些限制都不存在了,并且可以对任何对象的任何属性做成动画效果(包括视图和非视图),并且是改变对象本身。实现动画的方式上,属性动画更加强大。在高版本中,你可以给要操作的属性分配animators,比如color、position、size。并且可以定义动画的一些方面如插值以及animators同步。
然而,视图动画设置省时,代码简单。如果视图动画可以完成你的需求或者现有代码已经按照想要的方式运行,那就无需属性动画了。在不同的情况下使用两种动画系统也是可行的。
API 概览
你可以在
andorid.animation 找到大多数属性动画API。因为视图动画已经定义了很多插值函数(见
android.view.animation),可以在属性动画中使用这些插值函数。下面几张表描述了属性动画的主要组成。
Animator 类提供了创建动画的基础架构,一般不需要直接使用该类,因为它仅提供最小功能模块,需要被继承来完全支持动画值。继承自Animator的子类有:
表一 Animator
Class | 描述 |
ValueAnimator | 属性动画主要的定时机制,同时计算属性。该类包含所有核心功能,并包含计算动画值、每个动画的定时细节、是否重复的信息、接受更新事件的监听器、设置自定义类型。产生动画分为两部分:计算动画值 和 将值赋予对象属性。ValueAnimator并不实现第二部分,所以你必须监听ValueAnimator计算得到的值,然后通过自己的逻辑来实现动画。查看更多信息见Animating with ValueAnimator。 |
ObjectAnimator | ValueAnimator的子类,可以设定目标对象及目标属性。当该类计算好动画的新值时,相应地更新属性值。因为ObjectAnimator更新目标对象的属性动画值更加简单,所以大多数时候要使用该类。然而有时候需要直接使用ValueAnimator,因为ObjectAnimator有更多的限制,比如目标对象上需要特定的访问器方法。 |
AnimatorSet | 提供动画分组机制,这样它们之间可以以某种关系运行。你可以指定这些动画同时运行,顺序执行,或者以一定的延迟来执行。常看更多信息见Choreographing multiple animations with AnimationSets |
Evaluators 阐述了属性动画系统如何给给定的属性计算值。它们拿到Animator类得到的时序数据(动画的初始值和结束值)并且基于该数据计算动画下的属性值。属性动画系统包含以下evaluators:
表2 Evaluators
Class/Interface | Description |
IntEvaluators | 计算int 属性值的默认evaluator |
FloatEvaluation | 计算float属性值的默认evaluator |
ArgbEvaluation | 计算颜色属性的默认evaluator(十六进制) |
TypeEvaluator | 创建自定义evaluator的接口。如果要产生动画的属性既不是int,float或者color,你必须实现TypeEvaluator接口来指定如何计算对象属性的动画值。如果你想改变int,float,color这些类型的默认行为,也可以给指定自定义的TypeEvaluator。查看更多信息写自定义详见-->Using a TypeEvaluator |
Interpolator:时间插值器指定动画值作为时间的函数。举个例子,你可以指定整个动画过程以线性执行,意味着整个时间段动画均匀的移动;也可以指定非线性时间。表3描述的interpolator包含在--->
android.view.animation.如果下面的interpolator均不能满足要求,实现TimeInterpolar接口并实现自己的。自定义interpolator更多信息--->
Using interpolator
表3 Interpolators
Class/Interface | Description |
AccelerateDecelerateInterpolator | Interpolator:开始和结束的时候变化很慢,但中间会加速 |
AccelerateInterpolator | Interpolator:开始很慢,然后不断加速 |
AnticipateInterpolator | Interpolator:whose change starts backward then flings forward(大意是,开始向后再向前) |
AnticipateOvershootInterpolator | Interpolator:开始向后再向前,但是超过目标值,再回退到final value |
BounceInterpolator | Interpolator:动画结束时候有反弹 |
CycleInterpolator | Interpolator:动画以指定的循环数循环 |
DecelerateInterpolator | Interpolator:开始很快,然后开始减速 |
LinearInterpolator | Interpolator:匀速改变 |
OvershootInterpolator | Interpolator:向前甩,超过最终值后再返回 |
TimeInterpolar | 接口:可以实现自己的Interpolator |
ValueAnimator 实现动画
通过指定一系列int,float,或者color值,ValueAnimator类可以将其产生动画。你可以通过ValueAnimator的构造方法来获得一个ValueAnimator,构造方法有ofInt(), ofFloat(), ofObject()。举个例子:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
在上述代码中,ValueAnimator开始在0和1之间计算动画值,持续时间1000 ms,调用start() 方法开始执行。
你也可以指定自定义类型,如下:
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
在上述代码中,ValueAnimator利用MyTypeEvaluator中实现的逻辑计算startPropertyValue到endPropertyValue之间的动画值。持续时间1000 ms,调用start() 方法开始执行。
之前的代码段并没有对对象有真正的影响,因为ValueAnimator并没有直接操作对象及属性。但是你最想做的是利用计算出来的数据来改变对象,产生动画。你可以在ValueAnimator中定义一个监听器来处理动画生命周期中的重要事件,比如框架的更新(Frame updates)。当实现监听器后,你可以通过调用getAnimatedValue() 方法来获取指定框架刷新需要的计算值。listener的更多信息,详见---> Animation Listener
ObjectAnimator实现动画
前面章节已经提过,ObjectAnimator是ValueAnimator的子类,结合了时序机制和ValueAnimator的值计算。这使得对象产生动画更加简单,你不再需要实现ValueAnimator.AnimatorUpdateListener,因为可以实现属性的自动更新。
实例化ObjectAnimator对象和ValueAnimator类似,但是你也可以指定对象以及对象的属性(以String的形式),以及动画值的区间。
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
为了使ObjectAnimator正确的更新属性,需要做以下几项:
- 对象属性必须有个setter函数,形式为set<属性名>()。因为ObjectAnimator自动更新属性,所以必须能通过setter方法来获取属性。比如,属性名为 foo,你需要一个setFoo()方法。如果setter方法不存在,你有三种选择:
- 如果可以,加setter方法;
- 使用一个你能更改的封装类,这个封装类通过一个有效的setter方法接收值然后转寄给原始对象。
- 使用ValueAnimator代替
- 如果你在ObjectAnimator工厂方法中为values...仅指定一个值,那么把其假定为ending value。所以你要操作的对象属性要有一个getter()方法来获取动画的初始值。getter()方法的形式必须是get<属性名>(),比如属性名是 foo,需要一个getFoo()方法。
- 属性的getter(如果需要)和setter方法必须操作同一类型(即为ObjectAnimator指定的初始值和结束值),举个例子,你必须用targetObject.setPropName(float)和targetObject.getPropName(float),当你使用了下述构造方法:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
- 根据产生动画的对象或属性,你有可能(might)需要调用View的invalidate()方法来强制屏幕以更新后的属性值来重绘。你要在onAnimationUpdate()回调中完成。例如,让一个Drawable对象的color属性产生动画,只有在重绘的时候才能在屏幕上更新。所有关于View的属性setters,比如setAlpha()和setTranslationX(),invalidate the View properly,所以在调用这些方法赋予新值时无需再invalidate the View。
利用Animation创建多动画
很多情况下,你想播放一个动画取决于另一动画的开始或者结束。Android系统允许你将动画捆绑一起到AnimationSet,所以你可以指定是否要同时、顺序、以特定延迟来执行动画,你也可以相互嵌套AnimationSet。
下面的简单代码取自
Bouncing Balls,以如下顺序执行Animator对象:
1. 播放 bounceAnim;
2. 同时播放 squashAnim1,squashAnim2,stretchAnim1,stretchAnim2;
3. 播放 bounceBackAnim;
4. 播放 fadeAnim。
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();
Animation 监听器
你可以使用下列监听器来监听动画持续时间内的重要事件:- Animator.AnimatorListener
- onAnimationStart() - 动画开始时调用
- onAnimationEnd() - 动画结束时调用
- onAnimationRepate() - 动画重复时调用
- onAnimationCancel()- 动画取消时调用,同时会调用onAnimationEnd()方法(无论何种方式停止都会被调用)。
- ValueAnimator.AnimationUpdateListener
- onAnimationUpdate() - 被动画的每一帧调用,监听该事件来使用ValueAnimator计算好的值。为了使用该值,通过ValueAnimator对象的getAnimatedValue()方法来获得。如果使用ValueAnimator,需要实现该监听器接口。
举个例子,Bouncing Balls 例子仅仅为onAnimationEnd() 回调创建了AnimatorListenerAdapter:
ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}
Animating Layout Changes to ViewGroups
属性动画可以animate changes to ViewGroup objects ,也提供了简单的方法使View对象产生动画。
你可以通过LayoutTransition类来使ViewGroup里布局变化(layout changes)产生动画,当调用View的setVisiblity()方法(VISIBLE或者GONE)来设置ViewGroup 中的View添加到或者移除时,View经历出现和消失的动画。ViewGroup中剩下的Views也可以以动画的形式变到新的位置上。你可以在LayoutTransition对象中通过调用setAnimator()来定义以下动画,并且传递一个Animator对象,同时携带下面LayoutTransition常量之一:
- APPEARING - 条目加入到容器中动画标志位
- CHANGE_APPEARING - 由于新条目加入,其他条目移动动画的标志位
- DISAPPEARING - 条目从容器中移除时的动画标志位
- CHANGE_DISAPPEARING - 由于条目移出,其他条目移动的动画标志位
LayoutTransition例子展示了如何为布局转换定义动画,然后然后将动画效果设定在想产生动画的View对象。
LayoutAnimationByDefault和它对应的布局文件layout_animations_by_default.xml如何在XML中使用默认的布局转换动画。只需要为ViewGroup设定android: animationLayoutchanges 属性为true即可。例如:
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
将此属性设定为true,自动地加入Views加入或移除的动画,以及ViewGroup中剩余Views的动画效果。
使用TypeEvaluator
如果你要对Android系统不”能识别“的类型产生动画,你可以通过实现TypeEvaluator接口来实现自己的evaluator。Android”能识别“的类型有int、float和color,分别对应IntEvaluator,FloatEvaluator和ArgbEvaluator三种类型的evaluator。
TypeEvaluator接口只需实现一个方法,evaluate()。这个方法允许你使用的Animator为你的动画属性返回合适的值。FloatEvaluator类说明了如何使用:
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或者ObjectAnimator运行时,它计算动画当前播放的部分比(介于0,1之间),然后根据使用的Interpolator来计算插值,插值分数是你的TypeEvaluator接受到的fraction参数。所以计算动画值时你不必考虑Interpolator。
使用Interpolator
Interpolator定义了特定值与时间的函数关系。比如你可以指定整个动画线性执行,也就是整个动画阶段动画均匀移动;或者指定动画非线性执行,比如使用加速或者减速在动画的开始或者结尾。
Interpolators从Animator中接收一个代表动画执行过的时间分数,Interpolators改变这个分数来配合动画类型。Android在android.view.animation package中提供了一系列常见的Interpolators。如果没有动画满足需求,可以实现TimeInterpolator接口来自定义。
举例说明,对比默认Interpolators中的AccelerateDecelerateInterpolator和LinearInterpolator计算插值的不同。LinearInterpolator不影响输入的时间分数,AccelerateDecelerateInterpolator使进入时加速,出去时减速。下述方法定义了这些Interpolator的逻辑:
AccelerateDecelerateInterpolator:
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
LinearInterpolator:
public float getInterpolation(float input) {
return input;
}
下表代表1000ms动画中interpolators计算的近似值:
ms elapsed | Elspased fraction/ Interpolated fraction(Linear) | Interpolated fraction(Accelerate / Decelerate) |
0 | 0 | 0 |
200 | .2 | .1 |
400 | .4 | .345 |
600 | ,6 | .8 |
800 | .8 | .9 |
1000 | 1 | 1 |
指定关键帧(KeyFrames)
关键帧对象包含一个时间/值 对,使你可以定义特定时间的动画的特定状态。每个keyframe也可以有自己的Interpolator,来控制前一个关键帧到该关键帧的动画行为。
实例化一个keyframe对象,必须使用下列工厂方法之一:ofInt(), ofFloat(), or ofObject()来获取合适类型的的keyframe。然后调用ofKeyframe()工厂方法来获取一个PropertyValuesHolder对象。一旦有了这个对象,你可以传递该对象以及需要产生动画的对象来获得一个Animator:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);
更多使用keyframe的完整的例子,见--->
MultiPropertyAnimation
Animating Views
属性动画系统支持View对象的流线型动画(streamlined animation)并且与视图动画相比有一定优势。视图动画是改变View对象绘制的方法,这是由每个View的容器控制的,因为View本身没有可以被操作的属性。这就导致了View产生了动画,但是View本身没有任何变化,最后导致虽然View绘制在其他位置,但是该对象依然存在于原来的位置。在Android 3.0中,加入了新的属性和对应的setter和getter方法来消除这一缺点。
属性动画可以通过改变View对象的实际属性来使View产生动画。另外当属性变化时,Views自动调用invalidate()方法来刷新屏幕。View类中帮助属性动画的新特性是:
- translationX 和 translationY:该特性控制View定位时,相对于布局容器中设定值的偏移量
- rotation, rotationX, rotationY:这些属性控制围绕基准点的2D,3D旋转
- scaleX, scaleY:这些属性控制围绕基准点的2D缩放
- pivotX, pivotY:这些属性控制基准点的坐标,rotation和scaling变换中出现,默认基准点在对象的中心
- x 和 y:这是简单实用的属性来描述View在容器中的最终位置,为left值,top值和translationX和translationY值的和
- alpha:代表View的alpha透明度,默认为1,0表示完全透明(不可见)
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
使用ViewPropertyAnimator产生动画
ViewPropertyAnimator提供了简单的方法来并行操作一个view的多个属性。他的功能很想ObjectAnimator,因为它改变view属性的实际值,但是同时操作多个动画属性就不太一样。另外,使用ViewPropertyAnimator的代码更加简洁,方便阅读。以下代码片段展示了使用multiple ObjectAnimator,single ObjectAnimator,和ViewPropertyAnimator同时改变view的x 与 y属性:
Multiple ObjectAnimator objects:
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
One ObjectAnimator:
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
ViewPropertyAnimator:
myView.animate().x(50f).y(100f);
在XML中声明动画
属性动画允许在XML中声明动画,在XML中定义动画,可以在对个Activities中重复使用,而且更加简单的编辑动画序列。
为了区分使用新的属性动画APIs的动画文件和使用view.animation框架的文件,在Android 3.1后,你应该把属性动画的XML文件保存在res/animator/ 路径下。
下述动画类由下列XML标签来声明:
- ValueAnimator - <animator>
- ObjectAnimator - <objectanimator>
- Animator - <set>
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
为了执行动画,你必须在代码中将XML文件inflate到一个AnimationSet对象中,然后在动画开始前要指定所有动画的目标对象。调用setTarget()方法来快捷地为该AnimationSet的所有子动画指定一个目标对象。下面代码展示了如何做:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
更多定义属性动画的XML语法,详见--->
Animation Resource