PropertyAnimation
属性动画系统是一个健壮的框架,它可以允许你给任何东西加上动画.你可以定义一个随时间推移改变任何对象的属性的动画,而不用去管这个对象是不是显示在屏幕上.属性动画可以在一段指定的时间内改变一个对象的属性.你只需要指定你想加动画的属性即可,比如一个View在屏幕上的位置.和动画的时间,以及属性变化的区间.
属性动画让你可以定义下列动画的属性:
1. 持续时间:动画的持续时间,默认为300毫秒
2. 时间插值:用一个函数指定相对于动画已经运行的时间而言,属性的值应该是多少
3. 重复次数和行为:指定是否需要重复播放动画,以及重复次数.也能指定动画是否要反向播放.将他设置为反向且重复后,它会来回反复播放动画到对应次数.
4. 动画集合:用一个set集合把几个动画封装成一组,让他们同时播放,或者在一个指定的延迟后依次播放
5. 刷新率:指定动画的刷新率.默认是每10毫秒刷新一次,但是最终的刷新率是由CPU的性能和空闲决定的.
How Property Animation Works
首先,让我们用一个小例子来回顾下一个动画是如何工作的.下图描绘了一个物体正在执行和它的X属性相关的动画,X属性代表了它在屏幕上的横向位置.动画的持续时间被设置为40毫秒,动画的距离为40像素.使用默认的10ms的刷新率.在40ms后,动画结束.物体在横向坐标40处.这是一个线性插值动画例子,物体匀速运动.
你也能给动画指定一个非线性的插值.下图展示了一个物体开始时加速,运动结束时减速的例子.这个物体还是在40ms内运动40像素的距离,不过这次不是线性运动.开始时,动画加速到中点,然后从中点减速到动画结束.如下图所示,中点的速度比起始的速度都快.
现在来仔细看看属性动画系统有哪些计算动画的关键组件,图三描绘了最主要的类是如何和其他类一起工作的.
ValueAnimator对象追踪着动画运行的时间,和动画中的对象的属性.
ValueAnimator封装了一个定义了动画插值的TimeInterpolator对象,以及一个TypeEvaluator对象,定义了正在被动画的对象属性的计算方式.例如图2中, TimeInterpolator对象可能就是一个AccelerateTimeInterpolator对象, TypeEvaluator是一个IntEvaluator对象.
那么怎么样去开启一个动画呢?创建一个ValueAnimator并且设置起始的属性值,以及动画的持续时间.当你调用start()方法时,动画开始.在动画执行过程中, ValueAnimator会基于动画持续时间和动画运行时间计算一个在0到1之间的值, 叫做Elapsed fraction.这个值代表着动画完成的比例.0代表着0%,1代表100%.例如在图一中,t=10的时候, Elapsed fraction的值是0.25,因为总时间是40ms.
当ValueAnimator计算完成Elapsed fraction以后,他会调用当前设置的TimeInterpolator去计算一个Interpolated fraction.一个插值函数将一个现有的Elapsed fraction映射到一个考虑了当前设置的Interpolated fraction的新的Elapsed fraction上.例如,图2中,15ms时,因为动画缓慢加速,所以Interpolated fraction大约是15,比Elapsed fraction的25少.而在图一中, Interpolatedfraction总是和Elapsed fraction相同.
当Interpolated fraction被计算完成,ValueAnimator调用相应的TypeEvaluator来计算正在动画的实际值,这个值得计算基于Interpolated fraction和属性的起始值.例如图二中, Interpolated fraction在15ms的值是15,所以这个属性就是0.15*(40-0)=6.
APIdemo中的com.example.android.apis.animation 包中有许多使用属性动画的例子.
How Property Animation Differs from View Animation
视图动画系统只能给一个View做动画,所以如果你想去给一个不是View的对象做动画,你只有自己实现代码.视图动画系统同样也被它那几个少的可怜的属性所限制,例如旋转和缩放,但是像改颜色的之类就没了.
另一个视图动画的缺陷就是视图动画系统仅仅改变了View绘制的方式,而不是真正改变了View的属性.如果你用动画把一个按钮移动到屏幕的另一边,按钮原来所在的位置还是会响应点击事件.所以你要自己再写代码解决这个问题.
而在属性动画系统中,这些缺陷都没有了.你能给任何对象的任何属性做动画,并且对象的属性是真实被修改了.属性动画系统在它执行动画上来说也更加健壮.你给你想改变的属性指定animator例如颜色,位置和尺寸.并且能定义动画的多个方面,例如插值,以及动画同步.
但是不管如何,视图动画的执行效率和代码量还是优于属性动画的.如何你使用视图动画已经满足你的需求,那么就没必要一定要用属性动画.有些情况下同时使用视图动画和属性动画也是可以的.
API Overview
在android.animation.包中有许多属性动画的API.因为视图动画已经在android.view.animation定义好了许多常用的插值器,你也能在属性动画中使用这些插值器.以下表格描述属性动画系统的主要组件.
Animator类提供了一个创建动画的基础结构,但你通常不需要直接使用这个类,因为它只提供了必须被继承以扩展的最小功能.下列子类继承自Animator:
Table 1. Animators
Class | Description |
一个最主要的时间引擎,它计算每个帧的属性值.并且包含了动画的所有核心功能,包括每个动画的时间细节,是否重复,动画事件的监听,按属性值的设置自定义规则.一个动画需要完成两大块内容,计算属性的值,并为对象设置属性值,ValueAnimator不负责第二块内容.所以你必须监听ValueAnimator计算出来的值并且修改你要做动画的对象的值. | |
ValueAnimator的子类,允许你设置一个目标对象和相应的属性去做动画.当它每计算一个属性值时,他就会更新动画的一个属性.ObjectAnimator更加常用,因为这样用起来更加简单.但是有的时候还是需要ValueAnimator.因为ObjectAnimator有一些限制,比如说对应的属性必须提供对应的getter和setter方法.(译者注:这里应该是使用了反射技术) | |
提供一种将动画分组的机制让你能将有关联关系的动画打包,可以让动画同时执行,也能依次执行,或者以指定的延迟来执行. |
Evaluators告诉系统怎么计算属性值.它持有系统提供的时间数据,动画开始和结束的值,并且依据这些计算属性的值.下列是系统提供的evaluators
Class/Interface | Description |
The default evaluator to calculate values for int properties. | |
The default evaluator to calculate values for float properties. | |
默认的evaluator,用来计算16进制表示的颜色值 | |
一个evaluator的接口,让你可以自定义你的计算方式,如果你要改变的值不是int float color的话 |
一个时间插值器用一个时间函数定义了指定的动画的值如何计算.例如你能指定动画从头到尾线性执行,也就是物体做匀速运动.或者指定一个非线性的插值器,例如开始加速,结尾减速.表三描述了中的插值器.如果现有的插值器不能满足你的需求,你也可以实现TimeInterpolator 接口来自定义一个插值器.
le 3. Interpolators
Class/Interface | Description |
两头慢,中间快 | |
匀加速 | |
刚开始反向走一点,然后加速向前(译者注:看起来就像一个刚开始蓄力然后加速运行的过程) | |
刚开始反向走一点,然后加速向前,会冲过终点线,然后返回 | |
撞到终点会反弹 | |
重复多次的 | |
减速 | |
匀速 | |
超过终点反弹回来 | |
让你自己实现的一个插值器接口 |
Animating with ValueAnimator
ValueAnimator 类让你能用一个int,float等的集合对对象的属性值做动画.使用工厂方法ofInt(), ofFloat(),or ofObject()可以得到ValueAnimator的一个实例.例如
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
上面这段代码中,当start()方法执行时ValueAnimator在1000ms的时间内让一个浮点数从0变到了1.
你也能指定一个自定义类型做动画,如下.
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
上面这段代码中,ValueAnimator在 startPropertyValue和endPropertyValue 之间使用MyTypeEvaluator 提供的逻辑计算了一个持续1000ms的动画.
但是上述代码段没有对任何对象造成影响,因为它还没有和某个对象关联起来.给ValueAnimator定义一个监听器处理动画运行时的一些事件,比如说帧刷新.当你实现了监听器,你能通过调用getAnimatedValue()方法来获得相应帧刷新的值.
Animating with ObjectAnimator
ObjectAnimator是ValueAnimator 的子类,并且结合了ValueAnimator的能力和关联对象的能力.这使得你给某个对象做动画就变得简单的多了,因为你不必再去实现ValueAnimator.AnimatorUpdateListener,,因为这个动画属性会自动更新.
ObjectAnimator初始化和ValueAnimator类似.但是你要指定一个对象并且用一个字符串去指定你要改变的对象的属性以及动画的起始值.
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
为了使ObjectAnimator 正确的自动更新属性,你需要这么做.
A:你想做动画的对象的属性必须有个set<propertyName>()形式的setter方法.因为这里使用了反射的方法.比如说,如果属性名是foo,你需要提供一个setFoo()的方法.如果这个setter方法不存在.你有三个选择:
1. 如果你有权利,那么就去添加这样的set方法.
2. 使用一个包装类将其包装,给包装类设置set方法,然后把相应的数值在包装类里给原始类的对象送进去.
3. 使用ValueAnimator替代
B:如果你只给 ObjectAnimator的工厂方法的values...参数指定了一个值,那么这个值就是对象的结束值.所以你就要提供一个get方法让取得初始值.get方法的形式和上面的set方法类似.
C:getter和setter方法必须操作的是和你给 ObjectAnimator指定的参数类型相同的变量.
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
D:根据你所做动画的类型,你可能需要调用view的invalidate()方法来强制视图刷新新的值.你可以在onAnimationUpdate()回调中来做这件事.例如给一个可绘制的对象的颜色属性做动画只有在视图重绘时屏幕才会更新.View的所有属性的setter方法都会正确的重绘视图,例如 setAlpha()和setTranslationX() .所以当你调用View的相应方法时就不用刷新了.
Choreographing Multiple Animations withAnimatorSet
很多情况下,一个动画的依赖着其他动画的开始或结束.Android系统允许你用一个AnimatorSet将动画打包.这样你就可以同步,依次,或延迟之后执行动画了.你也可以嵌套AnimatorSet 里的每个其他对象.
请看下面这个来自 Bouncing Balls示范代码的例子.
1. Plays bounceAnim.
2. Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.
3. Plays bounceBackAnim.
4. Plays 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 Listeners
你能使用下列监听器监听一些重要的事件.
o onAnimationStart() – 动画开始时调用
o onAnimationEnd() – 动画结束时调用
o onAnimationRepeat() – 动画重新播放时调用
o onAnimationCancel() – 动画被取消调用,动画被取消时也会调用onAnimationEnd() 方法
· ValueAnimator.AnimatorUpdateListener
o onAnimationUpdate() – 动画的每一帧调用.在这个方法中,你可以调用方法获得当前计算的值.如果你使用ValueAnimator,那么实现这个方法是非常重要的.值得注意的是,你可能需要调用 invalidate()方法刷新界面.
如果你不想重写Animator.AnimatorListener 接口的所有方法,你可以继承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
属性动画系统给VIewGroup提供了一个像View一样的简单的动画实现方式.
你可以用一个LayoutTransition 类去给布局做动画.在VIewGroup里的View对象在你添加移除或者设置可见度时会经历一个动画.剩余的View也能以动画的方式移动到它们的新位置.你能调用setAnimator() 方法给LayoutTransition 定义一个动画,方法中传入动画对象和下列常量参数.
· APPEARING – 标记着view出现时的动画.
· CHANGE_APPEARING -标记着viewgroup中有新的view加入时改变布局的动画.
· DISAPPEARING -标记着view消失时的动画.
· CHANGE_DISAPPEARING –布局中有View消失而改变布局的动画
如果要使用系统默认的布局动画,给 android:animateLayoutchanges 设置为true即可
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
这样就可以自动播放布局动画了
Using a TypeEvaluator
如果你想使用一个对Android系统来说陌生的类型,你可以实现TypeEvaluator 接口来创建一个自己的Evaluator.安卓系统知道的只有int和float以及color,相应的Evaluator有IntEvaluator, FloatEvaluator, 和ArgbEvaluator
TypeEvaluate接口只有一个evaluate()方法.用这个在当前动画的点返回一个合适的值.FloatEvaluate类说明了做法.
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运行时,它计算了当前的elapsed fraction并且计算了一个插值后的版本.这个interpolated fraction是你从fraction参数中获得的,所以你计算属性值的时候不必考虑插值.
Using Interpolators
插值器用一个时间函数定义了一个指定的值在动画过程中如何被计算.
插值器从animator中获得一个fraction,这个fraction代表着动画的elapsed time.插值器修改了fraction以覆盖相应的动画.系统提供的插值器代码如下
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;
}
下面这个表格展示了插值的近似值
ms elapsed | Elapsed 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 |
正如表格所显示的,线性插值器匀速改变值,加减速插值器在200-600比线性快.600-1000ms内比线性慢.
Specifying Keyframes
关键帧对象由一组时间/值组成让你指定某个特定时间的特定状态.每个关键帧也能持有它自己的插值器来控制上一个关键帧到当前关键帧的值变化.
必须使用一个工厂方法才能初始化一个关键帧对象.你之后可以调用ofKeyFrame()工厂方法来获取aPropertyValuesHolder 对象.一旦你得到了这个对象,你能把它和需要执行动画的对象一同传入,这样你就可以获得一个ObjectAnimator对象.具体看代码:
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);
Animating Views
属性动画系统提供了流线型的动画方式,并且比视图动画更加有优势.视图动画的变化只是改变了View的绘制方式,这是在每个View的容器中处理的,因为View没有可以直接操作的属性.这回导致View执行了动画,但是实际上View本身没有任何变化.Android3.0之后,增加了一些新的属性以及相应的方法来消除这个缺陷.
属性动画系统能通过改变真实属性的方式给View做动画.除此以外,当属性改变时View也能自动调用invalidate()方法刷新屏幕.这些新添加的属性是:
· translationX and translationY: 这个属性控制了View的位置以相对于父控件的上和左坐标的方式来表示.
· rotation, rotationX, and rotationY: 这些属性控制了View围绕旋转轴所做的2D和3D旋转
· scaleX and scaleY: 这个属性控制了围绕枢纽点的2D缩放.
· pivotX and pivotY: 这两个属性控制了发生缩放和旋转所对应的枢纽点的位置.默认为View的中心点.
· x and y: 这是简单又直接的属性,描述着translation坐标和父容器提供的坐标的和,直接决定了View的最终位置.
· alpha: 透明度.默认1(不透明),而0值代表着不可见.
Animating with ViewPropertyAnimator
ViewPropertyAnimator 提供了一种用底层Animator 对象同时给几个属性做动画的方式.看起来很像ObjectAnimator因为它修改View属性的真实值,但是在几个属性同时动画时,比ObjectAnimator更有效率.除此之外,使用ViewPropertyAnimator 的代码更简洁,更易于阅读.下列代码片段比较了使用几种动画同时改变XY方式的不同.
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);
Declaring Animations in XML
属性动画系统让你能用XML文件取代硬编码的方式来定义动画.使用XML文件定义动画使得你的动画可以重用,并且更改也会很方便.
为了和之前的动画系统区分,你需要把属性动画放到res/animator/目录下.使用animator 目录是无所谓的.但是如果你想使用ADT的布局编辑工具的话,你就必须这么做了.因为ADT只会搜索这个目录.
下列属性动画有相应的XML标记可以使用.
· ValueAnimator - <animator>
· ObjectAnimator - <objectAnimator>
· AnimatorSet - <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成一个对象,然后给所有动画设置目标对象.调用setTarget()方法给所有动画设置一个共同目标对象.如下代码所示:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();