【Android API指南】动画和图像(1) - 属性动画
属性动画系统是非常强健的框架,允许你移动几乎任何东西。你可以定义一个动画去改变任何对象属性,不管它是否绘制到屏幕上。一个属性动画在一个指定时间内改变一个属性(对象中的一个字段)的值。你需要先指定要改变的对象属性,例如一个对象在屏幕中的位置,然后指定你想在多长时间内改变它,改变这个值的区间是什么。
属性动画系统让你可以定义下面这些动画特征:持续时间:动画的持续时间,默认是300ms。时间差值:通过一个方法计算出来的当前运行时间内属性的值。重复次数和行为:你可以指定动画一个持续时间后是否重重,重复几次。你也可以指定动画是否反向播放,直到达到指定的重复次数。动画集:你可以设置一组动画以前播放,或者按照顺序播放,或者延时播放。帧的刷新延时:你可以指定多长时间刷新一次你的动画帧。默认是10ms一次,但是这取决于你的系统的繁忙程度,以及系统可以支持的底层计数器的速度。属性动画是怎么工作的首先,我们用一个简单例子了解动画的工作原理。图一描述了一个沿着x属性运动的假定对象,x属性代表屏幕的水平位置。动画的持续时间为40ms,距离是40像素。默认每10ms刷新一次帧,这时对象水平移动了10个像素。40ms后,动画停止,对象停止在水平位置40的地方。这是一个简单的线性插值动画,意思是对象是匀速运动的。图一:线性插值动画
你也可以指定一个不是线性插值的动画,图二展示的动画是开始加速,结束减速的动画。对象也是40ms移动了40个像素,但是它的非线性的。在开始阶段,动画加速到中间点,然后从中间点减速,直到动画停止。如图二所示,开始和结束的10ms移动的距离要小于中间时段的距离。图二:非线性动画
让我们详细了解一下属性动画系统的重要组件,以及它们是怎么计算上图那样的动画的。图三描述了相互工作的主要类。图三:动画是怎么计算的
ValueAnimator对象记录你的动画时间轨迹,比如动画已经运行了多久,运行时的当前属性值。
ValueAnimator封装了一个TimeInterpolator,它定义了动画的时间插值,还封装了一个TypeEvaluator,它指定动画师属性值是怎么被计算的。例如,图二中,TimeInterpolator就是AccelerateDecelerateInterpolator,TypeEvaluator是IntEvaluator。
开始一个动画,创建一个ValueAnimator,赋给它动画开始和结束的属性值,当你调用start()启动动画时,整个动画期间,ValueAnimator计算一个0到1之间的分数,表示动画在整个持续时间内已经运行了多少比例。这个分数表示整个动画运行的百分比,0表示0%,1表示100%。例如,在图一中,t=10ms时这个数就是0.25,因为整个动画持续时间是40ms。
当ValueAnimator已经计算好一个运行分数,就会调用TimeInterpolator当前的设置,去计算一个插值分数。一个插值分数是计算了插值后从运行分数产生的一个新的分数。例如,在图二中,由于开始运行的速度比较慢,所以在t=10ms时插值分数是0.15,比运行分数0.25小。在图一中插值分数和运行分数是一样的。
当插值分数已经被计算好,ValueAnimator调用合适的TypeEvaluator去计算属性的值,这个计算是基于插值分数,开始值和结束值的。例如,在图二中,t=10ms时插值分数是0.15,所以这个时间属性值就是 0.15 * (40 - 0) ,或者是6。
在API Demo的com.example.android.apis.animation包有使用属性动画的例子。
属性动画和视图动画有什么不同视图动画只提供View对象的动画,所以你想做非View对象的动画,你必须自己实现很多代码。事实上,视图动画系统只公开了部分视图动画功能,比如缩放和旋转一个视图,而没有背景色动画等。
视图动画的另外一个优势是,它只修改绘制视图的位置,而不是视图本身。例如,你让一个按钮在屏幕中移动,按钮是已经被绘制并移动着,但是点击按钮的具体位置并没有改变,所以你需要实现自己的逻辑代码来处理它。
对于属性动画系统,这些限制都没有,你可以改变任何对象的任何属性,对象本身也确实会被改变。属性动画也有很多健壮的方法来实现动画。高级应用中,你可以指定你想改变的任何属性,例如颜色,位置,尺寸,还可以定义动画的特征,如插入动画和同步动画。
对于视图动画,需要很少的代码和时间就能实现。如果视图动画能实现你的功能,那么就使用它就够了,或者你已有的代码已经实现了你想要的功能,那么也不需要使用属性动画。有时候同时使用两种动画系统也是好的选择。
API概述你可以在 android.animation
查看属性动画的所有API。由于视图动画系统已经定义了很多内部程序在 android.view.animation
,你可以很友好的在属性动画中使用这些内部程序。下面的表格描述了一些属性动画系统的主要组件。
Animator类提供创建动画的基础构造。你通常不会直接使用这个类,而是扩展这个类来满足你的需要,下面子类扩展自Animator:
表格一:Animators
ValueAnimator
属性动画的主要定时引擎,也计算动画的属性值。它包含计算动画值的所有核心功能,包含每个动画的时间细节,动画是否重复的信息,监听事件更新,计算自定义属性的值。改变动画属性有两步:计算动画的值和设置这些值到已经改变的对象中。ValueAnimator不实现第二步,那么你需要使用ValueAnimator去监听值的更新,然后修改对象的值。更多信息查看下面的 使用ValueAnimator实现动画 章节。ObjectAnimator
这是一个ValueAnimator的子类,允许你设置一个需要运动的目标对象。当为动画计算好一个新值时,这个类会相应地更新对象的属性值。你会经常使用这个类,因为它能很简单的处理动画过程中的值。不过,有时你还是要直接使用ValueAnimator,因为ObjectAnimator有很多限制,比如,需要在目标对象中指定特别的acessor方法。AnimatorSet
提供动画组的机制,以便运行一组相关的动画。你可以多个动画一起进行,有序的进行,或者指定一个延时播放。更多信息查看下面的 使用动画集编排多个动画 章节。IntEvaluator
计算int属性值的默认求值程序。FloatEvaluator
计算float属性值的默认求值程序。ArgbEvaluator
计算十六进制颜色值的默认求值程序。TypeEvaluator
一个允许创建自己求值程序的接口。如果你的动画属性不是int,float,或者颜色,你必须实现TypeEvaluator接口,你也可以自定义一个计算int,float和颜色值的TypeEvaluator,如果你想实现与众不同的动画行为。使用一个TypeEvaluator 章节会教你怎么写一个自定义的求值程序。AccelerateDecelerateInterpolator
开始和结束时比较慢,中间加速通过的插入器。AccelerateInterpolator
开始比较慢,然后逐渐加速的插入器。AnticipateInterpolator
先向后运动,然后迅速向前的插入器。AnticipateOvershootInterpolator
先向后运动,然后迅速向前的超过目标值,最后返回一个指定的值。BounceInterpolator
在运动的后面产生弹跳的效果。CycleInterpolator
循环运行特定次数。DecelerateInterpolator
开始很快,然后逐渐减速。.LinearInterpolator
匀速运动。OvershootInterpolator
抛出,超过设定的值,然后返回。TimeInterpolator
可以实现自己的插入器的接口。ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);animation.setDuration(1000);animation.start();在上面的代码中,当start()方法执行时,ValueAnimator开始计数动画的值,这个值在0到1之间,持续时间是1000ms。
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);animation.setDuration(1000);animation.start();在上面的代码中,区间值是startPropertyValue到endPropertyValue之间,在MyTypeEvaluator中定义计算支持的逻辑。
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);anim.setDuration(1000);anim.start();要正确更新ObjectAnimator的属性值,你必须做下面这些事:属性值必须有一个设置函数(驼峰式写法):set<propertyName>。因为ObjectAnimator要自动更新属性值需要使用这个方法。例如,属性名是foo,那么需要有setFoo()方法。如果这个设置函数不存在,你有三个选择:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)根据你动画的属性或者对象,你可能需要在View中调用invalidate()来重画更新后的值。你可以在onAnimationUpdate()回调函数中实现。例如,要改变一个绘图对象的颜色,在重画的时候才会更新到屏幕上。view中的所有属性设置,比如setAlpha()和setTranslationX()都会正确的作废View,所以当你调用这个方法设置新值的时候,你不需要再作废View。更多信息查看下面的 动画监听 章节。使用动画集编排多个动画很多情况下,你希望一个动画开始或者结束的同时播放另外一个动画。Android系统让你绑定多个动画到AnimatorSet中,以便你定义同时启动动画,还是按照顺序启动动画,或者延时播放动画。
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();完整的代码可以查看APIDemos。
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());}
<LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="match_parent" android:id="@+id/verticalContainer" android:animateLayoutChanges="true" />设置这个属性为true以后,在ViewGroup中添加和删除View时就会自动带有动画效果了。
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运行时,它会计算一个正确的运行分数(0-1之间),然后根据你使用的插入器计算出一个插值分数,这个插值分数就是TypeEvaluator中的fraction参数,所以当你计算值的时候不需要考虑插入器。
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中这两个插入器计算后的近似值:
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);完整的使用关键帧的例子,可以看APIDemo中的MultiPropertyAnimation例子。
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
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);
<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();更多XML定义属性动画的内容,查看: Animation Resources。