Android属性动画简单总结
内容总结自郭神的三篇属性动画博客,附上链接:
Android属性动画完全解析(上),初识属性动画的基本用法
Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法
为什么要引入属性动画?
Android系统目前有3种动画:逐帧动画(frame-by-frame animation)和补间动画(tweened animation)以及属性动画(property animation)。逐帧动画类似于动画片,就是将完整的动画过程分成一张张单独的图片,然后连起来播放;补间动画就是对View进行一系列的操作,包括淡入淡出、平移、旋转、缩放四种。逐帧动画和补间动画技术旧了,用的不多,属性动画弥补了这两者的缺点,其优点如下:
- 可以对非View对象进行操作,比如有个自定义的View里面有个Point对象用于管理坐标,如果我们想操作View,就必须对Point进行操作,但是非View对象逐帧和补间做不到,属性动画可以
- 可以对对象进行动态操作,比如说我们要在程序中动态改变背景的颜色,由于补间和逐帧的动画机制是以硬编码的方式实现的,无法做到动态改变,属性动画可以
- 可以真正意义上改变View的属性,逐帧和补间虽然实现了View显示效果上的改变,但是并没有真正改变其属性,比如说我们将一个按钮拖动到别的位置,它实际上的点击位置没变,我们只有点击原位置才能激发点击事件。
属性动画实际上是对值进行操作,将值的改变赋给指定对象的指定属性上,可以是任意对象的任意属性,从而实现真正意义上的改变属性。
ValueAnimator
属性动画是对值的操作,而ValueAnimator就是负责从初始值到结束值之间的过渡动画,我们只需要给定初始值和结束值,以及动画持续时间,ValueAnimator会自动帮我们实现动画效果。
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float)animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
ofFloat方法第一个参数是初始值,第二个是结束值,代码中还设置了动画监听器,动画更新时会自动回调更新方法,getAnimatedValue()用于将每次更新后的值返回。
除此之外,我们还可以调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。
ObjectAnimator
ObjectAnimator继承自ValueAnimator,所以父类的方法在ObjectAnimator里也可以用,此外ValueAnimator只是值的一种变化,而ObjectAnimator则真正可以对对象的属性进行修改,看个例子:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
这里的ofFloat方法就更加具体了,除了值的变化,还增加了对象textview和属性”alpha”,即在5s内textview的透明度从完全不透明变化到透明再变到不透明。
ObjectAnimator中的ofXXX方法需要指定对象和属性,但是属性可以是任意的吗?答案是:任意的。因为ObjectAnimator当初的设计就不是仅仅针对View,而是对任意的对象的属性。
那对于比如说textView中我们传入的String类型的属性名,是否在TextView中有同样属性名的变量呢?答案是:没有。父类View中也没有,实际上ObjectAnimator是通过传入的属性名在对象中找与其对应的set和get方法,比如说:
public void setAlpha(float value);
public float getAlpha();
通过这两个方法来修改对象的属性,并且这两个方法是由View提供的,也就是说任何继承View的类都能使用。
AnimatorSet
实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
- after(Animator anim) 将现有动画插入到传入的动画之后执行
- after(long delay) 将现有动画延迟指定毫秒后执行
- before(Animator anim) 将现有动画插入到传入的动画之前执行
- with(Animator anim) 将现有动画和传入的动画同时执行
用法如下:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
Animator监听器
Animator类提供了一个addListener()方法,该方法传入一个AnimatorListener,我们只需要实现这个AnimatorListener就可以实现监听功能。
无论是ValueAnimator、ObjectAnimator还是AnimatorSet都继承了Animator,所以都可以添加监听器。
添加监听器的代码如下:
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
如果不需要实现监听器的全部功能,可以传入一个AnimatorListenerAdapter对象,由于其已经实现了监听器的功能,我们只需要重写需要的方法即可。
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
使用XMl编写动画
使用XMl去编写动画可能会比代码编写要慢,但是重用起来方便,适合编写重用度高的动画。
如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中我们一共可以使用如下三种标签:
- <animator> 对应代码中的ValueAnimator
- <objectAnimator> 对应代码中的ObjectAnimator
- <set> 对应代码中的AnimatorSet
下面代码实现将一个视图先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<objectAnimator
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="-500"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="together" >
<objectAnimator
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="sequentially" >
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" >
</objectAnimator>
</set>
</set>
</set>
最后在代码中调用该动画如下:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
Interpolator
实际上从Android 1.0版本开始就一直存在Interpolator接口了,而之前的补间动画当然也是支持这个功能的。只不过在属性动画中新增了一个TimeInterpolator接口,TimeInterpolator中文翻译为时间插值器,作用是根据时间流逝的百分比来计算当前属性值改变的百分比,这个接口是用于兼容之前的Interpolator的。下面是TimeInterpolator常用的实现类:
- BounceInterpolator:弹跳插值器,动画会有落地弹跳的效果
- LinearInterpolator:线性插值器,匀速动画
- DecelerateInterpolator:减速插值器,动画越来越慢
- AccelerateDeceleterateInterpolator:加速减速插值器,动画两头慢中间快(系统默认的是该插值器)
- OvershootInterpolator:过度插值器,动画最后会超过结束值播放一段时间再返回,就像跑步太快冲过头了
- AnticipateInterpolator:助跑插值器,动画会向后退一段距离助跑,然后再开始
下面看下TimeInterpolator的源码:
public interface TimeInterpolator {
float getInterpolation(float input);
}
getInterpolation()方法中接收一个input参数,这个参数的值会随着动画的运行而不断变化,从0匀速变化到1。
显而易见,线性插值器恰好是匀速变化的,所以应该直接返回这个input,我们看下源码:
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
可以看出直接返回了input,对于加速减速插值器,返回的是经过计算包装的input。如果我们想要获得自己的插值器,实现更复杂的功能,可以照着源码编写,只是返回的input需要运算包装。那么就有问题了:这个input返回后给哪个方法调用了呢?我们现在只是做到了随着时间的流逝,可以获得对应时间下的属性值改变的百分比,但是如何通过百分比来改变属性的值呢?这就引入了TypeEvaluator。
TypeEvaluator
TypeEvaluator的中文翻译为类型估值算法,简称估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有:
- IntEvaluator:针对整型属性
- FloatEvaluator:针对浮点型属性
- ArgbEvaluator:针对Color属性
我们来看下FloatEvaluator的源码:
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
fraction是个估值小数,其实fraction就是上面input的返回值,传到估值器中根据属性变化的百分比来计算当前属性的值,然后返回每一时刻的属性值,我们可以在下面的代码中使用animator.getAnimatedValue()方法获取每一时刻的属性值。
如果我们想要实现自己的动画过渡效果,告诉系统如何让指定对象的指定属性按照我们的需求改变,就需要自定义估值器,也就是创建一个类实现TypeEvaluator,可以看下面的例子:
public class ColorEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
String startColor = (String) startValue;
String endColor = (String) endValue;
......
}
}
ViewPropertyAnimator
我们都知道,属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。但是,在绝大多数情况下,主要都还是对View进行动画操作的。Android开发团队也是意识到了这一点,没有为View的动画操作提供一种更加便捷的用法确实是有点太不人性化了,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。
我们先来回顾一下之前的用法吧,比如我们想要让一个TextView从常规状态变成透明状态,就可以这样写:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();
看上去不复杂,将需要的参数传入即可,但是却不太符合我们平常面向对象的思维。而用ViewPropertyAnimator实现如下:
textview.animate().alpha(0f);
看上去简便很多,animator方法返回一个ViewPropertyAnimator对象,我们可以调用这个对象的方法去修改对象的属性,例如:
textview.animate().x(500).y(500).setDuration(5000).setInterpolator(new BounceInterpolator());
ViewPropertyAnimator的对象采用连缀的方式,将多个操作连缀在一起,先在x方向移动到500这个位置,再在y方向上移动,这样启动的时候就能同时执行。
需要注意的是:
- 整个ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,后面连缀的方法每次执行完一个后,还是返回该实例,这样可以通过一行代码实现对View的操作。
- 在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()方法来启动动画。