Android动画框架(二)----属性动画

转载请注明出处:http://blog.csdn.net/fishle123/article/details/50705928

Android提供三种形式动画:视图动画,帧动画,属性动画。其中属性动画的功能最强大,在android 3.0中开始引入。本文介绍属性动画。属性动画可以针对Object的任何一个属性实施动画,并且Object的属性值会随着动画改变,这一点与视图动画不同。属性动画功能非常强大,几乎可以实现任何动画效果。本文首先介绍属性动画的工作原理,然后全面的介绍了属性动画的使用技巧,为了更好的使用属性动画,还详细的介绍了TimeInterpolator和TypeEvaluatory,以及KeyFrame的使用,最后总结了多种同时对多个属性作用动画的方法。

属性动画工作原理

介绍属性动画的工作原理之前,先看两个例子。下图展示了一个属性动画,它针对Object的横坐标x做动画,动画时间是40ms,横坐标xx=0增加到x=40。从图中可以看出,每个10msAndroid默认的动画更新频率)Object向右水平移动10pixel。在40ms的时候,Object停止在x=40的位置。这个例子中描述的是一个使用线性差值器的例子,Object以一个平均速度移动。

 

当然,通过使用不同的差值器,可以让Object以非线性的速度移动。下图展示的这个动画,Object仍然是在40ms内移动40pixel。但是在开始的时候移动速度不断加快,快结束的时候移动速度逐渐变小。从图中可以看出,Object在中间时刻移动速度比开始和结束的时候都要快一些。

 

属性动画的核心在于计算不同时刻Object的属性值。下图指出了Android属性动画相关的几个核心类之间如何协作完成动画的。

 

其中,ValueAnimator记录了动画的相关信息,如属性的起始值和结束值,以及开始动画后,该属性在当前时刻的值(value),动画播放了多长时间等。

在ValueAnimator中有一个TimeInterpolator参数,即时间差值器,它根据时间流逝的百分比来计算当前属性值改变的百分比。在ValueAnimator中还有一个TypeEvaluator参数,即类型估值器,它根据时间差值器来计算在动画播放过程中某一个特定时间Object的属性值。例如,在上面的图2中TimeInterpolator使用的是 AccelerateDecelerateInterpolator ,ValueEvaluator使用的是IntEvaluator。

设计动画的时候,先使用Object、属性起始值、结束值、动画时间等参数创建一个ValueAnimator,然后调用ValueAnimator的start方法开始播放。在动画期间,ValueAnimator根据流逝的时间计算出一个elapsed fraction流逝分数(0----1之间),这个流逝分数描述了动画已经完成了多少,0表示0%即动画还没开始,1表示100%即动画已经全部完成。例如,在上面的图1中,在t=10ms的时候流逝比例为10ms/40ms=0.25。

ValueAnimator计算完流逝分数之后,调用TimeInterpolator根据流逝分数计算interpolated fraction差值分数。例如,在上面图2中,设置的TimeInterpolator是AccelerateDecelerateInterpolator ,在t=10ms的时候,流逝分数是0.25,但是差值分数是0.15(AccelerateDecelerateInterpolator 在开始的时候缓慢加速)。在上面图1中,因为是匀速移动,差值分数一直等于流逝分数。

计算完差值分数之后,ValueAnimator调用TypeEvaluator根据差值分数、属性起始值、结束值来计算属性的最新值。例如,在上面的图2中,在t=10ms时,差值分数为0.15,所以属性的值为0.15*(40-0)=6.

属性动画与视图动画的差异

1)视图动画的对象仅限于View,而且只是View的几个属性(透明度,旋转,缩放,平移)可以做动画。

2)视图动画仅仅影响View的绘制,并没有真正改变View对应的属性值。如针对Button的平移动画,将Button的中心从A点移动到B点,动画完成后Button在B点显示,但如果要点击Button,还是得点A点区域,而不是点B点区域。

3)属性动画则没有视图动画的这些缺点,你可以对任何Object(Views 或者non-Views)的任何属性(如颜色,位置,大小)。

当然,视图动画使用起来更简单。如果视图动画已经满足了你的动画需求,那也没必要说一定要使用属性动画。

相关API

在Android中Animator是属性动画框架的基础,但是我们并不直接使用Animator来设计属性动画。而是使用Animator的三个子类ValueAnimator、ObjectAnimator、AnimatorSet。另外,还提供了一些TimeInterpolator和ValueEvaluator。

