Android提供了两种动画系统:属性动画(property animation)和View动画。这两个动画系统都是可用的,不过大部分情况下可能我们会选择使用属性动画,因为属性动画更加地灵活,同时功能也更加强大。除了这两种动画系统外,还有一种Drawable Animation(逐帧动画),它允许你加载一个Drawable资源,并且一帧帧地展示其中的Drawable。
属性动画是在Android 3.0(API level 11)时被提出来的,属性动画系统允许你对任何对象的属性进行动画操作,包括那些没有渲染到屏幕上的属性。属性动画可以在你指定的一段时间内去改变对象的属性。为了对某个属性进行动画,你需要指定具体的属性名称,比如对象在屏幕上的位置,以及动画执行的时间和动画前后的属性对应的值。
属性动画允许你指定如下的一些动画相关的值:
【1】Duration(动画持续时间):你可以指定动画的持续时间。默认值为300ms。
【2】Time Interpolation(时间插值):你可以指定在动画运行的时间段内属性值如何被计算。
【3】Repeat count and behavior(动画重复次数和行为):你可以指定在动画结束后,是否重复,以及重复执行多次。另外你也可以指定是否希望动画反向执行回来,如果设置反向执行动画,那么就会先向前执行动画,然后反向执行回来,知道重复次数执行完毕。
【4】Animator sets(属性动画集合):你可以让一组属性动画一起执行或者按顺序执行,或在指定的延时后执行。
【5】Frame refresh delay(刷新帧的间隔时间):你可以指定多长时间刷新一次动画的帧。默认值为10ms刷新一次,但是在应用运行时,这个刷新速度最终是由系统的繁忙程度以及系统对底层计时器的服务速度来决定的。
1. 属性动画是怎么工作的
首先,我们来看一个属性动画的简单例子。下图描述了一个假想对象,并对这个对象的x属性进行动画,即它在屏幕水平方向上的位置。动画的持续时间为40ms,整个动画执行的距离为40像素。然后每10ms刷新一次帧,每次对象都移动10个像素。在40ms结束后,动画停止,对象将会停在40像素的位置上。这是一个线性插值的例子,也就意味着这个对象在以一个恒定的速率运动。
当然,你也可以指定动画为一个非线性的插值。下图中描述了一个对象在动画开始时进行加速,然后在动画结束时进行减速的过程。这个对象仍然是在40ms内移动了40个像素距离,但是它是非线性的。在动画开始的时候,它会一直加入到中间位置,然后在后半段中减速运动。如下图所示,这个对象在动画的开始和结束时,运动的距离是小于中间时间段的距离的。
下面我们来详细看一下属性动画的重要组件是如何计算像上面两种图片中的动画的。下图描述了重要类之间是怎么相互工作的:
其中ValueAnimator对象保存了动画运行的状态信息,比如动画已经执行多长时间了,当前被动画的属性的取值之类的。另外,ValueAnimator对象中还封装有一个TimeInterpolator和一个TypeEvaluator,TimeInterpolator定义了动画的插值,TypeEvaluator则定义了被动画的属性值怎么计算。比如,在第二个图中,TimeInterpolator就是AccelerateDecelerateInterpolator,而TypeEvaluator则是IntEvaluator。
为了开始动画,需要创建一个ValueAnimator对象,并且给它设置好开始的属性值和结束的属性值。你可以调用start()方法开始动画,在整个动画的过程中,ValueAnimator会基于动画的持续时间和当前已经过去的时间计算出一个0-1之间的分数,这个分数代表着动画执行的完成度百分比,0即0%,1即100%,比如在第一个图中,在t = 10ms的时候,这个分数的值为0.25,因为总时间为40ms。
当ValueAnimator计算完当前的动画完成度后,它会调用当前设置的TimeInterpolator来计算一个插补分数,这个插补分数将动画完成度分数通过考虑当前设置的时间插补器后映射到一个新的分数中,比如说,在第二个图中,因为动画在t = 10ms时是缓慢加速过程,这个插值分数是15%(6 / 40),比动画完成度分数25%(当前过去时间 / 总持续时间)要低。而在图一中,插值分数和完成度分数是完全一致的。
当这个插值分数计算完成后,ValueAnimator就会调用合适的TypeEvaluator,根据插值分数,开始值和结束值来计算当前的属性的取值。比如,在第二个图中,在t = 10ms时,插值分数为0.15,所以x属性的取值应该为 0.15 * ( 40 - 0) = 6。
总结工作流程如下:
【1】创建ValueAnimator,初始化持续时间,开始属性值,结束属性值。
【2】在动画开始后,ValueAnimator根据当前过去时间和动画持续时间计算出完成度分数。
【3】ValueAnimator调用TimeInterpolator,根据完成度分数计算一个插值分数,姑且称它为时间插值分数。
【4】根据这个插值分数计算出当前具体的属性取值。
2. 属性动画和View动画有什么不同
View动画系统仅仅只能对View对象进行动画,所以,如果你希望对一个非View对象进行动画操作,你必须自己去实现。而且View动画只能对View一些有限的方面进行动画操作,比如View的旋转或缩放等,而不能对View的背景进行动画操作。
另外一个View动画系统的缺点就是动画只会改变View对象视觉上的绘制,而不会改变它本身的属性。比如说,你对一个按钮进行了平移动画,在动画完成后,你仍然能在按钮之前的位置上点击到之前的按钮,而不能去点击按钮动画后的绘制位置。
而属性动画则没有这些约束,你可以对任何对象的任何属性进行动画操作,且动画会实实在在改变对象本身的真实属性。而且属性动画在执行动画这方面比View动画要更加强大。从更高层次来说,你可以指定任何你想进行动画的属性,如颜色,位置或大小等,另外你还可以指定动画的其它方面,如插值,多个动画的同步等。
当然,View动画的实现比属性动画更简单,需要编写的代码也更少,如果View动画能够完成你的需求,那么自然没必要去用属性动画。有时候甚至需要同时使用View动画和属性动画。
3. 属性动画相关的API
你可以在android.animation中找到大部分的属性动画的API。因为View动画系统已经在android.view.animation中定义了很多的插补器,这些插补器同样可以用于属性动画中。
Animator类是所有属性动画的基类。它的子类有如下一些:
【1】ValueAnimator :ValueAnimator是Animator类的直接子类。ValueAnimator是根据时序去进行计算属性取值的属性动画。它具有属性动画的所有核心功能,如计算属性值,包含每个动画的时序细节,是否需要重复动画的信息,接收更新事件的监听器,以及设置评估计算自定义类型的能力等。属性动画可以主要分为两大块:计算动画的属性值和设置这个属性值到执行动画的属性上。所以你必须监听ValueAnimator计算的属性值的更新,然后根据自己的逻辑去修改你的动画对象。所有的属性动画都使用了一个单一的时序脉冲,它运行在一个自定义的Handler中,以确保属性的改变发生在UI线程中。默认情况下,ValueAnimator使用了一个非线性的插补器,即AccelerateDecelerateInterpolator加速进入,减速退出插补器。它可以通过调用setInterpolator(TimeInterpolator)方法去改变。
【2】AnimatorSet :AnimatorSet是Animator类的直接子类。它提供一个让一组属性动画一起或按一定顺序执行的机制。你可以指定这些动画一起运行,按顺序运行,或在一定延时时间后执行。
【3】ObjectAnimator :ObjectAnimator是ValueAnimator类的直接子类。它允许你设置一个目标对象和目标对象的属性进行动画。它会在动画计算出一个新的属性值的时候立马去更新这个属性的取值。很多时候,你可能会使用ObjectAnimator,因为它相对而言会比较简单,但是,有时候我们也会直接使用ValueAnimator类,因为ObjectAnimator相对于ValueAnimator有更多的限制和要求,比如ObjectAnimator要求我们的动画对象中必须提供动画属性的setter方法。
Evaluator(评估计算器)是用来告诉属性动画系统怎么计算属性取值的。它们根据Animator类提供的时序数据,动画的开始属性值和结束属性值,然后计算出动画的属性取值。属性动画提供了下面一些Evaluator:
【1】IntEvaluator:用来计算int类型属性取值的评估器。
【2】FloatEvaluator:用来计算float类型属性取值的评估器。
【3】ArgbEvaluator:用来计算十六进制颜色类型属性取值的评估器。
【4】TypeEvaluator:所有的评估器都需要实现此接口,通过这个接口,你也可以创建自己的评估器。如果你要进行动画的属性取值类型不是int,float或color类型,那么你就必须去实现TypeEvaluator接口,指定怎么计算动画的属性取值。当然,你也可以创建一个int,float或color类型的自定义评估器,来实现和默认行为不一样的评估器。
Time interpolator(时间插补器)相当于是提供了一个用来计算动画值的时间函数。比如说,你可以指定整个动画过程都是线性进行,这也就意味着动画是均匀变化的。你也可以执行动画使用一个非线性的时序,在开始时加速,在结束时减速。由于之前在学习从零学Android(七)、Android资源类型之动画资源时已经学习过相关的插补器,就不再记录了。
4. 使用ValueAnimator进行动画
ValueAnimator可以通过你指定的一系列的int,float或color类型的值和动画持续时间去进行动画。虽然你可以通过new的方式去创建一个ValueAnimator对象,但是这种方法一般是在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();
这个代码中,在调用了
tart()方法之后, ValueAnimator会使用
MyTypeEvaluator提供的逻辑计算在1000ms内,在
startPropertyValue-
endPropertyValue之间进行计算。
5. 使用ObjectAnimator进行动画
ObjectAnimator是ValueAnimator的子类,它继承了ValueAnimator的计算属性值的能力和时序引擎,另外还加入了自己特有的对目标对象的目标属性进行动画操作的能力。这让动画功能变得简单了很多,因为你不再需要去实现ValueAnimator.AnimatorUpdateListener接口来更新属性值了,它会自动更新。
ObjectAnimator的初始化方法和ValueAnimator的类似,但是你需要指定目标动画对象和动画属性的名称,动画的属性取值:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
为了让 ObjectAnimator能正确地更新属性取值,你必须做如下一些事情:
【1】进行动画的属性必须包含有setter方法,格式如:set
·如果你有添加setter方法的权力,添加上setter方法。
·使用你有权力改变的包装类并且给这个包装类提供一个有效的setter方法,这个setter方法指向原来的对象。
·使用ValueAnimator代替
【2】如果在ObjectAnimator初始化工厂方法的values...参数你只指定一个属性取值,它就会被当做动画的结束取值。因此,这种情况下你必须提供动画属性的getter方法,以便获取动画的开始取值。getter方法的形式如get
【3】进行动画的属性的getter方法(如果必要的话)和setter方法的数据类型必须和ObjectAnimator中指定的开始和结束取值的数据类型一致。比如,如果你使用如下的一个ObjectAnimator的工厂方法的话,那么你必须有格式为targetObject.setPropName(float)和targetObject.getPropName(float)的getter和setter方法:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
【4】视进行动画的属性而定,当属性动画取值更新时,你可能需要调用 invalidate()方法来强制屏幕重绘View。你可以在 onAnimationUpdate()回调方法中执行这个操作。比如说,你对一个Drawable的颜色属性进行动画,那么只有你重绘View的时候,屏幕才会刷新。View的所有属性setter方法,如 setAlpha()和 setTranslationX()都会导致View的重绘,所以当动画更新取值时,你不用手动去调用刷新方法。
6. 使用AnimatorSet编排多个动画
在许多情况下,你需要在一个动画开始或者结束时,开始另外一个动画,Android系统允许你将这些动画一起打包放入一个AnimatorSet中,你可以指定这些动画一起执行,按顺序执行,或在一定的延时时间后执行。同样的,你也可以在AnimatorSet中嵌套另外一个AnimatorSet。
7.动画监听器
你可以使用下列动画监听器来监听到动画过程中的一些重要事件:
【1】Animator.AnimatorListener
·onAnimationStart() : 这个回调方法在动画开始时被调用。
·onAnimationEnd() :这个回调方法在动画结束时被调用。
·onAnimationRepeat() :这个回调方法在动画重复时被调用。
·onAnimationCancel() :这个回调方法在动画被取消时被调用。在这个方法被调用后,不管动画是怎么结束的,它都会回调onAnimationEnd() 方法。
【2】ValueAnimator.AnimatorUpdateListener
·onAnimationUpdate() :动画的每一帧在刷新时都会回调该方法。你可以通过在这个回调方法中调用getAnimatedValue()方法来使用由ValueAnimator在动画时更新产生的属性取值。如果你使用ValueAnimator来进行动画效果,那么你就必须实现该接口。
如果你不希望实现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());
}
8. 对ViewGroup进行布局动画
属性动画系统可以对ViewGroup对象进行动画(当然对View也可以)。你可以通过使用LayoutTransition类去对ViewGroup的layout布局进行动画操作。你可以通过调用View的setVisibility()方法来控制ViewGroup中View的可见性,那么ViewGroup中剩下的View在你添加或者移除View的时候,它们就能动画到它们新的位置上。你可以在一个LayoutTransition对象上通过调用setAnimator()来绑定一个Animator对象到下面LayoutTransition常量中的某个常量上:
·APPEARING
:和动画绑定的Item正在容器中出现。
·CHANGE_APPEARING
:和动画绑定的Item因为容器中其它的item出现而正在发生改变。
·DISAPPEARING
:和动画绑定的Item正在从容器中消失。
·CHANGE_DISAPPEARING
:和动画绑定的Item因为容器中其它的item消失而正在发生改变。
你可以给这四种情况定义自己的动画来自定义布局过渡的外观,也可以使用默认的动画。你需要做的仅仅就是将ViewGroup的android:animateLayoutchanges属性取值为true:
9. TypeEvaluator的使用
如果你需要多一个Android系统没有内置支持的类型进行动画,那么你需要通过实现TypeEvaluator接口来创建一个你自己的评估器。Android系统现在支持的类型有三种,分别为:int,float和color,对应评估器类分别为IntEvaluator,FloatEvaluator和ArgbEvaluator。
TypeEvaluator接口只有一个evaluate()方法需要实现。这个方法允许你使用的动画在当前的时间点上返回一个合适的取值。比如FloatEvaluator类的实现如下:
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
注意:当 ValueAnimator或 bjectAnimator在动画的时候,它会计算一个当前动画的完成度分数,然后根据你设置的插补器和当前的完成度分数再次计算出一个插补器分数,而这个插补器分数就是你的 TypeEvaluator的 evaluate()方法中的参数
fraction,所以这里的计算,你无需再考虑插补器的影响。
10. InterPolator的使用
插补器InterPolator相当于一个用来计算动画属性的时间函数。插补器会从动画系统中收到一个动画完成度分数,然后插补器会根据动画的类型去修改这个分数。Android系统在
android.view.animation package包下提供了很多常用的插补器,如果都不使用,你可以通过实现TimeInterpolator接口,创建一个属于你自己的插补器。我们先来看看系统两个常用的插补器的实现:
LinearInterpolator
public float getInterpolation(float input) {
return input;
}
AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
不难看出,线性插补器 LinearInterpolator没有去改变动画完成度分数,而非线性插补器 AccelerateDecelerateInterpolator则去改变了这个分数的取值。
11. 指定关键帧Keyframes
一个Keyframe对象是一个时间/取值对,它允许你指定一个动画的特定时间点的特定状态。每一个关键帧都可以有自己的插补器,来控制上一个关键帧到当前关键帧这段时间内的动画行为。
你可以使用Keyframe的几个工厂方法ofInt(),ofFloat()或ofObject()来初始化一个Keyframe对象。然后调用ofKeyframe()方法获取一个PropertyValuesHolder对象,一旦你有了这个PropertyValuesHolder对象,你就可以通过这个对象获取到一个动画对象,并且将PropertyValuesHolder对象中的Keyframe应用到这个动画对象上:
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);
12. 使用ViewPropertyAnimator进行动画
ViewPropertyAnimator提供了一个简单方式去对View的一些属性进行动画,它本身并不是属性动画,而是它使用了一个底层的Animator对象。它和ObjectAnimator比较像,因为它也是实实在在修改了View的属性,但是当同时改变View的多个属性的时候,ViewPropertyAnimator要更加的高效。另外,使用ViewPropertyAnimator的代码也更加简洁和易理解。下面的代码对比了使用多个ObjectAnimator,一个ObjectAnimator和一个ObjectAnimator来同时对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();
一个ObjectAnimator:
myView.animate().x(50f).y(100f);