您的位置:首页 > 移动开发 > Android开发

《疯狂Android讲义》 -- Android 动画系列之属性动画

2016-11-16 18:13 483 查看


前言

从某种角度来看,属性动画是增强版的补间动画,属性动画的强大可以体现在如下两方面。

补间动画只能定义两个关键帧在“透明度”、“旋转”、“缩放”、“位移”4个方面的变化,但属性动画可以定义任何属性的变化。
补间动画只能对UI组件执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。

与补间动画类似的是,属性动画也需要定义如下几个属性。

动画持续时间。该属性的默认值是300ms。在属性动画资源文件中通过android:duration属性指定。
动画插值方式。该属性的作用与补间动画中插值属性的作用基本类似。在属性动画资源文件中通过android:interpolator属性指定。
动画重复次数。指定动画重复播放的次数。在属性动画资源文件中通过android:repeatCount属性指定。
重复行为。指定动画播放结束后、重复下次动画时,是从开始帧再次播放到结束帧,还是从结束帧反向播放到开始帧。在属性动画资源文件中通过android:repeatMode属性指定。
动画集。开发者可以将多个属性动画合并成一组,既可让这组属性动画按次序播放,也可让这组属性动画同时播放。在属性动画资源文件中通过<set.../>元素来组合,该元素的android:ordering属性指定该组动画是按次序播放,还是同时播放。
帧刷新频率。指定每隔多长时间播放一帧。该属性的默认值为10ms。

属性动画的API

属性动画涉及的API如下:

Animator:它提供了创建属性动画的基类。基本上不会直接使用该类。通常该类只用于被继承并重写它的相关方法。
ValueAnimator:属性动画主要的时间引擎,它负责计算各个帧的属性值。它定义了属性动画的绝大部分核心功能,包括计算各帧的相关属性值,负责处理更新事件,按按属性值的类型控制计算规则。属性动画主要由两方面组成:①计算各帧的相关属性值;②为指定对象设置这些计算后的值。ValueAnimator只负责第一方面内容,因此程序员必须根据ValueAnimator计算并监听值更新来更新对象的相关属性值。
ObjectAnimator:它是ValueAnimator的子类,允许程序员对指定对象的属性执行动画。在实际应用中,ObjectAnimator使用起来更加简单,因此更加常用。在少数场景下,由于ObjectAnimator存在一些限制,可能需要考虑使用ValueAnimator。
AnimatorSet:它是Animator的子类,用于组合多个Animator,并指定多个Animator是按次序播放,还是同时播放。

除此之外,属性动画还需要利用一个Evaluator(计算器),该工具类控制属性动画如何计算属性值。Android提供了如下Evaluator。

IntEvaluator:用于计算int类型属性值的计算器。
FloatEvaluator:用于计算float类型属性值的计算器。
ArgbEvaluator:用于计算以十六进制形式表示的颜色值的计算器。
TypeEvaluator:它是计算器接口,开发者可以通过实现该接口来实现自定义计算器。如果需要对int、float或者颜色值以外类型的属性执行属性动画,可能需要实现TypeEvaluator接口来实现自定义计算器。

使用ValueAnimator创建动画

使用ValueAnimator创建动画可按如下4个步骤进行。

调用ValueAnimator的ofInt()、ofFloat()或ofObject()静态方法创建ValueAnimator实例。
调用ValueAnimator的setXxx()方法设置动画持续时间、插值方式、重复次数等。
调用ValueAnimator的start()方法启动动画。
为ValueAnimator注册AnimatorUpdateListener监听器,在该监听器中可以监听ValueAnimator计算出来的值的改变,并将这些值应用到指定对象上。

例如如下代码片段:

ValueAnimatoranimation=ValueAnimator.ofFloat(0f,1f);
animation.setDuration(1000);
animation.start();
[/code]

上面的例子实现了在1000ms内,值从0到1的变化。

除此之外,开发者也可以提供一个自定义的Evaluator计算器,例如如下代码:

ValueAnimatoranimation=ValueAnimator.ofObject(newMyTypeEvaluator(),startVal,endVal);
animation.setDuration(1000);
animation.start();
[/code]

在上面的代码片段中,ValueAnimator仅仅是计算动画过程中变化的值,并没有把这些计算出来的值应用到任何对象上,因此也不会显示任何动画。

如果希望使用ValueAnimator创建动画,还需要注册一个监听器:AnimatorUpdateListener,该监听器负责更新对象的属性值。在实现这个监听器时,可以通过getAnimatedValue()方法来获取当前帧的值,并将该计算出来的值应用到指定对象上。当该对象的属性持续改变时,该对象也就呈现出动画效果了。

使用ObjectAnimator创建动画

ObjectAnimator继承了ValueAnimator,因此它可以直接将ValueAnimator在动画过程中计算出来的值应用到指定对象的指定属性上(ValueAnimator则需要注册一个监听器来完成这个工作)。因此使用ObjectAnimator就不需要注册AnimatorUpdateListener监听器了。

使用ObjectAnimator的ofInt()、ofFloat()或ofObject()静态方法创建ObjectAnimator时,需要指定具体的对象,以及对象的属性名。