先看看Animator相关的类:

Class

Description

ValueAnimator

它实现了属性动画的所有核心功能。属性动画有两个核心部分:1)计算动画属性的值;2)将计算出来的值赋值给该动画属性。ValueAnimator没有实现第2)点功能,因此需要我们自己监听ValueAnimator.AnimatorUpdateListener

并在onAnimationUpdate(ValueAnimator animation)中手动更新属性值。

ObjectAnimator

它是ValueAnimator的子类。ObjectAnimator不仅实现了动画播放(即上面的核心功能1计算动画属性得值),还会将动画过程中计算的属性值设置到相应的属性上(即上面的核心功能2)。因此,大部分情形使用ObjectAnimator就可以了。当然,有时候也需要使用ValueAnimator,因为ObjectAnimator要求动画操作的属性必须提供相应的getXXX()setXXX()方法。 

AnimatorSet

动画集合,可以操作多个动画之间的播放顺序。

 

估值器Evaluator在属性动画中用来计算动画过程中被操作的属性的值。Android提供了以下Evaluators:

Class/Interface

Description

IntEvaluator

int类型属性的默认估值器

FloatEvaluator

float类型属性的默认估值器

ArgbEvaluator

color类型属性的默认估值器

TypeEvaluator

它是一个interface类型。如果我们要实现自己的Evaluator,则需要实现这个接口。当动画属性的类型不是int,float,color的时候,就需要我们定义自己的Evaluator了。 

 

时间差值器TimeInterpolator用来定义动画过程中被操作的属性值随时间变化的规则。例如,可以指定为LinearInterpolator线性差值器,这样Object的属性值随时间均速变化。当然,也可以指定为非线性变化的差值器,如AccelerateDecelerateInterpolator加速减速差值器,动画开始的时候变化速率逐渐增加,快结束的时候变化速率逐渐减慢,即开始和结束的时候变化慢,中间变化快。Android在android.view.animation包中提供了很多差值器,如果这些差值器不能满足要求,我们还可以定义自己的差值器,只需要实现TimeInterpolator接口就可以了。下面看一下系统提供的差值器:

Class/Interface

Description

AccelerateDecelerateInterpolator

开始和结束的时候变化慢,中间变化快

AccelerateInterpolator

开始后一直加速

AnticipateInterpolator

开始的时候向后然后向前移动

AnticipateOvershootInterpolator

开始的时候向后然后向前越过一定值后返回最后的值

BounceInterpolator

动画结束的时候弹起

CycleInterpolator

循环播放特定的次数,速率改变沿着正弦曲线

DecelerateInterpolator

开始快,逐渐减速

LinearInterpolator

匀速变化

OvershootInterpolator

向前越过一定值后再回到原来位置

TimeInterpolator

定义了时间差值器的接口,通过实现TimeInerpolaor接口来定义自己的差值器

 

ValueAnimator的使用

它实现了属性动画的所有核心功能。属性动画有两个核心部分:1)计算动画属性的值;2)将计算出来的值赋值给该动画属性。ValueAnimator没有实现第2点功能,因此需要我们自己监听ValueAnimator.AnimatorUpdateListener,

并在onAnimationUpdate(ValueAnimator animation)中手动更新属性值。如果不手动实现第2点功能,将看不到任何动画效果。

下面使用ValueAnimator演示属性动画。这个例子里面针对小球的横纵坐标xy做动画,从0pixel500pixel

[java]  view plain  copy
  1. private void doVauleAnimator(){  
  2. ValueAnimator animator = ValueAnimator.ofFloat(0,500);  
  3. animator.setDuration(3000);  
  4. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  5. @Override  
  6. public void onAnimationUpdate(ValueAnimator animation) {  
  7. // TODO Auto-generated method stub  
  8. float value = (Float) animation.getAnimatedValue();  
  9. mBall.setTranslationX(value);  
  10. }  
  11. });  
  12. animator.start();  
  13. }  
  14.    
  15. protected void onCreate(Bundle savedInstanceState) {  
  16. super.onCreate(savedInstanceState);  
  17. setContentView(R.layout.activity_main);  
  18. mBall = (ImageView)findViewById(R.id.imageViewBall);  
  19. mBtnStart = (Button)findViewById(R.id.start);  
  20. mBtnStart.setOnClickListener(new View.OnClickListener() {  
  21. @Override  
  22. public void onClick(View v) {  
  23. // TODO Auto-generated method stub  
  24. doVauleAnimator();  
  25. }  
  26. });  
  27. }  

