Android动画框架总结

概述

Android中, 动画分为两种: AnimationTransition. 其中Animation分为View AnimationProperty Animation.

Transition是用于ActivityFragment的转场动画.

View Animation的作用对象是整个View对象, 它只支持4种动画效果: 平移, 缩放, 旋转和透明度动画. 另外, View Animation的一种特殊表现为: 逐帧动画. 它是通过缓慢的播放一组图像来到达一种动画效果. 需要注意的是: 由于逐帧动画是播放一组图像, 如果图片太大的话, 容易导致OOM.

Property Animation中文意思为: 属性动画. 它是Android 3.0推出的全新动画. 它主要是为了弥补View Animation的缺陷. View Animation只能支持View的四中动画. 除了这四种外的动画, View Animation都无能为力. 例如: 颜色的渐变动画.

下面主要总结一下Android中的属性动画.

动画的简单原理

假设现在有一个View, 它的X坐标为0. 如果我们想让它移动到X坐标为100的地方. 我们可以调用view.setTranslationX(). 但是出来的效果是: View在一瞬间跳跃到了100那里, 给人一种非常突然的感觉. 基于这种情况, 我们要让View移动到100的地方这个过程更符合人类的感官. 所以我们可以在一定的时间内, 每一帧改变View的位置, 当最后一帧的时候, 让View处于最终位置. 这样会比较符合人类的感官.

简单的来讲, 动画的整个过程可以这么概括: 给定一个动画完成的时间S, 动画每一帧的时间Sa, 根据经过的帧数来计算出动画完成的时间度(n*Sa / S). 接着根据这个时间完成度, 计算出动画完成度D. 最后根据D来计算出此时对应的属性为多少, 然后根据这个值来重新绘制.

时间完成度通过一个TimeInterpolator来计算出来动画的完成度. 而动画完成度通过TypeEvaluator计算出此刻的属性.

属性动画API

在使用属性动画时, 我们可以用这三个类:

  • ValueAnimator: 最基础的动画类, 它只负责根据时间完成度来计算出动画完成度. API最难使用, 但是最为灵活.

  • ObjectAnimator: ValueAnimator的子类, 对ValueAnimator进一步的封装. 内部会根据传入的属性值来调用其对应的setget方法来更新界面. API比较简单, 但是灵活性不如ValueAnimator.

  • ViewPropertyAnimator: 针对Android View控件的动画的封装. 该类的使用场景: 多个动画需要同时进行. 该类内部会只调用一个重绘操作. 这也是该类的一个主要优化. 它的API调用最为简单. 但是只支持View的四种动画, 灵活性不大.

ViewPropertyAnimator

使用

ViewPropertyAnimator的使用最为简单:

  • 执行单个动画:
1
view.animate().translationX(50); //向右平移50
  • 多个动画同时执行:
1
2
3
4
5
view.animate()
.setDuration(400)
.setInterpolator(new LinearInterpolator())
.translationX(50)
.scaleX(0.5f);

由于ViewPropertyAnimator在播放多个动画时, 只支持同时播放, 所以播放同时播放多个动画时, 各个动画是共享Interpolator和播放时间的.

设置监听器

ViewPropertyAnimator支持设置三种类型的监听器:

  • ViewPropertyAnimator.setListener()

  • ViewPropertyAnimator.setUpdateListener()

  • ViewPropertyAnimator.withStartAction/EndAction()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
propertyAnimator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//动画开始时被回调
}
@Override
public void onAnimationEnd(Animator animation) {
//动画完成的时候被回调, 注意: 当动画被取消后, 该方法也会回调.
}
@Override
public void onAnimationCancel(Animator animation) {
//动画被取消的时候被会回调
}
@Override
public void onAnimationRepeat(Animator animation) {
//动画重复执行的时候被回调
}
});
propertyAnimator.setListener(new AnimatorListenerAdapter() {
//如果不想重写那么多方法的话, 可以利用这个适配器
//根据需要重写对应的方法
});
1
2
3
4
5
propertyAnimator.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//动画更新时被调用, 默认更新的时间为10ms
}});
1
2
3
4
5
6
7
8
9
10
11
12
13
propertyAnimator.withEndAction(new Runnable() {
@Override
public void run() {
//动画结束的时候被调用(被取消的时候不会被调用), 该监听器是一次性的, 只要被调用过一次的话, 后面就不会再被调用了
}
});
propertyAnimator.withEndAction(new Runnable() {
@Override
public void run() {
//动画开始的时候被调用, 该监听器是一次性的, 只要被调用过一次的话, 后面就不会再被调用了
}
});

适用场景

ViewPropertyAnimator的动画只支持View的四种动画. 还有只支持多个动画同时播放. 所以它的适用场景:

  • 需要的动画类型为: View的四种动画.(因此API调用简单)

  • 需要的是View动画类型, 并且多个动画同时播放. (因此内部会只进行一次重绘的优化)

ObjectAnimator

如果你有一个自定义View, 它是一个自定义的圆形进度条. 你想对它的进度进行动画, 但是进度的类型为float, 因此, ViewPropertyAnimator不能胜任. 此时就要用ObjectAnimator了.