例如如下代码片段:

ObjectAnimatoranim=ObjectAnimator.ofFloat(foo,"alpha",0f,1f);
anim.setDuraction(1000);
anim.start();
[/code]

与ValueAnimator不同的是,使用ObjectAnimator有如下几个注意点。

要为该对象对应的属性提供setter方法,如上例中需要为foo对象提供setAlpha(floatvalue)方法。
调用ObjectAnimator的ofInt()、onFloat()或ofObject()工厂方法时,如果values...参数只提供了一个值(本来需要提供开始值和结束值),那么该值会被认为是结束值。该对象应该为该属性提供一个getter方法,该getter方法的返回值将被作为开始值。
如果动画的对象是View,为了能显示动画效果,可能还需要在onAnimationUpdate()事件监听方法中调用View.invalidate()方法来刷新屏幕的显示,比如对Drawable对象的color属性执行动画。但View定义的setter方法,如setAlpha()和setTranslationX()等方法,都会自动地调用invalidate()方法,因此不需要额外地调用invalidate()方法。

使用属性动画

属性动画即可作用于UI组件,也可作用于普通的对象(即使它没有在UI界面上绘制出来)。

定义属性动画有如下两种方式。

使用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画。
使用资源文件来定义动画。

使用属性动画的步骤如下。

创建ValueAnimator或ObjectAnimator对象----即可从XML资源文件加载该动画资源,也可直接调用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画。
根据需要为Animator对象设置属性。
如果需要监听Animator的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为Animator对象设置事件监听器。
如果有多个动画需要按次序或同时播放,则应使用AnimatorSet组合这些动画。
调用Animator对象的start()方法启动动画。

使用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画

下面的示例示范了如何利用属性动画来控制“小球”掉落动画。该示例会监听用户在屏幕上的“触屏”时间,程序会在屏幕的触摸点绘制一个小球,并用动画控制该小球向下掉落。

该示例的界面布局文件非常简单,界面布局文件中只有一个LinearLayout,因此此处不再给出界面布局文件。下面是该示例的Activity代码。

packagecom.yzx.myapplication;
importandroid.animation.Animator;
importandroid.animation.AnimatorListenerAdapter;
importandroid.animation.AnimatorSet;
importandroid.animation.ObjectAnimator;
importandroid.animation.ValueAnimator;
importandroid.app.Activity;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.graphics.RadialGradient;
importandroid.graphics.Shader;
importandroid.graphics.drawable.ShapeDrawable;
importandroid.graphics.drawable.shapes.OvalShape;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.animation.AccelerateInterpolator;
importandroid.widget.LinearLayout;
importjava.util.ArrayList;
/**
*Createdbygv52jy1on2016/11/16.
*/
publicclassMainActivityextendsActivity{
//定义小球大小的常量
privatestaticfinalfloatBALL_SIZE=50F;
//定义小球从屏幕上方下落到屏幕底端的总时间
privatestaticfinalfloatFULL_TIME=1000;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ValueAnimatorvalueAnimator=ValueAnimator.ofInt(0,1);
valueAnimator.getAnimatedValue();
LinearLayoutlayout=(LinearLayout)findViewById(R.id.container);
//设置该窗口显示MyAnimationView组件
layout.addView(newMyAnimationView(this));
}
publicclassMyAnimationViewextendsViewimplementsValueAnimator.AnimatorUpdateListener{
publicfinalArrayList<ShapeHolder>balls=newArrayList<>();
publicMyAnimationView(Contextcontext){
super(context);
setBackgroundColor(Color.WHITE);
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
//如果触碰事件不是按下、移动事件
if(event.getAction()!=MotionEvent.ACTION_DOWN&&event.getAction()!=MotionEvent.ACTION_MOVE){
returnfalse;
}
//在事件发生点添加一个小球(用一个圆形代表)
ShapeHoldernewBall=addBall(event.getX(),event.getY());
//计算小球下落动画开始时的Y坐标
floatstartY=newBall.getY();
//计算小球下落动画结束时的y坐标(落到屏幕最下方,就是屏幕的高度减去小球的高度)
floatendY=getHeight()-BALL_SIZE;
//获取屏幕高度
floath=getHeight();
floateventY=event.getY();
//计算动画的持续时间
intduration=(int)(FULL_TIME*((h-eventY)/h));
//定义小球“落下”的动画
//让newBall对象的y属性从事件发生点变化到屏幕最下方
ValueAnimatorfallAnim=ObjectAnimator.ofFloat(newBall,"y",startY,endY);
//设置fallAnim动画的持续时间
fallAnim.setDuration(duration);
//设置fallAnim动画的插值方式:加速插值
fallAnim.setInterpolator(newAccelerateInterpolator());
//为fallAnim动画添加监听器
//当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
fallAnim.addUpdateListener(this);
//定义对newBall对象的alpha属性执行从1到0的动画(即定义渐隐动画)
ObjectAnimatorfadeAnim=ObjectAnimator.ofFloat(newBall,"alpha",1f,0f);
//设置动画持续时间
fadeAnim.setDuration(250);
//为fadeAnim动画添加监听器
fadeAnim.addListener(newAnimatorListenerAdapter(){
//当动画结束时
@Override
publicvoidonAnimationEnd(Animatoranimation){
//动画结束时将该动画关联的ShapeHolder删除
balls.remove(((ObjectAnimator)animation).getTarget());
}
});
//为fadeAnim动画添加监听器
//当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
fadeAnim.addUpdateListener(this);
//定义一个AnimatorSet来组合动画
AnimatorSetanimatorSet=newAnimatorSet();
//指定在播放fadeAnim之前,先播放fallAnim动画
animatorSet.play(fallAnim).before(fadeAnim);
//开发播放动画
animatorSet.start();
returntrue;
}
privateShapeHolderaddBall(floatx,floaty){
//创建一个椭圆
OvalShapecircle=newOvalShape();
//设置该椭圆的宽高
circle.resize(BALL_SIZE,BALL_SIZE);
//将椭圆包装成Drawable对象
ShapeDrawabledrawable=newShapeDrawable(circle);
//创建一个ShapeHolder对象
ShapeHoldershapeHolder=newShapeHolder(drawable);
//设置SHapeHolder的x、y坐标
shapeHolder.setX(x-BALL_SIZE/2);
shapeHolder.setY(y-BALL_SIZE/2);
intred=(int)(Math.random()*255);
intgreen=(int)(Math.random()*255);
intblue=(int)(Math.random()*255);
//将red、grenn、blue三个随机数组合成ARGB颜色
intcolor=0xff000000+red<<16|green<<8|blue;
//获取drawable上关联的画笔
Paintpaint=drawable.getPaint();
//将red、grenn、blue三个随机数除以4得到商值合成ARGB颜色
intdarkColor=0xff000000|red/4<<16|green/4<<8|blue/4;
//创建圆形渐变
RadialGradientgradient=newRadialGradient(37.5f,12.5f,BALL_SIZE,color,darkColor,Shader.TileMode.CLAMP);
paint.setShader(gradient);
//为shapeHolder设置paint画笔
shapeHolder.setPaint(paint);
balls.add(shapeHolder);
returnshapeHolder;
}
@Override
protectedvoidonDraw(Canvascanvas){
//遍历balls集合中的每个ShapeHolder对象
for(ShapeHoldershapeHolder:balls){
//保存canvas的当前坐标系统
canvas.save();
//坐标变换:将画布坐标系统平移到shapeHolder的X、Y坐标处
canvas.translate(shapeHolder.getX(),shapeHolder.getY());
//将shapeHolder持有的圆形绘制在Canvas上
shapeHolder.getShape().draw(canvas);
//恢复Canvas坐标系统
canvas.restore();
}
}
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
//指定重绘该界面
this.invalidate();
}
}
}
[/code]