布局文件activity_main.xml

[html]  view plain  copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.    
  11.     <ImageView  
  12.         android:id="@+id/imageViewBall"  
  13.         android:layout_width="40dp"  
  14.         android:layout_height="40dp"  
  15.          
  16.         android:src="@drawable/red_ball" />  
  17.    
  18.     <Button  
  19.         android:id="@+id/start"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:layout_alignParentBottom="true"  
  23.         android:layout_centerHorizontal="true"  
  24.         android:text="start" />  
  25.    
  26. </RelativeLayout>  

动画效果如下:

 

ObjectAnimator的使用

它是ValueAnimator的子类。ObjectAnimator不仅实现了动画播放(即上面的核心功能1计算动画属性得值),还会将动画过程中计算的属性值设置到相应的属性上(即上面的核心功能2)。因此,大部分情形使用ObjectAnimator就可以了。当然,有时候也需要使用ValueAnimator,因为ObjectAnimator要求动画操作的属性必须提供相应的getXXX()和setXXX()方法。 

同样地,在上面平移的例子中改成旋转,可以使用ObjectAnimator如下实现:

[java]  view plain  copy
  1. private void doObjectAnimator(View target){  
  2. ObjectAnimator animator=ObjectAnimator.ofFloat(target, "rotationX"0,-180,0);  
  3. animator.setDuration(3000);  
  4. animator.start();  
  5. }  

由此可见,ObjectAnimator使用起来比ValueAnimator简单很多。动画效果如下:

 

如果想使用ObjeactAnimator同时对多个属性进行动画,如旋转的同时做平移,同样可以利用ValueAnimator.AnimatorUpdateListener接口来手动更新属性值,因此可以如下实现:

[java]  view plain  copy
  1. private void doTranslateAndRoate(final View target){  
  2. String propName="anything";  
  3. ObjectAnimator animator = ObjectAnimator.ofFloat(target, propName, 0f,1.0f);  
  4. animator.setDuration(2000);  
  5. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  6. @Override  
  7. public void onAnimationUpdate(ValueAnimator animation) {  
  8. // TODO Auto-generated method stub  
  9. float value =(Float)animation.getAnimatedValue();  
  10. //value也可以利用animation.getAnimatedFraction()来计算;  
  11. target.setTranslationX(value*100);  
  12. target.setRotationX(value*360);  
  13. }  
  14. });  
  15. animator.start();  
  16. }  

AnimatorSet的使用

对同一个Object同时作用多个属性动画效果,除了按上面提到的通过监听ValueAnimator.AnimatorUpdateListener来实现外,还可以使用AnimatiorSet。AnimatorSet除了能同时作用多个属性外,还可以实现更为精确的顺序控制。上面使用ObjectAnimator实现的同时作用多个属性的动画效果,使用AnimatorSet可以如下实现:

[java]  view plain  copy
  1. private void doTranslateAndRoatateWithAnimatorSet(View target){  
  2. ObjectAnimator animator0=ObjectAnimator.ofFloat(target, "RotationX",  0,360);  
  3. ObjectAnimator animator1=ObjectAnimator.ofFloat(target, "TranslationX"0,100);  
  4. AnimatorSet set = new AnimatorSet();  
  5. set.playTogether(animator0,animator1);  
  6. set.setDuration(2000);  
  7. set.start();  
  8. }  

使用XML设计属性动画

