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

前言

         从某种角度来看,属性动画是增强版的补间动画,属性动画的强大可以体现在如下两方面
  • 补间动画只能定义两个关键帧在“透明度”、“旋转”、“缩放”、“位移”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个步骤进行。
  1. 调用ValueAnimator的ofInt()、ofFloat()或ofObject()静态方法创建ValueAnimator实例。
  2. 调用ValueAnimator的setXxx()方法设置动画持续时间、插值方式、重复次数等。
  3. 调用ValueAnimator的start()方法启动动画。
  4. 为ValueAnimator注册AnimatorUpdateListener监听器,在该监听器中可以监听ValueAnimator计算出来的值的改变,并将这些值应用到指定对象上。
例如如下代码片段:
   
   
ValueAnimator animation = ValueAnimator.ofFloat(0f,1f);
animation.setDuration(1000);
animation.start();
         上面的例子实现了在1000ms内,值从0到1的变化。
         除此之外,开发者也可以提供一个自定义的Evaluator计算器,例如如下代码:
    
    
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(),startVal,endVal);
animation.setDuration(1000);
animation.start();
         在上面的代码片段中,ValueAnimator仅仅是计算动画过程中变化的值,并没有把这些计算出来的值应用到任何对象上,因此也不会显示任何动画。
         如果希望使用ValueAnimator创建动画,还需要注册一个监听器:AnimatorUpdateListener,该监听器负责更新对象的属性值。在实现这个监听器时,可以通过getAnimatedValue()方法来获取当前帧的值,并将该计算出来的值应用到指定对象上。当该对象的属性持续改变时,该对象也就呈现出动画效果了。

使用ObjectAnimator创建动画

         ObjectAnimator继承了ValueAnimator,因此它可以直接将ValueAnimator在动画过程中计算出来的值应用到指定对象的指定属性上(ValueAnimator则需要注册一个监听器来完成这个工作)。因此使用ObjectAnimator就不需要注册AnimatorUpdateListener监听器了。
         使用ObjectAnimator的ofInt()、ofFloat()或ofObject()静态方法创建ObjectAnimator时,需要指定具体的对象,以及对象的属性名。
         例如如下代码片段:
    
    
ObjectAnimator anim = ObjectAnimator.ofFloat(foo,"alpha",0f,1f);
anim.setDuraction(1000);
anim.start();
         与ValueAnimator不同的是,使用ObjectAnimator有如下几个注意点。
  • 要为该对象对应的属性提供setter方法,如上例中需要为foo对象提供setAlpha(float value)方法。
  • 调用ObjectAnimator的ofInt()、onFloat()或ofObject()工厂方法时,如果values...参数只提供了一个值(本来需要提供开始值和结束值),那么该值会被认为是结束值。该对象应该为该属性提供一个getter方法,该getter方法的返回值将被作为开始值。
  • 如果动画的对象是View,为了能显示动画效果,可能还需要在onAnimationUpdate()事件监听方法中调用View.invalidate()方法来刷新屏幕的显示,比如对Drawable对象的color属性执行动画。但View定义的setter方法,如setAlpha()和setTranslationX()等方法,都会自动地调用invalidate()方法,因此不需要额外地调用invalidate()方法。

使用属性动画

         属性动画即可作用于UI组件,也可作用于普通的对象(即使它没有在UI界面上绘制出来)。
         定义属性动画有如下两种方式。
  • 使用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画。
  • 使用资源文件来定义动画。
         使用属性动画的步骤如下。
  1. 创建ValueAnimator或ObjectAnimator对象----即可从XML资源文件加载该动画资源,也可直接调用ValueAnimator或ObjectAnimator的静态工厂方法来创建动画。
  2. 根据需要为Animator对象设置属性。
  3. 如果需要监听Animator的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为Animator对象设置事件监听器。
  4. 如果有多个动画需要按次序或同时播放,则应使用AnimatorSet组合这些动画。
  5. 调用Animator对象的start()方法启动动画。

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

         下面的示例示范了如何利用属性动画来控制“小球”掉落动画。该示例会监听用户在屏幕上的“触屏”时间,程序会在屏幕的触摸点绘制一个小球,并用动画控制该小球向下掉落。
         该示例的界面布局文件非常简单,界面布局文件中只有一个LinearLayout,因此此处不再给出界面布局文件。下面是该示例的Activity代码。
   
   
package com.yzx.myapplication;
 
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.widget.LinearLayout;
 
import java.util.ArrayList;
 
/**
* Created by gv52jy1 on 2016/11/16.
*/
public class MainActivity extends Activity {
// 定义小球大小的常量
private static final float BALL_SIZE = 50F;
// 定义小球从屏幕上方下落到屏幕底端的总时间
private static final float FULL_TIME = 1000;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,1);
valueAnimator.getAnimatedValue();
 
LinearLayout layout = (LinearLayout) findViewById(R.id.container);
// 设置该窗口显示MyAnimationView组件
layout.addView(new MyAnimationView(this));
}
 
public class MyAnimationView extends View implements ValueAnimator.AnimatorUpdateListener {
public final ArrayList<ShapeHolder> balls = new ArrayList<>();
 
public MyAnimationView(Context context) {
super(context);
setBackgroundColor(Color.WHITE);
}
 
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果触碰事件不是按下、移动事件
if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) {
return false;
}
// 在事件发生点添加一个小球(用一个圆形代表)
ShapeHolder newBall = addBall(event.getX(), event.getY());
// 计算小球下落动画开始时的Y坐标
float startY = newBall.getY();
// 计算小球下落动画结束时的y坐标(落到屏幕最下方,就是屏幕的高度减去小球的高度)
float endY = getHeight() - BALL_SIZE;
// 获取屏幕高度
float h = getHeight();
float eventY = event.getY();
// 计算动画的持续时间
int duration = (int) (FULL_TIME * ((h - eventY) / h));
// 定义小球“落下”的动画
 
