• 7.2 Android属性动画分析" level="2">7.2 Android属性动画分析
    • 7.2.1 ObjectAnimator" level="3">7.2.1 ObjectAnimator
    • 7.2.2 PropertyValuesHolder" level="3">7.2.2 PropertyValuesHolder
    • 7.2.3 ValueAnimator" level="3">7.2.3 ValueAnimator
    • 7.2.4 动画事件的监听" level="3">7.2.4 动画事件的监听
    • 7.2.5 AnimatorSet" level="3">7.2.5 AnimatorSet
    • 7.2.6 在XML中使用属性动画" level="3">7.2.6 在XML中使用属性动画
    • 7.2.7 View的animate方法" level="3">7.2.7 View的animate方法

    7.2 Android属性动画分析" class="reference-link">7.2 Android属性动画分析

    由于Android 3.0之前已有的动画框架Animation存在一些局限性——动画改变的只是显示,并不能响应事件。因此,在Android 3.0之后,Google就提出了属性动画这样一个新的动画框架,帮助开发者实现更加丰富的动画效果。

    而在Animator框架中使用最多的就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,而使用多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。最重要的是,属性动画通过调用属性的get、set方法来真实地控制了一个View的属性值,因此强大的属性动画框架,基本可以实现所有的动画效果。

    下面先来看看属性动画中最基础的ObjectAnimator。

    7.2.1 ObjectAnimator" class="reference-link">7.2.1 ObjectAnimator

    ObjectAnimator是属性动画框架中最重要的实行类,创建一个ObjectAnimator只需通过他的静态工厂类直接返回一个ObjectAnimator对象。参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过Java反射机制来调用set函数修改对象属性值。同样,你也可以调用setInterpolator设置相应的差值器。下面这个小例子就完成了一个非常简单的平移动画,程序运行效果如图7.2所示,点击后平移效果如图7.3所示。

    7.2 Android属性动画分析 - 图1 7.2 Android属性动画分析 - 图2
    图7.2 属性动画——平移动画(前) 图7.3 属性动画——平移动画(后)

    在前面的讲解中说到,以前的动画框架所产生的动画,并不能改变事件响应的位置,它只是单纯地修改了显示。如果使用旧的视图动画产生上面的效果,那么按钮的实际点击有效区依然在原来的地方,点击移动后的地方是不会有点击事件发生的。而属性动画则不同,由于它真实地改变了一个View的属性,所以事件响应的区域也同样发生了改变,这时候点击移动后的按钮,就会响应点击事件了。

    讲了这么多,让我们来看看这个简单的平移动画是如何实现的。麻雀虽小五脏俱全,这个简单的例子基本上就涵盖了ObjectAnimator的所有知识:

    1. ObjectAnimator animator = ObjectAnimator.ofFloat(
    2.  
    3. view,
    4. "translationX",
    5. 300);
    6. animator.setDuration(300);
    7. animator.start();

    通过ObjectAnimator的静态工厂方法,创建一个ObjectAnimator对象。第一个参数自然是需要操纵的View,第二个参数则是要操纵的属性,而最后一个参数是一个可变数组参数,需要传进去该属性变化的一个取值过程,这里只设置了一个参数,即变化到300。当然,与视图动画一样,也可以给属性动画设置显示时长、差值器等属性,这些参数与在视图动画中的设置方法类似。

    不过,在使用ObjectAnimator的时候,有一点非常重要,那就是要操纵的属性必须具有get、set方法,不然ObjectAnimator就无法起效。下面就是一些常用的可以直接使用属性动画的属性值。

    • translationX和translationY:这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。
    • rotation、rotationX和rotationY:这三个属性控制View对象围绕支点进行2D和3D旋转。
    • scaleX和scaleY:这两个属性控制着View对象围绕它的支点进行2D缩放。
    • pivotX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。
    • x和y:这是两个简单实用的属性,它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX、translationY值的累计和。
    • alpha:它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。

    由以上可知,视图动画所实现的动画效果,在这里基本都已经包含了。

    那么如果一个属性没有get、set方法,属性动画是不是就束手无策了呢?答案当然是否定的,Google在应用层提供了两种方案来解决这个问题,一个是通过自定义一个属性类或者包装类,来间接地给这个属性增加get、set方法;或者通过ValueAnimator来实现,ValueAnimator在后面的内容中会讲到,这里先来看看如何使用包装类的方法给一个属性增加get、set方法,代码如下所示。

    1. private static class WrapperView {
    2.  
    3. private View mTarget;
    4.  
    5. public WrapperView(View target) {
    6. mTarget = target;
    7. }
    8. public int getWidth() {
    9. return mTarget.getLayoutParams().width;
    10. }
    11. public void setWidth(int width) {
    12. mTarget.getLayoutParams().width = width;
    13. mTarget.requestLayout();
    14. }
    15. }

    通过以上代码,就给一个属性包装了一层,并给它提供了get、set方法。使用时只需要操纵包装类就可以间接调用到get、set方法了,代码如下所示。

    1. ViewWrapper wrapper = new ViewWrapper(mButton);
    2. ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();

    7.2.2 PropertyValuesHolder" class="reference-link">7.2.2 PropertyValuesHolder

    类似视图动画中的AnimationSet,在属性动画中,如果针对同一个对象的多个属性,要同时作用多种动画,可以使用PropertyValuesHolder来实现。比如举例的平移动画,如果需要在平移的过程中,同时改变X、Y轴的缩放,可以这样实现,代码如下所示。

    1. PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 300f);
    2. PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
    3. PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
    4. ObjectAnimator.ofPropertyValuesHolder(view, pvh1, pvh2, pvh3)
    5. .setDuration(1000).start();

    在代码中,分别使用PropertyValuesHolder对象来控制translationX、scaleX、scaleY这三个属性,最后调用ObjectAnimator.ofPropertyValuesHolder方法实现多属性动画的共同作用,整个实现方法非常类似AnimationSet的使用。

    7.2.3 ValueAnimator" class="reference-link">7.2.3 ValueAnimator

    ValueAnimator在属性动画中占有非常重要的地位,虽然不像ObjectAnimator那样耀眼,但它却是属性动画的核心所在,ObjectAnimator也是继承自ValueAnimator。

    1. public final class ObjectAnimator extends ValueAnimator

    ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程,ValueAnimator的一般使用方法如下所示,通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变换,从而完成动画的变换。

    1. ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
    2. animator.setTarget(view);
    3. animator.setDuration(1000).start();
    4. animator.addUpdateListener(new AnimatorUpdateListener() {
    5. @Override
    6. public void onAnimationUpdate(ValueAnimator animation) {
    7. Float value = (Float) animation.getAnimatedValue();
    8. // TODO use the value
    9. }
    10. });

    7.2.4 动画事件的监听" class="reference-link">7.2.4 动画事件的监听

    一个完整的动画具有Start、Repeat、End、Cancel四个过程,通过Android提供了接口,可以很方便地监听到这四个事件,代码如下所示。

    1. ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
    2. anim.addListener(new AnimatorListener() {
    3. @Override
    4. public void onAnimationStart(Animator animation) {
    5. }
    6. @Override
    7. public void onAnimationRepeat(Animator animation) {
    8. }
    9. @Override
    10. public void onAnimationEnd(Animator animation) {
    11. }
    12. @Override
    13. public void onAnimationCancel(Animator animation) {
    14. }
    15. });
    16. anim.start();

    当然,大部分的时候,我们都只关心onAnimationEnd事件,所以Android也提供了一个AnimatorListenerAdapter来让我们选择必要的事件进行监听,代码如下所示。

    1. anim.addListener(new AnimatorListenerAdapter() {
    2. @Override
    3. public void onAnimationEnd(Animator animation) {
    4. }
    5. });

    7.2.5 AnimatorSet" class="reference-link">7.2.5 AnimatorSet

    对于一个属性同时作用多个属性动画效果,前面已经用PropertyValuesHolder实现了这样的效果。而AnimatorSet不仅能实现这样的效果,同时也能实现更为精确的顺序控制。同样是实现上面使用PropertyValuesHolder演示的那个动画效果,如果使用AnimatorSet来实现,那么代码如下所示。

    1. ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, " translationX ", 300f);
    2. ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f, 1f);
    3. ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, " scaleY ", 1f, 0f, 1f);
    4. AnimatorSet set = new AnimatorSet();
    5. set.setDuration(1000);
    6. set.playTogether(animator1, animator2, animator3);
    7. set.start();

    在属性动画中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play(). with()、defore()、after()这些方法来控制多个动画的协同工作方式,从而做到对动画播放顺序的精确控制。

    7.2.6 在XML中使用属性动画" class="reference-link">7.2.6 在XML中使用属性动画

    属性动画同视图动画一样,也可以直接写在XML文件中,代码如下所示。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:duration="1000"
    4. android:propertyName="scaleX"
    5. android:valueFrom="1.0"
    6. android:valueTo="2.0"
    7. android:valueType="floatType" >
    8. </objectAnimator>

    可以发现,属性动画与视图动画在XML文件中的写法很相似。

    在程序中使用XML定义的属性动画也非常简单,代码如下所示。

    1. public void scaleX(View view) {
    2. Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
    3. anim.setTarget(mMv);
    4. anim.start();
    5. }

    7.2.7 View的animate方法" class="reference-link">7.2.7 View的animate方法

    在Android 3.0之后,Google给View增加了animate方法来直接驱动属性动画,代码如下所示,可以发现,其实animate方法可以认为是属性动画的一种简写方式。

    1. view.animate()
    2. .alpha(0)
    3. .y(300)
    4. .setDuration(300)
    5. .withStartAction(new Runnable() {
    6. @Override
    7. public void run() {
    8. }
    9. })
    10. .withEndAction(new Runnable() {
    11.  
    12. @Override
    13. public void run() {
    14. runOnUiThread(new Runnable() {
    15. @Override
    16. public void run() {
    17. }
    18. });
    19. }
    20. }).start();