英文原文:http://developer.android.com/guide/topics/graphics/prop-animation.html
版本:Android 4.0 r1 - 22 Mar 2012 0:35
译者注:黄色底色为未决译文
property 动画系统是相当健壮的框架,它几乎可以动画显示任何对象。 你可以定义一个动画来定时改变任何对象的属性值,不论该对象是否在屏幕上显示。 property 动画将以一定的时间间隔修改属性值(对象中的字段值)。 要实现动画显示,你须指定对象的相应属性(比如对象的屏幕位置),以及动画时长、动画时间间隔。
property 动画系统能让你设定以下动画要素:
首先,我们通过一个简单例子来回顾一下动画的工作原理。图 1 表明了某对象的 x 属性变化情况,即在屏幕上水平的位置。 动画的持续时间设为 40 ms,移动的距离是 40 个像素点。每隔 10 ms,这是默认的帧刷新率,此对象横向移动 10 个像素点。 在 40 ms 到期后,动画停止,此对象位置横移 40 个像素点。以下是采用线性插值的示例,也即此对象以固定的速度移动。
图 1. 线性动画的示例
你还可以把动画设置为非线性插值方式。图 2 表示,某对象开始时加速移动,结束时减速移动。 此对象仍然是在 40 ms 内移动 40 个像素点,但是速度是非线性变化的。在开始时,此动画加速移动至中间位置,然后再减速移动至终点。 如图 2 所示,开始和结束阶段移动的距离比中间位置要少一些。
图 2. 非线性动画示例
让我们来仔细查看一下 property 动画系统的关键部件在上述动画中的计算过程。图 3 展示了主要类的相互工作方式。
图 3. 动画的计算过程
ValueAnimator 对象记录了动画自身的一些值,比如已经显示了多长时间、动画对应属性的当前值。
ValueAnimator 中封装了一个定义动画变化方式的 TimeInterpolator ,以及定义了属性计算方式的 TypeEvaluator 。比如,图 2 中的 TimeInterpolator 应该使用 AccelerateDecelerateInterpolator , TypeEvaluator 应该使用 IntEvaluator。
要启动动画,请创建一个 ValueAnimator 并指定要动画显示的属性的初始值和结束值。调用 start() 启动动画。在整个动画过程中, ValueAnimator 根据动画总时间和已进行的时间和计算出一个时间比例因子(elapsed fraction),大小介于0和1之间。 时间比例因子代表动画已完成时间的百分比,0 表示 0%,1 表示 100%。例如,在图 1 中,t = 10 ms 时的时间比例因子应该是 0.25,因为总时间 t = 40 ms。
ValueAnimator 算完时间比例因子后,将调用已设置好的 TimeInterpolator 计算出一个插值因子(interpolated fraction)。插值因子是一个由时间比例因子换算出来的图像显示状态因子。 比如,在图 2 中,t = 10 ms 时,因为动画的加速度较小,插值因子约是 0.15,它小于时间比例因子 0.25。 在图 1 中,插值因子一直保持不变,并与时间比例因子一致。
算完插值因子, ValueAnimator 就会调用合适的 TypeEvaluator ,根据插值因子、初始值、结束值计算出需要动画显示的属性值。 比如,在图 2 中,t = 10 ms 时的插值因子是 0.15,所以此时的属性值应该是 0.15 X (40 - 0),也即为 6。
范例 API Demos 中的 com.example.android.apis.animation 包给出了很多 property 动画系统的使用例子。
API 概述你可以在 android.animation 中找到大部分 property 动画系统的 API。 由于 view 动画系统已经在 android.view.animation 中定义了很多 interpolator ,你可以在 property 动画系统中直接使用它们。 下表对 property 动画系统中的主要内容进行了说明。
Animator 类只提供了用于创建动画的基础构造方法。它只提供了最基本的功能,你必须扩展这些功能才能完成对动画的支持,因此通常你不应该直接使用该类。 以下子类就对 Animator 进行了扩展:
表 1. Animator
类 | 说明 |
---|---|
ValueAnimator | property 动画的主要计时器引擎,用于计算需动画显示的属性值。包含了计算动画值的所有核心功能、每个动画的计时信息、动画是否循环播放、接收更新事件的侦听器、对自定义类型计算的支持。 让属性动画显示包括两步工作:计算动画属性值、向对象的属性赋值。 ValueAnimator 并不会执行第二步工作,你必须对 ValueAnimator 计算值进行侦听,并自行更新对象属性。详情请参阅 用 ValueAnimator 实现动画 章节。 |
ObjectAnimator | ValueAnimator 的子类,用于设置需动画显示的目标对象和属性。它会在计算出一个新值时更新属性值。 因为它能非常简便地处理目标对象的动画值,所以你在大多数情况下都会用到它。 不过,有时你还是会直接使用 ValueAnimator ,因为使用 ObjectAnimator 会有一些限制,比如请求目标对象特定的访问器(accessor)方法。 |
AnimatorSet | 提供一种把动画分组归并的机制,以便组合运行多个动画效果。你可以一起播放多个动画效果,或者错时播放。 详情请参阅用 Animator Set 编排多个动画章节。 |
evaluator 用于告知 property 动画系统给定属性值的计算方式。根据 Animator 类给出的计时数据、动画初始值和结束值,它将计算出需动画显示的属性值。property 动画系统提供了以下 evaluator:
表 2. Evaluator
类/接口 | 说明 |
---|---|
IntEvaluator | 计算整数型(int)属性时的缺省 evaluator。 |
FloatEvaluator | 计算浮点数型(float)属性时的缺省 evaluator。 |
ArgbEvaluator | 计算十六进制颜色属性时的缺省 evaluator。 |
TypeEvaluator | 一个接口,允许你创建自己的 Evaluator。 如果要动画显示的对象属性类型不是int、float、颜色,你就必须实现 TypeEvaluator 接口来定义对象属性值的计算方法。如果需要进行缺省方式之外的处理,你也可以为int、float、颜色类型的属性指定一个自定义的 TypeEvaluator 。关于编写自定义 evaluator 的详细信息,请参阅使用 TypeEvaluator章节。 |
时间 interpolator 定义了动画显示的计算方式,它是一个关于时间的函数。 比如,你可以指定线性播放动画,也即全程匀速播放。或者,你也可以指定为非线性播放,比如一开始加速,而临近结束时减速。 表 3 列出了 android.view.animation 中包含的 Interpolator。 如果已有的 interpolator 无法满足需求,你可以实现 TimeInterpolator 接口并创建你自己的 interpolator 。 关于编写自定义 interpolator 的方法,请参阅 使用 Interpolator。
表 3. Interpolator
类/接口 | 说明 |
---|---|
AccelerateDecelerateInterpolator | 该 interpolator 的速度在开始和结束时较慢,而中间加速变化。 |
AccelerateInterpolator | 该 interpolator 的速度在开始时较慢,然后加速。 |
AnticipateInterpolator | 该 interpolator 先把起点往回移一点,再快速播放。 |
AnticipateOvershootInterpolator | 该 interpolator 先把起点往回移一点,再快速播放,待越过终点后再返回至结束值。 |
BounceInterpolator | 该 interpolator 在最后阶段以弹球效果显示 |
CycleInterpolator | 该 interpolator 循环播放给定次数。 |
DecelerateInterpolator | 该 interpolator 开始时快速播放,然后减速。 |
LinearInterpolator | 该 interpolator 匀速播放。 |
OvershootInterpolator | 该 interpolator 快进至超过结束值后返回。 |
TimeInterpolator | 该 interpolator 允许你实现自己的 interpolator。 |
通过指定一组int、float、颜色值, ValueAnimator 类能让你在动画过程中修改某些类型的值。通过调用其工厂方法(factory method): ofInt()、 ofFloat()、 ofObject(), 你可以获得一个 ValueAnimator 实例。比如:
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 先计算动画值为startPropertyValue 至 endPropertyValue之间,应用 MyTypeEvaluator,持续时间 1000 ms,并执行 start()。
其实这段代码对某对象是无法实现动画效果的,因为 ValueAnimator 还没有直接控制对象或其属性值。你最有可能要做的事就是用计算出来的属性值修改动画对象。 通过在 ValueAnimator 中定义侦听器,你就可以实现这一点。侦听器可以实现动画过程中的事件处理,比如帧的刷新。 在侦听器的实现代码中,你可以调用 getAnimatedValue() 获取为本次画面刷新计算出来的属性值。关于侦听器的详细信息,请参阅 动画侦听器一节。
ObjectAnimator 是 ValueAnimator (前一节已介绍)的子类,它是由计时引擎和计算目标对象的属性值 ValueAnimator 组合而成的。因为属性值将会自动更新,你不再需要实现 ValueAnimator.AnimatorUpdateListener 了,因此实现任意对象的动画显示就更加容易了。
ObjectAnimator 的示例与 ValueAnimator 类似,只是你还需要在动画区间值之前额外指定对象和属性名称(字符串格式):
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
为了保证 ObjectAnimator 能够正确地更新属性值,你必须做到以下几点:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
很多时候,你需要在一个动画的开始和结束时播放另一个动画。Android 系统可以让你把多个动画组合为一个 AnimatorSet ,这样你就可以设定同时播放、顺序播放或延迟一段时间后再播放。 你还可以嵌套多个 AnimatorSet 对象。
以下例程来自 Bouncing Balls 范例(作了一定简化),它将用以下规则播放 Animator 对象:
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();
关于使用 animator set 的完整示例,请参阅 API Demo 中的 Bouncing Balls。
在播放过程中,你可以用下列侦听器来监听那些重要的动画事件。
onAnimationUpdate() —— 动画每播放一帧时调用。在动画过程中,可侦听此事件来获取并使用 ValueAnimator 计算出来的属性值。利用传入事件的 ValueAnimator 对象,调用其 getAnimatedValue() 方法即可获取当前的属性值。如果你使用 ValueAnimator ,则必需实现此侦听器。
根据不同的动画对象及其属性,你也许需要调用 View 的 invalidate() 方法,强制用新的属性值重绘屏幕区域。比如,要动画显示 Drawable 对象的颜色属性,只有重绘才能在屏幕上显示出变化。 View 中所有的属性 setter 方法,比如 setAlpha() 和 setTranslationX() ,都会适时地禁用 View,因此你不需要在用新属性值调用这些方法时禁用 View。
如果你不愿意实现 Animator.AnimatorListener 中所有的方法,那你可以不用此侦听器,而是扩展 AnimatorListenerAdapter 类。 AnimatorListenerAdapter 类为所有方法都实现了空方法体,你自行选择并覆盖即可。
例如,API demo 的 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());
}
与 View 动画一样简单, property 动画系统对 ViewGroup 对象的动画显示提供了支持。
你可以用 LayoutTransition 类来动画显示 ViewGroup 中的 Layout。 通过把 View 加入或移出 ViewGroup ,或者以 VISIBLE、 INVISIBLE、 GONE 调用 View 的 setVisibility() 方法,可以实现某个 View 的显现和消失效果。 当你添加或删除 View 时,ViewGroup 中的其它 View 也可以用动画的方式移到新的位置显示。 你可以调用 LayoutTransition 对象的 setAnimator() 方法来定义下列动画方式,调用参数是 Animator 对象和以下 LayoutTransition 常量:
你可以为这四种事件定义自己的动画方式,以便定制 layout 变化时的外观,也可以只是通知动画系统采用默认的动画方式。
API Demo 中的 LayoutAnimations 示例展示了 layout 变化形式的定义,并设置了 View 的动画方式。
LayoutAnimationsByDefault 及其对应的 layout_animations_by_default.xml layout 资源文件展示了以 XML 格式启用 ViewGroups layout 默认变化形式的方法。 你唯一要做的就是把 ViewGroup 的 android:animateLayoutchanges 属性设为 true。 比如:
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
此属性设为 true 将会自动把 View 加入和移出 ViewGroup 的过程以动画方式显示,ViewGroup 中其它的 View 同时也会以动画方式进行调整。
如果 Android 系统无法识别需要动画显示的属性值类型,你可以创建自己的 evaluator,实现 TypeEvaluator 即可。Android 系统可识别的类型包括int、float和颜色,分别由 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 提供支持。
TypeEvaluator 接口中只要实现一个方法即可,就是 evaluate()。 此方法返回一个当前动画时刻的属性值。 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);
}
}
TypeEvaluator
接收到的
fraction 参数本身就是此插值因子,因此你在计算动画属性值时就不必用到 interpolator 了。
插值器(interpolator)定义了动画过程中属性值的变化规则,它是一个关于时间的函数。 比如,你可以把动画过程设定为线性变化,这意味着它全程都是匀速变化。 或者,你可以设定动画为非线性变化,比如在开始或结束时加速或减速。
动画系统中的 interpolator 会从 Animator 中接收到一个时间比例因子,此因子代表了动画已显示的时间。 interpolator 会根据动画的类型修改此因子。Android 系统在 android.view.animation 包中提供了一组通用的 interpolator。如果这些都不满足需要,你可以实现 TimeInterpolator 接口来创建自己的 interpolator。
作为示例,缺省的 AccelerateDecelerateInterpolator 和 LinearInterpolator 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;
}
下表列出了这些 interpolator 对持续 1000 ms 的动画计算出的近似值:
已显示的时间值 ms | 已显示的时间比例因子/插值后因子(线性) | 插值后因子 (加速/减速) |
---|---|---|
0 | 0 | 0 |
200 | .2 | .1 |
400 | .4 | .345 |
600 | .6 | .8 |
800 | .8 | .9 |
1000 | 1 | 1 |
如上表所示, LinearInterpolator 匀速改变属性值,每 200 ms 变化 0.2。 AccelerateDecelerateInterpolator 则在 200ms 至 600ms 间变化比 LinearInterpolator 快,600ms 至 1000ms 间变化则较慢。
Keyframe 对象中包含了一个时间/属性值的键值对,用于定义某个时刻的动画状态。 每个关键帧还可以拥有自己的 interpolator,用于控制前一关键帧至本关键帧之间的动画行为。
要实例化一个 Keyframe 对象,你必须使用它的工厂方法 ofInt()、 ofFloat()、 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);
关于使用关键帧的完整示例,请参阅 API Demo 中的 MultiPropertyAnimation
property 动画系统可以让 View 对象进行一系列的动画显示,相比 view 动画系统具有更多优势。 view 动画系统通过改变 View 对象的绘制方式来实现动画效果。 因为 View 本身没有给出属性以供控制,所以这是由 View 所在容器来完成处理的。 虽然这样能实现 View 的动画效果,但 View 对象本身并没有变化。 因此会出现这种情况:虽然屏幕上的显示位置已经移动过了,但对象实际仍然停留在原来的位置。 为了消除这一弊病,在 Android 3.0 中给 View 增加了一些新的属性以及相应的 getter、setter 方法。
property 动画系统可以通过修改 View 对象实际的属性值来实现屏幕上的动画效果。此外,当属性值发生变化时,Views 也会自动调用 invalidate() 方法来刷新屏幕。 View 类中新增的便于实现 property 动画的属性包括:
要动画显示 View 对象的某个属性,比如颜色或旋转值,你所有要做的事情就是创建一个 property animator,并设定对应的 View 属性。比如:
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
关于创建 animator 的详细信息,请参阅 ValueAnimator 和 ObjectAnimator 章节。
利用一个基础的 Animator 对象, ViewPropertyAnimator 为同时动画显示 View 的多个属性提供了一种捷径。这种方式与一个 ObjectAnimator 很类似,因为也会修改 View 的实际属性值,但在需要一次动画显示多个属性时效率会更高一些。 此外,使用 ViewPropertyAnimator 的代码会简洁很多,可读性更好。以下代码段展示了分别使用多个 ObjectAnimator 对象、单个 ObjectAnimator 对象、 ViewPropertyAnimator 对象实现 view 的 x 和 y 属性的同时变化。
多个 ObjectAnimator 对象
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
单个 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);
关于 ViewPropertyAnimator 的详细信息,请参阅 Android Developers blog post 相关内容。
property 动画系统允许你用 XML 声明 property 动画,而不需要编写代码来实现。 通过 XML 定义的方式,你可以方便地在多个 activity 中复用动画资源,并且更容易编排动画顺序。
为了把采用新增 property 动画 API与采用以前 view animation 框架的动画文件区分开来,自 Android 3.1 开始,你应该把 property 动画 XML 文件保存到res/animator/目录下(而不是res/anim/目录)。 目录名animator是可以修改的,但如果你要用 Eclipse ADT(ADT 11.0.0+) 插件作为 layout 编辑工具,那就不能动了。 因为 ADT 只会搜索res/animator/目录下的动画资源。
以下列出了可用 XML 标记声明的 property 动画类:
以下例子顺序播放两组动画,第一组动画内嵌入了两个对象的动画:
<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 资源置入 AnimatorSet 中,然后设置动画的所有目标对象,再开始动画。用 setTarget() 可以很方便地为 AnimatorSet 下所有子元素设置一个目标对象。以下代码展示了这种设置方法:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
关于定义 property 的 XML 语法,请参阅动画资源。