除了代码的方式,也可以使用XML来设计属性动画。属性动画的xml文件需要保存到res/animator目录下(而不是视图动画的res/anim,当然Google也说了使用animator目录是可选的。但是如果使用的eclipse adt,就必须保存到animator下面。在XML中分别使用<animator><objectAnimator><set>三个标签来定义ValueAnimatorObjectAnimatorAnimatorSet这三种类型的动画。这里给出一个例子:

[html]  view plain  copy
  1. <set xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:ordering="sequentially" >  
  3.    
  4.     <set>  
  5.         <objectAnimator  
  6.             android:duration="500"  
  7.             android:propertyName="x"  
  8.             android:valueTo="400"  
  9.             android:valueType="intType" />  
  10.         <objectAnimator  
  11.             android:duration="500"  
  12.             android:propertyName="y"  
  13.             android:valueTo="300"  
  14.             android:valueType="intType" />  
  15.     </set>  
  16.    
  17.     <objectAnimator  
  18.         android:duration="500"  
  19.         android:propertyName="alpha"  
  20.         android:valueTo="1" />  
  21.    
  22. </set>  

然后在代码中引用这个动画:

[java]  view plain  copy
  1. AnimatorSet set = (AnimatorSet) AnimatorInflater  
  2. .loadAnimator(MainActivity.this, R.animator.prop_anim);  
  3. set.setTarget(target);  
  4. set.start();  

Animation Listeners

Android提供了以下监听器帮助我们来处理一些重要的动画事件:

Animator.AnimatorListener

onAnimationStart() - 动画开始时被调用

onAnimationEnd() - 动画结束时被调用

onAnimationRepeat() -动画重复播放时被调用

onAnimationCancel() - 动画被取消时被调用,接着onAnimationEnd也会被调用

ValueAnimator.AnimatorUpdateListener

onAnimationUpdate() - 动画的每一帧都会调用,通过监听Update事件,我们就可以使用ValueAnimator计算出来的属性值(通过 getAnimatedValue()方法获取),然后实现自己的动画逻辑。

对于AnimatorListener接口,如果不想实现所有方法,可以继承 AnimatorListenerAdapter,仅仅重写我们需要监听的事件方法。

当我们对不同的属性作用动画时,有时候可能需要调用View的invalidate()方法来强制重绘,以便新的属性值生效。例如,对一个Drawable对象的color属性作用属性动画时,在重绘的时候仅仅只是刷新屏幕。而View的所有提供了setter的属性如setAlpha,setTranslationX等会自动调用invalidate,不需要手动调用invalidate。

理解TypeEvaluator

Android提供了IntEvaluator,FloatEvaluator和ArgbEvaluator三种类型的估值器,如果我们想用来做动画的属性不是这三种类型,如color类型,或者某个自定义的类型,那么可以通过实现TypeEvaluator来定义我们自己的TypeEvaluator类型估值器,只需要实现一个evaluate方法就可以了。evaluate方法的参数是TimeInterpolator时间差值器计算出来的差值分数、属性的起始值、结束值,然后根据这个三个计算当前的属性值。

下面看一下FloatEvaluator的源码:

[java]  view plain  copy
  1. public class FloatEvaluator implements TypeEvaluator<Number> {  
  2.    
  3.     /** 
  4.      * This function returns the result of linearly interpolating the start and end values, with 
  5.      * <code>fraction</code> representing the proportion between the start and end values. The 
  6.      * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, 
  7.      * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, 
  8.      * and <code>t</code> is <code>fraction</code>. 
  9.      * 
  10.      * @param fraction   The fraction from the starting to the ending values 
  11.      * @param startValue The start value; should be of type <code>float</code> or 
  12.      *                   <code>Float</code> 
  13.      * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code> 
  14.      * @return A linear interpolation between the start and end values, given the 
  15.      *         <code>fraction</code> parameter. 
  16.      */  
  17.     public Float evaluate(float fraction, Number startValue, Number endValue) {  
  18.         float startFloat = startValue.floatValue();  
  19.         return startFloat + fraction * (endValue.floatValue() - startFloat);  
  20.     }  
  21. }  

因为时间差值器已经在计算差值分数的时候已经把动画的变化效果考虑进去了,所以在计算属性值得时候不需要再考虑差值变化的问题。

理解Interpolators

在动画中,差值器用来定义属性值如何随时间变化。差值器根据Animator计算出来的流逝时间分数来计算出一个差值分数。根据动画效果的不同,相同的流逝时间分数会计算出不同的差值分数。如果Android提供的差值器都不能达到我们的想要的效果,可以实现TimeInterpolator来定义我们自己的差值器。

下面看一下AccelerateDecelerateInterpolator和 LinearInterpolator的源码,看看它们是怎样计算差值分数的:

AccelerateDecelerateInterpolator源码:

[java]  view plain  copy
  1. /** 
  2.  * An interpolator where the rate of change starts and ends slowly but 
  3.  * accelerates through the middle. 
  4.  *  
  5.  */  
  6. public class AccelerateDecelerateInterpolator implements Interpolator {  
  7.     public AccelerateDecelerateInterpolator() {  
  8.     }  
  9.       
  10.     @SuppressWarnings({"UnusedDeclaration"})  
  11.     public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {  
  12.     }  
  13.       
  14.     public float getInterpolation(float input) {  
  15.         return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  
  16.     }  
  17. }  

LinearInterpolator源码:

[java]  view plain  copy
  1. /** 
  2.  * An interpolator where the rate of change is constant 
  3.  * 
  4.  */  
  5. public class LinearInterpolator implements Interpolator {  
  6.    
  7.     public LinearInterpolator() {  
  8.     }  
  9.       
  10.     public LinearInterpolator(Context context, AttributeSet attrs) {  
  11.     }  
  12.       
  13.     public float getInterpolation(float input) {  
  14.         return input;  
  15.     }  
  16. }  

下表给出了使用AccelerateDecelerateInterpolator和 LinearInterpolator计算出来的差值。

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

从上表可以看出,LinearInterpolator计算出来的差值随时间匀速变化,每200ms增加0.2。而AccelerateDecelerateInterpolator在开始的时候缓慢增加,在中间的时候如600ms的时候增加很快(在400ms----600ms之间增加明显比LinearInterpolator快),后面又开始缓慢增加。

Keyframes的使用

可以使用关键帧KeyFrame来指定在特定时刻的动画帧。KeyFrame提供了ofInt()、ofFloat()、ofObject()三个工厂方法来帮助我们初始化一个KeyFrame。每个KeyFrame可以指定自己的TimeInterpolator,这个TimeInterpolator在上一个KeyFrame和当前KeyFrame之间的这段时间的动画上生效。我们可以如下使用KeyFrame:

[java]  view plain  copy
  1. Keyframe kf0 = Keyframe.ofFloat(0f, 0f);//ofFloat(fraction,value)  
  2. Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);  
  3. Keyframe kf2 = Keyframe.ofFloat(1f, 0f);  
  4. PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);  
  5. ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);  
  6. rotationAnim.setDuration(5000);  

