Android 开发艺术探索笔记 第七章 Android动画深入分析

View动画

view动画的种类

View动画的四种变换效果对应Animation的四个子类,既可以通过XML来定义,也可以通过代码来创建。

  1. TranslateAnimation 平移动画
  2. ScaleAnimation 缩放动画
  3. RotateAnimation 旋转动画
  4. AlphaAnimation 透明度动画

set标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且它的内部也是可以嵌套其它动画集合的,它的两个属性的含义如下:
android:interpolator:表示动画集合使用的插值器,默认为加速减速插值器
android:shareInterpolator:表示集合中的动画是否和集合共享同一个插值器

translate表示平移动画,对应TranslateAnimation类,可以使一个View在水平和竖直的方向完成平移的动画效果,属性含义如下:

android:fromXDelta :表示x的起始值,比如0;
android:toXDelta :表示x的结束值,比如100;
android:fromYDelta :表示y的起始值;
android:fromYDelta :表示x的结束值;

scale标签表示缩放动画,对应ScaleAnimation,它可以使View具有放大或者缩小的动画效果,属性含义如下:

android:fromXScale :水平方向缩放的起始值,比如0.5;
android:toXScale :水平方向缩放的结束值,比如1.2;
android:fromYScale :竖直方向缩放的起始值;
android:toYScale :竖直方向缩放的结束值;
android:pivotX :缩放的轴点的x坐标,它会影响缩放的效果;
android:pivotY :缩放的轴点的y坐标,它会影响缩放的效果;
上面描述的轴点概念,默认情况下轴点是View的中心点,这个时候在水平方向进行缩放的话会导致View向左右两个方向同时进行缩放,如果设为View的右边界,那么View只会向左边进行缩放。

rotate标签表示旋转动画,对于RotateAnimation,它可以使View具有旋转的动画效果,属性含义如下:

android:fromDegrees :旋转开始的角度,比如0;
android:toDegrees :旋转结束的角度,比如180;
android:pivotX :旋转的轴点的x坐标;
android:pivotY :旋转的轴点的y坐标;

alpha标签表示透明度动画,对应AlphaAnimation,它可以改变View的透明度,它的属性含义如下:

android:fromAlpha :表示透明度的起始值,比如0.1;
android:toAlpha :表示透明度的结束值,比如1;

View还有一些常用的属性,如下:

android:duration :动画的持续时间;
android:fillAfter :动画结束以后View是否停留在结束位置,true表示View停留在结束位置,false则不停留;

自定义view动画

自定义动画需要继承Animation这个抽象类,然后重写它的initialize和applyTransformation方法,在initialize做一些初始方法,在applyTransformation中进行相应的矩阵变换即可,很多时候需要采用Camera来简化矩阵变换的过程。

帧动画

帧动画是顺序播放一组预先定义好的图片,系统提供了另外一个类AnimationDrawable来使用帧动画。首先在xml中定义,如下所示:


<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item
        android:drawable="@mipmap/lottery_1"
        android:duration="200" />
    <item
        android:drawable="@mipmap/lottery_2"
        android:duration="200" />
    <item
        android:drawable="@mipmap/lottery_3"
        android:duration="200" />

animation-list>

然后将上述的Drawable作为View的背景并通过Drawable来播放动画即可:

        Button mButton = (Button) findViewById(R.id.button1);
        mButton.setBackgroundResource(R.drawable.frame_animation);
        AnimationDrawable drawable = (AnimationDrawable) mButton.getBackground();
        drawable.start();

帧动画的使用比较简单,但是比较容易引起OOM,所以在使用帧动画的时候尽量避免使用过多尺寸较大的图片。

View动画的特殊使用场景

LayoutAnimation

LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当它的子元素出场时都会具有这种动画效果,这种效果常常被用于ListView上。属性的含义如下:

android:delay:表示子元素开始动画的时间延迟,比如子元素入场动画的事件周期为300ms,那么0.5表示每个子元素都需要150ms才能播放入场动画,第一个子元素延迟150ms,第二个子元素延迟300ms,一次类推。

android:animationOrder:表示子元素动画的顺序:

  1. normal:表示顺序显示
  2. reverse:表示逆向显示
  3. random:随机播放入场动画

    android:animation为子元素指定具体的入场动画

Activity的切换效果

