欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android属性动画简单总结

程序员文章站 2022-05-03 08:25:11
...

内容总结自郭神的三篇属性动画博客,附上链接:
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()方法来启动动画。
相关标签: android 动画