同时对多个属性作用属性动画

上面的例子中有使用ValueAnimator.AnimatorUpdateListener可以实现同时对多个属性作用动画,其实Android提供了更简单的办法来同时对多个属性作属性动画。下面简单总结一下:

1)使用AnimatorSet

[java]  view plain  copy
  1. ObjectAnimator animX = ObjectAnimator.ofFloat(target, "x", 50f);  
  2. ObjectAnimator animY = ObjectAnimator.ofFloat(target, "y", 100f);  
  3. AnimatorSet animSetXY = new AnimatorSet();  
  4. animSetXY.playTogether(animX, animY);  
  5. animSetXY.start();  

2)使用PropertyValuesHolder 

[java]  view plain  copy
  1. PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);  
  2. PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);  
  3. ObjectAnimator.ofPropertyValuesHolder(target, pvhX, pvhY).start();  

3)使用ViewPropertyAnimator

[java]  view plain  copy
  1. target.animate().x(50f).y(100f).setDuration(4000);  

View的animate()方法返回一个ViewPropertyAnimator对象的实例。

4)使用ValueAnimator.AnimatorUpdateListener

[java]  view plain  copy
  1. String propName="anything";  
  2. ObjectAnimator animator = ObjectAnimator.ofFloat(target, propName, 0f,1.0f);  
  3. animator.setDuration(2000);  
  4. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  5. @Override  
  6. public void onAnimationUpdate(ValueAnimator animation) {  
  7. // TODO Auto-generated method stub  
  8. float value =(Float)animation.getAnimatedValue();  
  9. //value也可以利用animation.getAnimatedFraction()来计算;  
  10. target.setTranslationX(value*100);  
  11. target.setRotationX(value*360);  
  12. }  
  13. });  
  14. animator.start();  

相比于其他几种方法,AnimatorSet可以更容易的控制多个动画之间的顺序。

总结

到此为止,本文详细的分析android属性动画的工作原理及相关使用技巧,还介绍了布局动画的详细使用。

如果大家对动画感兴趣,可以阅读我的动画相关的其他文章,看完这些文章相信大多数动画相关的问题都能解决了。

Android动画框架(一)----视图动画&帧动画

Android动画框架(三)----布局动画&Activity过渡动画