属性动画是增强版的补间动画
- 补间动画只能定义两个关键帧在“透明度”“旋转”“缩放”“位移”4个方面的变化,但属性动画可以定义任何属性的变化。
- 补间动画只能对 UI组件执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。
与补间动画类似的是,属性动画也需要定义如下几个属性。
- 动画持续时间:该属性的默认值是300ms。在属性动画资源文件中通过
android:duration
属性指定。 - 动画插值方式:该属性的作用与补间动画中插值属性的作用基本类似。在属性动画资源文件中通过
android:interpolator
属性指定。 - 动画重复次数:指定动画重复播放的次数。在属性动画资源文件中通过
android:repeatCount
属性指定。 - 重复行为:指定动画播放结束后、重复下次动画时,是从开始帧再次播放到结束帧,还是从结束帧反向播放到开始帧。在属性动画资源文件中通过
android:repeatMode
属性指定。 - 动画集:开发者可以将多个属性动画合并成一组,既可让这组属性动画按次序播放,也可让这组属性动画同时播放。在属性动画资源文件中通过
<set../>
元素来组合,该元素的android:ordering
属性指定该组动画是按次序播放,还是同时播放。 - 帧刷新频率:指定每隔多长时间播放一帧。该属性的默认值为10ms。
属性动画的 API
-
Animator
它提供了创建属性动画的基类,基本上不会直接使用该类。通常该类只用于被继承并重写它的相关方法。 -
ValueAnimator
属性动画主要的时间引擎,它负责计算各个帧的属性值。它定义了属性动画的绝大部分核心功能,包括计算各帧的相关属性值,负责处理更新事件,按属性值的类型控制计算规则。属性动画主要由两方面组成:①计算各帧的相关属性值;②为指定对象设置这些计算后的值。ValueAnimator
只负责第一方面内容,因此程序员必须根据ValueAnimator
计算并监听值更新来更新对象的相关属性值。 -
ObjectAnimator
它是ValueAnimator
的子类,允许程序员对指定对象的属性执行动画。在实际应用中,ObjectAnimator
使用起来更加简单,因此更加常用。在少数场景下,由于ObjectAnimator
存在一些限制,可能需要考虑使用ValueAnimator
。 -
AnimatorSet
它是Animator
的子类,用于组合多个Animator
,并指定多个Animator
是按次序播放,还是同时播放。
除此之外,属性动画还需要利用一个 Evaluator
(计算器),该工具类控制属性动画如何计算属性值。Android 提供了如下 Evaluator
。
-
IntEvaluator
用于计算int
类型属性值的计算器。 -
FloatEvaluator
用于计算float
类型属性值的计算器。 -
ArgbEvaluator
用于计算以十六进制形式表示的颜色值的计算器。 -
TypeEvaluator
它是计算器接口,开发者可以通过实现该接口来实现自定义计算器。如果需要对int
、foat
或者颜色值以外类型的属性执行属性动画,可能需要实现TypeEvaluator
接口来实现自定义计算器。
Android 8 还为 AnimatorSet
新增了如下方法。
-
reverse()
反向播放属性动画。 -
long getCurrentPlayTime()
获取动画的当前播放时间。 -
setCurrentPlayTime(long playTime)
设置动画的播放时间。
通过调用 setCurrentPlayTime()
方法,Android 允许 AnimatorSet
动画直接调到指定时间点进行播放,不需要总是从头开始播放;通过 reverse()
方法则允许对动画进行倒播--以前可能需要定义两组动画,其中一组用于正向播放(比如放大、淡入);另一组用于反向播放(比如缩小、淡出),有了倒播功能之后,只需定义一组动画即可。
1. 使用 ValueAnimator 创建动画
使用 ValueAnimator
创建动画可按如下4个步骤进行。
- 调用
ValueAnimator
的ofint()
、ofFloat()
或ofObject()
静态方法创建ValueAnimator
实例。 - 调用
ValueAnimator
的setXxx()
方法设置动画持续时间、插值方式、重复次数等。
- 调用
ValucAnimator
的start()
方法启动动画。 - 为
ValueAnimator
注册AnimatorUpdateListener
监听器,在该监听器中可以监听ValueAnimator
计算出来的值的改变,并将这些值应用到指定对象上。
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(1000);
animator.start();
自定义的 Evaluator
计算器:
ValueAnimator animation = ValueAnimator.ofObject (MyTypeEvaluator (), startVal, endVal) ;
animation.setDuration (1000);
animation. start();
在上面的代码片段中,ValueAnimator
仅仅是计算动画过程中变化的值,并没有把这些计算出来的值应用到任何对象上,因此也不会显示任何动画。
如果希望使用 ValueAnimator
创建动画,还需要注册一个监听器:AnimatorUpdateListener
,该监听器负责更新对象的属性值。在实现这个监听器时,可以通过 getAnimatedValue()
方法来获取当前帧的值,并将该计算出来的值应用到指定对象上。当该对象的属性持续改变时,该对象也就呈现出动画效果了。
2. 使用 ObjectAnimator 创建动画
ObjectAnimator
继承了 ValueAnimator
,因此它可以直接将 ObjectAnimator
在动画过程中计算出来的值应用到指定对象的指定属性上(ValueAnimator
则需要注册一个监听器来完成这个工作)。因此使用 ObjectAnimator
就不需要注册 AnimatorUpdateListener
监听器了。
使用 ObjectAnimator
的 ofInt()
、ofFloat()
或 ofObject()
静态方法创建 ObjectAnimator
时,需要指定具体的对象,以及对象的属性名。
ObjectAnimator anim = ObjectAnimator.ofFloat (foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
与 ValueAnimator
不同的是,使用 ObjectAnimator
有如下几个注意点。
- 要为该对象对应的属性提供
setter
方法,如上例中需要为foo
对象提供setAlpha(float value)
方法。 - 调用
ObjectAnimator
的ofInt()
、ofFloat()或
ofObject()工厂方法时,如果
values...参数只提供了一个值(本来需要提供开始值和结束值),那么该值会被认为是结束值。该对象应该为该属性提供一个
getter方法,该
getter` 方法的返回值将被作为开始值。 - 如果动画的对象不是
View
,为了能显示动画效果,可能还需要在onAnimationUpdate()
事件监听方法中调用View.invalidate()
方法来刷新屏幕的显示,比如对Drawable
对象的color
属性执行动画。但如果是View
定义的setter
方法,如setAIpha()
和setTranslationX()
等方法,都会自动地调用invalidate()
方法,因此不需要额外地调用 invalidate()` 方法。
使用属性动画
属性动画既可作用于 UI 组件,也可作用于普通的对象(即使它没有在UI 界面上绘制出来)。
定义属性动画有如下两种方式。
- 使用
ValueAnimator
或ObjectAnimator
的静态工厂方法来创建动画。 - 使用资源文件来定义动画。
使用属性动画的步骤如下。
- 创建
ValueAnimator
或ObjectAnimator
对象--既可从 XML 资源文件加载该动画资源,也可直接调用ValueAnimator
或ObjectAnimator
的静态工厂方法来创建动画。 - 根据需要为
Animator
对象设置属性。 - 如果需要监听
Animator
的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为Animator
对象设置事件监听器。 - 如果有多个动画需要按次序或同时播放,则应使用
AnimatorSet
组合这些动画。 - 调用
Animator
对象的start()
方法启动动画。
MainActivity
public class MainActivity extends AppCompatActivity {
// 定义小球大小的常量
public static final float BALL_SIZE = 50f;
// 定义小球从屏幕上方下落到屏幕底端的总时间
public static final float FULL_TIME = 1000f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout container = findViewById(R.id.container);
// 设置该窗口显示MyAnimationView组件
container.addView(new MyAnimationView(this));
}
static class MyAnimationView extends View implements ValueAnimator.AnimatorUpdateListener {
List<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属性从事件发生点变化到屏幕最下方
ObjectAnimator 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、green、blue三个随机数组合成ARGB颜色
int color = -0x1000000 + red << 16 | (green << 8) | blue;
// 获取drawable上关联的画笔
Paint paint = drawable.getPaint();
// 将red、green、blue三个随机数除以4得到商值组合成ARGB颜色
int darkColor = (-0x1000000 | (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
public 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
public class ShapeHolder {
private float x = 0, y = 0;
private ShapeDrawable shape;
private int 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 int getColor() {
return color;
}
public void setColor(int 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;
}
}
image.gif
实例:大珠小珠落玉盘
MainActivity
public class MainActivity extends AppCompatActivity {
// 定义小球大小的常量
public static final float BALL_SIZE = 50f;
// 定义小球从屏幕上方下落到屏幕底端的总时间
public static final float FULL_TIME = 1000f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout container = findViewById(R.id.container);
// 设置该窗口显示MyAnimationView组件
container.addView(new MyAnimationView(this));
}
class MyAnimationView extends View {
List<ShapeHolder> balls = new ArrayList<>();
MyAnimationView(Context context) {
super(context);
// 加载动画资源
ObjectAnimator colorAnim = (ObjectAnimator) AnimatorInflater.loadAnimator(MainActivity.this,
R.animator.color_anim);
colorAnim.setEvaluator(new ArgbEvaluator());
// 对该View本身应用属性动画
colorAnim.setTarget(this);
// 开始指定动画
colorAnim.start();
}
@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属性从事件发生点变化到屏幕最下方
ObjectAnimator fallAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
// 设置fallAnim动画的持续时间
fallAnim.setDuration(duration);
// 设置fallAnim动画的插值方式:加速插值
fallAnim.setInterpolator(new AccelerateInterpolator());
// 定义小球“压扁”的动画:该动画控制小球的x坐标“左移”半个球
ObjectAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall,
"x", newBall.getX(), newBall.getX() - BALL_SIZE / 2);
// 设置squashAnim1动画的持续时间
squashAnim1.setDuration(duration / 4);
// 设置squashAnim1动画重复1次
squashAnim1.setRepeatCount(1);
// 设置squashAnim1动画的重复方式
squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
// 设置squashAnim1动画的插值方式:减速插值
squashAnim1.setInterpolator(new DecelerateInterpolator());
// 定义小球“压扁”的动画:该动画控制小球的宽度加倍
ObjectAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width",
newBall.getWidth(), newBall.getWidth() + BALL_SIZE);
// 设置squashAnim2动画的持续时间
squashAnim2.setDuration(duration / 4);
// 设置squashAnim2动画重复1次
squashAnim2.setRepeatCount(1);
// 设置squashAnim2动画的重复方式
squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
// 设置squashAnim2动画的插值方式:减速插值
squashAnim2.setInterpolator(new DecelerateInterpolator());
// 定义小球“拉伸”的动画:该动画控制小球的y坐标“下移”半个球
ObjectAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall,
"y", endY, endY + BALL_SIZE / 2);
// 设置stretchAnim1动画的持续时间
stretchAnim1.setDuration(duration / 4);
// 设置stretchAnim1动画重复1次
stretchAnim1.setRepeatCount(1);
// 设置stretchAnim1动画的重复方式
stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
// 设置stretchAnim1动画的插值方式:减速插值
stretchAnim1.setInterpolator(new DecelerateInterpolator());
// 定义小球“拉伸”的动画:该动画控制小球的高度减半
ObjectAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
newBall.getHeight(), newBall.getHeight() - BALL_SIZE / 2);
// 设置stretchAnim2动画的持续时间
stretchAnim2.setDuration(duration / 4);
// 设置squashAnim2动画重复1次
stretchAnim2.setRepeatCount(1);
// 设置squashAnim2动画的重复方式
stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
// 设置squashAnim2动画的插值方式:减速插值
stretchAnim2.setInterpolator(new DecelerateInterpolator());
// 定义小球“弹起”的动画
ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY, startY);
// 设置持续时间
bounceBackAnim.setDuration(duration);
// 设置动画的插值方式:减速插值
bounceBackAnim.setInterpolator(new DecelerateInterpolator());
// 使用AnimatorSet按顺序播放“下落/压扁&拉伸/弹起动画
AnimatorSet bouncer = new AnimatorSet();
// 定义在squashAnim1动画之前播放fallAnim下落动画
bouncer.play(fallAnim).before(squashAnim1);
// 由于小球在“屏幕”下方弹起时,小球要被压扁
// 即:宽度加倍、x坐标左移半个球,高度减半、y坐标下移半个球
// 因此此处指定播放squashAnim1的同时
// 还播放squashAnim2、stretchAnim1、stretchAnim2
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
// 指定播放stretchAnim2动画之后,播放bounceBackAnim弹起动画
bouncer.play(bounceBackAnim).after(stretchAnim2);
// 定义对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());
}
});
// 再次定义一个AnimatorSet来组合动画
AnimatorSet animatorSet = new AnimatorSet();
// 指定在播放fadeAnim之前,先播放bouncer动画
animatorSet.play(bouncer).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、green、blue三个随机数组合成ARGB颜色
int color = -0x1000000 + red << 16 | (green << 8) | blue;
// 获取drawable上关联的画笔
Paint paint = drawable.getPaint();
// 将red、green、blue三个随机数除以4得到商值组合成ARGB颜色
int darkColor = (-0x1000000 | (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
public 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();
}
}
}
}
ShapeHolder
public class ShapeHolder {
private float x = 0, y = 0;
private ShapeDrawable shape;
private int color;
private RadialGradient gradient;
private float alpha = 1f;
private Paint paint;
public ShapeHolder(ShapeDrawable s) {
shape = s;
}
public float getWidth() {
return shape.getShape().getWidth();
}
public void setWidth(float width) {
Shape s = shape.getShape();
s.resize(width, s.getHeight());
}
public float getHeight() {
return shape.getShape().getHeight();
}
public void setHeight(float height) {
Shape s = shape.getShape();
s.resize(s.getWidth(), height);
}
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 int getColor() {
return color;
}
public void setColor(int 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;
}
}
animator/color_anim.xml
<?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" />
image.gif
摘抄至《疯狂Android讲义(第4版)》
网友评论