// 让newBall对象的y属性从事件发生点变化到屏幕最下方
ValueAnimator fallAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
// 设置fallAnim动画的持续时间
fallAnim.setDuration(duration);
// 设置fallAnim动画的插值方式:加速插值
fallAnim.setInterpolator(new AccelerateInterpolator());
// 为fallAnim动画添加监听器
// 当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
fallAnim.addUpdateListener(this);
 
// 定义对newBall对象的alpha属性执行从1到0的动画(即定义渐隐动画)
ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
// 设置动画持续时间
fadeAnim.setDuration(250);
// 为fadeAnim动画添加监听器
fadeAnim.addListener(new AnimatorListenerAdapter() {
// 当动画结束时
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束时将该动画关联的ShapeHolder删除
balls.remove(((ObjectAnimator) animation).getTarget());
}
});
// 为fadeAnim动画添加监听器
// 当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
fadeAnim.addUpdateListener(this);
// 定义一个AnimatorSet来组合动画
AnimatorSet animatorSet = new AnimatorSet();
// 指定在播放fadeAnim之前,先播放fallAnim动画
animatorSet.play(fallAnim).before(fadeAnim);
// 开发播放动画
animatorSet.start();
return true;
}
 
private ShapeHolder addBall(float x, float y) {
// 创建一个椭圆
OvalShape circle = new OvalShape();
// 设置该椭圆的宽高
circle.resize(BALL_SIZE, BALL_SIZE);
// 将椭圆包装成Drawable对象
ShapeDrawable drawable = new ShapeDrawable(circle);
// 创建一个ShapeHolder对象
ShapeHolder shapeHolder = new ShapeHolder(drawable);
// 设置SHapeHolder的x、y坐标
shapeHolder.setX(x - BALL_SIZE / 2);
shapeHolder.setY(y - BALL_SIZE / 2);
 
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
// 将red、grenn、blue三个随机数组合成ARGB颜色
int color = 0xff000000 + red << 16 | green << 8 | blue;
 
// 获取drawable上关联的画笔
Paint paint = drawable.getPaint();
// 将red、grenn、blue三个随机数除以4得到商值合成ARGB颜色
int darkColor = 0xff000000 | red / 4 << 16 | green / 4 << 8 | blue / 4;
// 创建圆形渐变
RadialGradient gradient = new RadialGradient(37.5f, 12.5f, BALL_SIZE, color, darkColor, Shader.TileMode.CLAMP);
paint.setShader(gradient);
// 为shapeHolder设置paint画笔
shapeHolder.setPaint(paint);
balls.add(shapeHolder);
return shapeHolder;
}
 
@Override
protected void onDraw(Canvas canvas) {
// 遍历balls集合中的每个ShapeHolder对象
for (ShapeHolder shapeHolder : balls) {
// 保存canvas的当前坐标系统
canvas.save();
// 坐标变换:将画布坐标系统平移到shapeHolder的X、Y坐标处
canvas.translate(shapeHolder.getX(), shapeHolder.getY());
// 将shapeHolder持有的圆形绘制在Canvas上
shapeHolder.getShape().draw(canvas);
// 恢复Canvas坐标系统
canvas.restore();
}
}
 
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 指定重绘该界面
this.invalidate();
}
}
}
         上面的示例还用到了一个ShapeHolder类,该类只是负责包装ShapeDrawable对象,并为x、y、width、height、alpha等属性提供setter、getter方法,以方便ObjectAnimator动画控制它。下面是ShapeHolder类的代码。
   
   
package com.yzx.myapplication;
 
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.drawable.ShapeDrawable;
 
/**
* Created by gv52jy1 on 2016/11/16.
*/
public class ShapeHolder {
private float x = 0, y = 0;
private ShapeDrawable shape;
private Color color;
private RadialGradient gradient;
private float alpha = 1f;
private Paint paint;
 
public ShapeHolder(ShapeDrawable s) {
shape = s;
}
 
public float getX() {
return x;
}
 
public void setX(float x) {
this.x = x;
}
 
public float getY() {
return y;
}
 
public void setY(float y) {
this.y = y;
}
 
public ShapeDrawable getShape() {
return shape;
}
 
public void setShape(ShapeDrawable shape) {
this.shape = shape;
}
 
public Color getColor() {
return color;
}
 
public void setColor(Color color) {
this.color = color;
}
 
public RadialGradient getGradient() {
return gradient;
}
 
public void setGradient(RadialGradient gradient) {
this.gradient = gradient;
}
 
public float getAlpha() {
return alpha;
}
 
public void setAlpha(float alpha) {
this.alpha = alpha;
}
 
public Paint getPaint() {
return paint;
}
 
public void setPaint(Paint paint) {
this.paint = paint;
}
}

使用资源文件来定义动画

         属性动画的资源文件放在/res/animator/目录下,资源文件代码如下:
   
   
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns: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>
         Java代码加载资源文件的属性动画,代码如下:
   
   
// 加载动画资源
ObjectAnimator colorAnim = (ObjectAnimator) AnimatorInflater.loadAnimator(MainActivity.this, R.animator.color_anim);
// 设置颜色类型的属性值的计算器
colorAnim.setEvaluator(new ArgbEvaluator());
// 对该View本身应用属性动画
colorAnim.setTarget(this);
// 开始指定动画
colorAnim.start();

参考资料

  • 《疯狂Android讲义》









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值