Activity有默认的切换效果,但是这个效果我们可以自定义,主要用到overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在startActivity(intent)或者finish之后被调用才有效,参数含义如下:
1. enterAnim——Activity被打开时,所需的动画资源的id
2. exitAnim——Activity被暂停时,所需的动画资源的id
当启动一个Activity时,可以按照如下的方式为其添加自定义的切换效果:

    Intent intent = new Intent(this, TestActivity.class);
    startActivity(intent);
    overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);

当Activity退出时,可以为其指定自己的切换效果,如下所示:

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
    }

overridePendingTransition这个方法必须位于startActivity或者finish的后面,否则动画效果不起作用。
Fragment也可以添加动画,需要引入v4包,我们可以通过FragmentTranscation中的setCustomAnimations()方法来添加切换动画,切换动画需要view动画,不能采用属性动画。

属性动画

使用属性动画

属性动画是API 11新加入的特性,和View动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至还可以没有对象。属性动画有ValueAnimator、ObjectAnimator和AnimationSet等概念,如何使用属性动画,下面举几个例子:
改变一个对象的translationY,让其沿着Y轴向上平移一段距离:

ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight());

改变一个对象的背景色属性,改变View的背景色,下面的动画可以让背景色在3秒内实现从0xFFFF8080到0xFF8080FF的渐变,动画无限循环而且或有反转的效果。

ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", 0xFFFF8080, 0xFF8080FF);
            colorAnim.setDuration(3000);
            colorAnim.setEvaluator(new ArgbEvaluator());
            colorAnim.setRepeatCount(ValueAnimator.INFINITE);
            colorAnim.setRepeatMode(ValueAnimator.REVERSE);
            colorAnim.start();

动画集合,5秒对View的旋转、平移、缩放和透明度都进行了改变。

 AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(mBlueBall, "rotationX", 0, 360),
                ObjectAnimator.ofFloat(mBlueBall, "rotationY", 0, 180),
                ObjectAnimator.ofFloat(mBlueBall, "rotation", 0, -90),
                ObjectAnimator.ofFloat(mBlueBall, "translationX", 0, 90),
                ObjectAnimator.ofFloat(mBlueBall, "translationY", 0, 90),
                ObjectAnimator.ofFloat(mBlueBall, "scaleX", 1, 1.5f),
                ObjectAnimator.ofFloat(mBlueBall, "scaleY", 1, 0.5f),
                ObjectAnimator.ofFloat(mBlueBall, "alpha", 1, 0.25f, 1)
        );
        set.setDuration(5 * 1000).start();

标签的android:ordering 有两个可选值:together表示动画集合中的子动画同时播放,sequentially则表示动画集合中的子动画按照前后顺序依次播放,默认值是together。

对于ObjectAnimator标签的各个属性含义如下,animator属性比ObjectAnimator少了一个android:propertyName的属性,其它一样:

  • android:propertyName——表示属性动画的作用对象的属性的名称;
  • android:duration——表示动画的时长;
  • android:valueFrom——表示属性的起始值;
  • android:valueTo——表示属性的结束值;
  • android:startOffset——表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画;
  • android:repealCount——表示动画的重复次数,默认值为0,其中-1表示无限循环;
  • android:repealMode——表示动画的重复模式,有两个选项:“restart”和“reverse”,分别表示连续重复和逆向重复;
  • android:valueType——表示android:propertyName 所指定的属性的类型,有“intType”和“floatType”两个可选项,分别表示属性的类型为整型和浮点型。如果表示的是颜色,那么不需要指定android:valueType ,系统会自动对颜色类型的属性进行处理;

理解插值器和估值器

TimeInterpolator为时间插值器,作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有:

  • LinearInterpolator线性插值器:匀速动画
  • AccelerateDecelerateInterpolator加速减速插值器:动画两头慢中间快
  • DecelerateInterpolator减速插值器:动画越来越慢

TypeEvalutior为类型估值算法,也叫估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有:IntEvaluator针对整型属性、FloatEvaluator针对浮点型属性和ArgbEvaluator针对color属性。

属性动画要求对象的该属性有set方法和get方法。插值器除了系统提供外,我们还可以自定义,我们只需要派生一个类实现接口就可以了,自定义插值器需要实现Interpolator或者TimeInterpolator,自定义估值算法需要实现TypeEvaluator。另外就是如果要对其它烈性做动画,必须要实现自定义类型估值算法。

