1. 属性动画概述
属性动画是 API 11 新加入的特性,和 View 动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至还可以没有对象
属性动画的效果也得到了加强,不再像 View 动画那样只支持四种简单的变换
属性动画默认时间间隔是 300ms,默认帧率是 10ms/帧,可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。比较常用的动画类是:ValueAnimator、ObjectAnimator、AnimatorSet
Demo:
1
2// 改变一个对象的 translationY 属性,让其沿着 Y 轴向上平移一段距离(它的高度),在默认时间内完成
ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight()).start();1
2
3
4
5
6
7// 改变一个对象的背景色属性,让背景色在 3 秒内实现从 0xFFFF8080 到 0xFF8080FF 的渐变,无限循环且反转
ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "background", /* Red */ 0xFFFF808080, /* Blue */ 0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();1
2
3
4
5
6
7
8
9
10
11
12// 动画集合,5 秒内对 View 的旋转、平移、缩放和透明度都进行了改变
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1));
set.setDuration(5 * 1000).start();
2. 使用属性动画
属性动画除了通过代码实现以外,还可以通过 XML 来定义。属性动画需要定义在
res/animator/
目录下,语法如下: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<set
android:ordering=["together" | "sequentially"] >
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["restart" | "reverse"]
android:valueType=["intType" | "floatType"] />
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["restart" | "reverse"]
android:valueType=["intType" | "floatType"] />
<set
...
</set>
</set>ValueAnimator
对应<animator>
标签,ObjectAnimator
对应<objectAnimator>
标签,AnimatorSet
对应<set>
标签<set>
标签的android:ordering
属性有两个可选值:together
和sequentially
。together
表示动画集合中的子动画同时播放,sequentially
表示动画集合中的子动画按照前后顺序依次播放。默认值是together
<objectAnimator>
标签的属性及含义属性 含义 android:propertyName
表示属性动画的作用对象的属性的名称 android:duration
表示动画的时长 android:valueFrom
表示属性的起始值 android:valueTo
表示属性的结束值 android:startOffset
表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画 android:repeatCount
表示动画的重复次数,默认值为 0,-1 表示无限循环 android:repeatMode
表示动画的重复模式,有两个选项 restart
和reverse
,分别表示连续重复和逆向重复。连续重复就是动画每次都重新开始播放,逆向重复是指第一次播放完以后,第二次会倒着播放动画,第三次再重头开始播放动画,第四次再倒着播放动画,如此反复android:valueType
表示 android:propertyName
所指定的属性的类型,有intType
和floatType
两个可选项,分别表示属性的类型为整型和浮点型。另外,如果android:propertyName
所指定的属性表示的是颜色,那么不需要指定android:valueType
,系统会自动对颜色类型的属性做处理
Demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 通过 XML 定义一个属性动画并将其作用在 View 上
// res/animator/property_animator.xml
<set android:ordering="together">
<objectAnimator
android:propertyName="x"
android:duration="300"
android:valueTo="200"
android:valueType="intType" />
<objectAnimator
android:propertyName="y"
android:duration"300"
android:valueTo="300"
android:valueType="intType" />
</set>1
2
3
4// 使用上面的属性
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim_property_animator);
set.setTarget(mButton);
set.start();- 实际开发中建议采用代码的方式实现属性动画,一方面代码的方式比较简单,另一方面很多方面一个属性的起始值是无法提前确定的
3. 理解插值器和估值器
TimeInterpolator 为时间插值器,作用是根据时间流逝的百分比来计算出当前属性值改变的百分比
- 系统预置的有 LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快) 和 DecelerateInterpolator(减速插值器:动画越来越慢)
TypeEvaluator 为类型估值器,作用是根据当前属性改变的百分比来计算改变后的属性值
- 系统预置的有 IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点型属性) 和 ArgbEvaluator(针对 Color 属性)
线性插值器
LinearInterpolator
源码:1
2
3
4
5
6
7
8
9
10
11public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input; // 线性插值器的返回值和输入值一样
}
}整型估值器
IntEvaluator
源码:1
2
3
4
5
6public class IntEvaluator implements TypeEvaluator<Integer> {
public Ingeter evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int) (startInt + fraction * (endValue - startInt));
}
}自定义插值器和估值器
- 属性动画要求对象的该属性有
set
方法和get
方法(可选) - 插值器和估值器都是一个接口,且内部都只有一个方法
- 自定义插值器需要实现 Interpolator 或者 TimeInterpolator,自定义估值器需要实现 TypeEvaluator
- 如果要对其他类型(非
int
、float
、Color
)做动画,必须要自定义类型估值器
- 属性动画要求对象的该属性有
4. 属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:
AnimatorListener
和AnimatorUpdateListener
AnimatorListener
定义:1
2
3
4
5
6public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}AnimatorUpdateListener
定义:1
2
3public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}AnimatorUpdateListener
会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimationUpdate()
就会被调用一次,利用这个特性,可以做一些特殊的逻辑
5. 对任意属性做动画
对
object
的属性abc
做动画,如果想让动画生效,要同时满足两个条件object
必须要提供setAbc()
方法,如果动画的时候没有传递初始值,那么还要提供getAbc()
方法,因为系统要去读取abc
属性的初始值(如果这条不满足,程序直接 crash)object
的setAbc()
对属性abc
所做的改变必须能够通过某种方法反映出来,比如会带来 UI 的改变之类(如果这条不满足,动画无效果但不会 crash)
上述条件 2 的反例:对 Button 的 width 属性做动画没有效果
- 因为 Button 内部虽然提供了
getWidth()
和setWidth()
方法,但是这个setWidth()
方法并不是改变视图的大小,它是 TextView 新添加的方法,View 是没有这个setWidth()
方法的。由于 Button 继承了 TextView,所以 Button 也就有了setWidth()
方法 - 分析
getWidth()
和setWidth()
源码可知,getWidth()
的确是获取 View 的宽度的,但setWidth()
是 TextView 及其子类的专属方法,它的作用不是设置 View 的宽度,而是设置 TextView 的最大宽度和最小宽度的,这和 TextView 的宽度不是一个概念 - 具体来说,TextView 的宽度对应 XML 中的
android:layout_width
属性,而 TextView 还有一个属性android:width
对应了 TextView 的setWidth()
方法
- 因为 Button 内部虽然提供了
官方文档提供解决上面反例的 3 种方法
如果有权限的话,给带操作对象加上
getXxx()
和setXxx()
方法- 但很多时候我们并没有这个权限,比如我们无法给 Button 加上一个合乎要求的
setWidth()
方法,因为这是 Android SDK 内部实现的。这个方法最简单,但往往是不可行的
- 但很多时候我们并没有这个权限,比如我们无法给 Button 加上一个合乎要求的
用一个类来包装原始对象,间接为其提供
getXxx()
和setXxx()
方法Demo:
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
28private void performAnimate() {
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width();
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
采用
ValueAnimator
,监听动画过程,自己实现属性的改变ValueAnimator
本身不作用于任何对象,即直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改对象的属性值,这样也就相当于对对象做了动画Demo:
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
28
29
30private void performAnimate(final View target, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
// 持有一个 IntEvaluator 对象,方面下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animator) {
// 获得当前动画的进度值,整型,1~100 之间
int currentValue = (Integer) animator.getAnimatedValue();
Log.d(TAG, "current value: " + currentValue);
// 获得当前进度占整个动画过程的比例,浮点型,0~1 之间
float fraction = animator.getAnimatedFraction();
// 直接调用整型估值器,通过比例计算出宽度,然后再设给 Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
@Override
public void onClick(View v) {
if (v == mButton) {
performAnimate(mButton, mButton.getWidth(), 500);
}
}
6. 属性动画的工作原理
属性动画的原理
- 属性动画要求动画作用的对象提供该属性的
get
方法和set
方法 - 属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用
set
方法 - 每次传递给
set
方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值
- 属性动画要求动画作用的对象提供该属性的
源码分析
- 分析
ObjectAnimator
的start()
方法的源码可知,首先会判断当前动画、等待的动画(Pending)和延迟的动画(delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉 ObjectAnimator
继承了ValueAnimator
,分析ValueAnimator
的start()
方法源码可知:属性动画需要运行在 Looper 的线程中,然后会调到 JNI 层- 最终通过一系列调用,属性值的
get()
方法和set()
方法都是通过反射来调用的
- 分析