上面的示例还用到了一个ShapeHolder类,该类只是负责包装ShapeDrawable对象,并为x、y、width、height、alpha等属性提供setter、getter方法,以方便ObjectAnimator动画控制它。下面是ShapeHolder类的代码。

packagecom.yzx.myapplication;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.graphics.RadialGradient;
importandroid.graphics.drawable.ShapeDrawable;
/**
*Createdbygv52jy1on2016/11/16.
*/
publicclassShapeHolder{
privatefloatx=0,y=0;
privateShapeDrawableshape;
privateColorcolor;
privateRadialGradientgradient;
privatefloatalpha=1f;
privatePaintpaint;
publicShapeHolder(ShapeDrawables){
shape=s;
}
publicfloatgetX(){
returnx;
}
publicvoidsetX(floatx){
this.x=x;
}
publicfloatgetY(){
returny;
}
publicvoidsetY(floaty){
this.y=y;
}
publicShapeDrawablegetShape(){
returnshape;
}
publicvoidsetShape(ShapeDrawableshape){
this.shape=shape;
}
publicColorgetColor(){
returncolor;
}
publicvoidsetColor(Colorcolor){
this.color=color;
}
publicRadialGradientgetGradient(){
returngradient;
}
publicvoidsetGradient(RadialGradientgradient){
this.gradient=gradient;
}
publicfloatgetAlpha(){
returnalpha;
}
publicvoidsetAlpha(floatalpha){
this.alpha=alpha;
}
publicPaintgetPaint(){
returnpaint;
}
publicvoidsetPaint(Paintpaint){
this.paint=paint;
}
}
[/code]


使用资源文件来定义动画

属性动画的资源文件放在/res/animator/目录下,资源文件代码如下:

<?xmlversion="1.0"encoding="utf-8"?>
<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="backgroundColor"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="#ff8080"
android:valueTo="#8080ff"
android:valueType="intType">
</objectAnimator>
[/code]

Java代码加载资源文件的属性动画,代码如下:

//加载动画资源
ObjectAnimatorcolorAnim=(ObjectAnimator)AnimatorInflater.loadAnimator(MainActivity.this,R.animator.color_anim);
//设置颜色类型的属性值的计算器
colorAnim.setEvaluator(newArgbEvaluator());
//对该View本身应用属性动画
colorAnim.setTarget(this);
//开始指定动画
colorAnim.start();
[/code]

参考资料

《疯狂Android讲义》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 属性动画