属性动画的监听器

属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:
AnimatorUpdateListener和AnimatorListener。
AnimatorListener可以监听动画的开始、结束、取消以及重复播放,定义如下:

 public static interface AnimationListener {        
        void onAnimationStart(Animator animation);
        void onAnimationEnd(Animator animation);
        void onAnimationCancel(Animator animation);
        void onAnimationRepeat(Animator animation);
    }

AnimatorUpdateListener比较特殊,它会监听整个动画过程,动画是由许多帧组成,每播放一帧,onAnimationUpdate就会被调用一次,利用这个特性,我们可以做很多事情。

public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);
    }

对任意属性做动画

属性动画要求动画的作用的对象提供改属性的get和set方法,属性动画根据外界传递的改属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,随着时间的推移,传递的值越来越接近最终值。我们对Object的属性abc做动画,同时满足两个条件:

  1. object必须要提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,系统去取abc属性的初始值,这条不满足,程序直接crash。
  2. object的setAbc对属性abc所做的改变必须能通过某种方法反映出来,比如带来UI改变之类的,不满足不会crash但是会动画无效果。

我们给Button增加一个动画,让这个Button的宽度从当前的宽度增加到500px。
我们调用属性动画,如下所示:

 ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();

上述代码运行无效果,因为是随便传递的一个属性,轻则没动画效果,重则程序直接crash。
针对上述问题,官方文档给出我们有3中解决办法:

  1. 给你的对象加上get和set方法,如果你有权限的话
  2. 用一个类来包装原始对象,间接为其提供get和set方法
  3. 采用valueAnimator,监听动画过程,自己实现属性的改变

方案一无法给Button增加权限,因为这是Android SDK内部实现的。
方案二用一个类来包装原始对象,间接为其提供get和set方法,代码如下:

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

    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();
        }
    }

提供一个ViewWrapper类专门用来包装View,具体到本例是包装Button。然后我们对ViewWrapper的width属性做动画,在setWidth内部修改target的宽度,一个间接的属性动画就完成了。

方案三采用ValueAnimator,监听动画过程,自己实现属性的改变。ValueAnimator本身不作用与任何对象,直接使用没有任何动画效果,但是它可以对一个值做动画,我们可以监听其动画过程,在动画过程中修改我们对象的属性值,相当于我们的对象做了动画,例子如下:

private void performAnimate(final View target, final int start, final int end){
       ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
       valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           //持有一个IntEvaluator对象,方便下面估值使用
           private IntEvaluator mEvaluator = new IntEvaluator();
           @Override
           public void onAnimationUpdate(ValueAnimator animator) {
               //获取当前动画的进度值,整型, 1-100之间
               int currentValue = (Integer) animator.getAnimatedValue();

               //获得当前进度占整个动画过程的比例,浮点型,0-1之间
               float fraction = animator.getAnimatedFraction();

               //直接调用整型估值器,通过比例计算出宽度,然后设置给button
               target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
               target.requestLayout();
           }
       });
       valueAnimator.setDuration(5000).start();
   }

   performAnimate(mButton, mButton.getWidth(), 500);

上述过程很简单,使用了整型估值器IntEvaluator的内部实现。

属性动画的工作原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法。每次传递给set方法的值都不一样,随着时间的推移,所传递的值最终越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,系统要获取属性的初始值。详细分析可以看本书作者的一篇文章Android源码分析—属性动画的工作原理

使用动画的注意事项

  1. OOM问题
    帧动画中图片数量较多且图片较大是容易出现OOM,尤其注意,避免使用帧动画。
  2. 内存泄露
    属性动画中有无限循环的动画,动画需要在Activity退出时及时停止,否则将导致Activity无法释放而造成内存泄漏,view动画不存在此问题。
  3. View动画问题
    View动画是对view的影像做动画,并不是真正的改变View的状态,会出现动画完成后View无法隐藏的情况,即setVisibility(View.GONE)失效了,这个时候需要调用view.cleanAnimation()清除view动画即可解决此类问题。
  4. 不要使用px
    尽量使用dp,使用px会导致不同设备上有不同的效果。
  5. 动画元素的交互
    属性动画的点击事件触发位置位移动后的位置,view动画依然为原始位置。
  6. 硬件加速
    建议开启硬件加速,提高动画的流畅性。

你可能感兴趣的:(Android开发艺术探索,读书笔记)