ObjectAnimator支持对任何类型值进行动画. 但是有一个前提: 在自定义View的内部添加settergetter方法.

1
2
3
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 75);
animator.setInterpolator(new LinearInterpolator());
animator.start();

Interpolator

前面说过, Interpolator是计算动画完成度的依据. 它本质上是一个算法, 动画的完成度由时间完成度根据这个算法算出来的.

系统自带了它的很多子类. 这里不多说, 可以参考这篇文章

监听器

ObjectAnimator支持三种监听器

  • ObjectAnimator.addListener()

  • ObjectAnimator.addUpdateListener()

  • ObjectAnimator.addPauseListener()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//动画开始时被回调
}
@Override
public void onAnimationEnd(Animator animation) {
//动画完成的时候被回调, 注意: 当动画被取消后, 该方法也会回调.
}
@Override
public void onAnimationCancel(Animator animation) {
//动画被取消的时候被会回调
}
@Override
public void onAnimationRepeat(Animator animation) {
//动画重复执行的时候被回调
}
});
animator.addListener(new AnimatorListenerAdapter() {
//如果不想重写那么多方法的话, 可以利用这个适配器
//根据需要重写对应的方法
});
1
2
3
4
5
6
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//动画更新时被调用, 默认更新的时间为10ms
}
});
1
2
3
4
5
6
7
8
9
10
11
animator.addPauseListener(new Animator.AnimatorPauseListener() {
@Override
public void onAnimationPause(Animator animation) {
//动画被暂停的时候回调
}
@Override
public void onAnimationResume(Animator animation) {
//动画被恢复的时候调用
}
});

适用场景

  • 当动画类型不是View的四种动画类型, 并且你有权利给你的View添加settergetter

但是如果你没有权利给View添加settergetter方法呢? 比如你正在用一个第三方的View. 这时候有两种选择:

  • 用一个类来包装你的View, 为包装View添加settergetter方法.

  • 使用ValuesAnimator来自己手动更新.

ValuesAnimator

ValuesAnimatorViewPropertyAnimatorObjectAnimator的父类, 这两个类都是对ValuesAnimator的进一步封装, 以此来提供更为便利的API调用. 因此这两个类的灵活性也下降了. 当上面的两个类都不能满足你的需求时, 你可以使用ValuesAnimator, 让它帮你计算每一帧后的属性值, 然后你监听这个属性的变化, 最后设置对应的属性, 并且重绘View.

1
2
3
4
5
6
7
8
9
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
//设置 value属性, 并且重绘View
}
});
valueAnimator.start();

从上面的代码, 我们发现, ValueAnimatorAPI调用相比前两个类, 要复杂一点, 但是它最为灵活. 前两个类只不过是对ValueAnimator的封装, 以便提供更为方便的API.

TypeEvaluator

TypeEvaluator为估值器, 前面说到, 算出动画的完成度后, 我们可以根据这个动画完成度来计算出当前属性的值. 那怎么算呢? TypeEvaluator就是来完成这个计算工作的.

ObjectAnimator中, 我们常用的是ofIntofFloat方法, 但是如果你的属性是一个对象呢? 比如PointF对象, 这时, 应该使用ofObject方法.

下面以官方提供的一个PontFEvaluator为例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ObjectAnimator.ofObject(view, "point", new PointFEvaluator(), new PointF(0, 0),
new PointF(100, 100));
public class PointFEvaluator implements TypeEvaluator<PointF> {
private PointF mPoint;
public PointFEvaluator() {}
public PointFEvaluator(PointF reuse) {
mPoint = reuse;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + (fraction * (endValue.x - startValue.x)); //根据动画完成度算出X坐标
float y = startValue.y + (fraction * (endValue.y - startValue.y)); //根据动画完成度算出Y坐标
if (mPoint != null) {
mPoint.set(x, y);
return mPoint; //返回对应的新对象
} else {
return new PointF(x, y);
}
}
}

多个属性的动画同时执行

ViewPropertyAnimator调用多个属性进行动画时, 是同时进行的. 那么对于ObjectAnimator呢? 它如何让多个属性进行配合? 答案是使用PropertyValuesHolder. 用法如下:

1
2
3
4
5
6
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0.4f);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3);
objectAnimator.start();

多个动画配合执行

有时候, 我们需要的效果可能是多个动画配合执行, 这时候, 我们可以用AnimatorSet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectAnimator animator1 = ObjectAnimator.ofInt(...);
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
ObjectAnimator animator3 = ObjectAnimator.ofInt(...);
AnimatorSet set = new AnimatorSet();
set.playSequentially(animator1, animator2, animator3); //按顺序播放
set.start();
set.playTogether(animator1, animator2); //同时播放
set.start();
set.play(animator1).with(animator2);
set.play(animator2).after(animator3); //进行精准控制播放
set.start();

总结

  • 当你的动画类型是View四种动画的其中一种或者两种以上的话, 优先使用ViewPropertyAnimator 因为API调用很简单.

  • ViewPropertyAnimator不能满足你的需求时, 使用ObjectAnimator.

  • 如果使用的View的属性没有对应的settergetter方法的话, 可以使用ValueAnimator.

参考资料

HenCoder

Android开发艺术探索

分享到