Android 动画深入分析

Android 的动画可以分为三种:View 动画、帧动画和属性动画。

View 动画

View 动画的作用对象是 View,它支持四种动画效果:平移动画、缩放动画、旋转动画和透明度动画。帧动画也属于 View 动画,只是表现形式不一样。

View 动画的种类
  1. View 的四种动画效果对应着 Animation 的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation 和 AlphaAnimation。
  2. View 动画既可以通过 XML 来定义,也可以通过代码来动态创建,建议采用 XML 的方式,因为可读性更好。
名称 标签 子类 效果
平移动画 <translate> TranslateAnimation 移动 View
缩放动画 <scale> ScaleAnimation 放大或缩小 View
旋转动画 <rotate> RotateAnimation 旋转 View
透明度动画 <alpha> AlphaAnimation 改变 View 的透明度
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
30
31
32
<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" // 动画结束后 View 是否停留在结束位置
android:interpolator="@anim/interpolator_resourse" // 动画集合所采用的插值器
android:shareInterpolator="true" // 集合中的动画是否和集合共享同一个插值器>

<alpha // 透明度动画
android:fromAlpha="1.0" // 透明度的起始值
android:toAlpha="0.0" // 透明度的结束值
android:duration="10000" // 动画持续时间 />

<rotate // 旋转动画
android:fromDegrees="300" // 旋转开始时的角度
android:toDegrees="-360" // 旋转结束时的角度
android:pivotX="10%" // 旋转轴点的 X 坐标
android:pivotY="100%" // 旋转轴点的 Y 坐标 />

<scale // 缩放动画
android:fromXScale="0.0" // 水平方向缩放的起始值
android:toXScale="1.5" // 水平方向缩放的结束值
android:fromYScale="0.0" // 竖直方向缩放的起始值
android:toYScale="1.5" // 竖直方向缩放的结束值
android:pivotX="50%" // 缩放轴点的 X 坐标
android:pivotY="50%" // 缩放轴点的 Y 坐标 />

<translate // 平移动画
android:fromXDelta="320" // X 坐标的起始值
android:toXDelta="0" // X 坐标的结束值
android:fromYDelta="480" // Y 坐标的起始值
android:toYDelta="0" // Y 坐标的结束值 />

</set>
  1. 应用 XML 动画:

    1
    2
    Animation animation = AnimationUtils.loadAnimation(this, R.anim.animation);
    view.startAnimation(animation);
  2. 代码创建动画:

    1
    2
    3
    AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
    alphaAnimation.setDuration(300)
    view.startAnimation(alphaAnimation);
自定义 View 动画

继承 Animation 抽象类,然后重写它的 initialize 和 applyTransformation 方法,在 initialize 方法中做一些初始化的工作,在 applyTransformation 方法中进行相应的矩阵变换即可。Ps. 很多时候使用 Camera 来简化矩阵变化过程。

帧动画

帧动画是顺序播放一组预先定义好的图片,类似于电影播放。系统提供了一个 AnimationDrawable 来使用帧动画。
帧动画 XML 中使用 标签来定义,需要注意的是帧动画容易引起 OOM,应尽量避免使用尺寸较大的图片。

View 动画的特殊使用场景

LayoutAnimation

LayoutAnimation 作用于 ViewGroup,为 ViewGroup 指定一个动画,它的子元素出场时都会具有这种动画效果。
一般遵循如下步骤:

  1. 定义 LayoutAnimation:

    1
    2
    3
    4
    5
    <layoutAnimation
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5" // 表示子元素开始动画的时间延迟,需要乘以 Duration
    android:animationOrder="reverse" // 表示子元素动画的顺序,normal 表示顺序显示;reverse 表示排在后面先开始动画;random 表示随机
    android:animation="@anim/anim_item"// 为子元素指定具体的入场动画 />
  2. 为子元素指定具体的入场动画:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <alpha
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />

    <translate
    android:fromXDelta="500"
    android:toXDelta="0" />

    </set>
  3. 为 ViewGroup 指定 android:layoutAnimation="@anim/anim_layout" 属性。当然也可以使用代码来设置:

    1
    2
    3
    4
    5
    Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
    LayoutAnimationController controller = new LayoutAnimationController(animation);
    controller.setDelay(0.5f);
    controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
    listView.setLayoutAnimation(controller);
Activity 的切换效果

使用 overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim); 来自定义切换效果,注意:这个方法必须在 startActivity(intent) 或者 finish() 之后调用才能生效。

属性动画

属性动画是 API 11 新加入的特性。

使用属性动画

属性动画可以多任意对象的属性进行动画而不仅仅是 View,动画默认时间间隔300ms,默认帧率 10ms/帧。比较常用的几个动画类是:ValueAnimator、ObjectAnimator 和 AnimatorSet。

  1. 几个示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 改变一个对象 view 的 translationY 属性,让其沿着 Y 轴向上平移一段距离。
    ObjectAnimator.ofFloat(view, "translationY", -getActionBar().getHeight()).start();
    ...
    // 动态改变一个对象 view 的背景色。
    ValueAnimator colorAnim = ObjectAnimator.ofInt(view, "backgroundColor", 0xFFFF8080, 0xFF8080FF);
    colorAnim.setDuration(3000);
    colorAnim.setEvaluator(new ArgbEvaluator());
    colorAnim.setRepeatCount(ValueAnimator.INFINITE);
    colorAnim.setRepeatMode(ValueAnimator.REVERSE);
    colorAnim.start();
    ...
    // 动画集合
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.playTogether(
    ObjectAnimator.ofFloat(view, "translationX", 0, 90),
    ObjectAnimator.ofFloat(view, "translationY", 0, 90)
    );
    animatorSet.setDuration(1000).start();
  2. 使用 XML 来定义属性动画

    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <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>

最后通过 Java 代码来使用属性动画:

1
2
3
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();

理解插值器和估值器
  1. TimeInterpolator 翻译为时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置有 LinearInterpolator、AccelerateInterpolator 等等插值器。
  2. TypeEvaluator 翻译为类型估值算法,也叫估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置有 IntEvaluator、FloatEvaluator 和 ArgbEvaluator 等估值器。
  3. 自定义插值器需要实现 Interpolator 或 TimeInterpolator 接口;自定义估值器需要实现 TypeEvaluator 接口。
属性动画的监听器

属性动画主要提供了两个接口用于监听:AnimatorListener 和 AnimatorUpdateListener。

  1. AnimatorListener 可以监听动画的开始、结束、取消以及重复播放等等。系统还提供了 AnimatorListenerAdater 适配器类,我们可以选择性的监听我们需要的方法。
  2. AnimatorUpdateListener 监听整个动画过程,没播放一帧 onAnimationUpdate 就会被调用一次。
对任意属性做动画
  1. 属性动画要求动画作用的对象提供该属性的 get 和 set 方法,要同时满足以下两个条件:
    • object 必须要提供 setAbc 的方法,如果动画的时候没有传递初始值,那么还要提供 getAbc 的方法,因为系统要去取 abc 属性的初始值,如果不满足这个条件,程序会 Crash。
    • object 的 setAbc 的方法对属性 abc 所做的改变必须能通过某种方式反映出来,比如会带来 UI 的改变,如果不满足这个条件,动画将会没有效果,但程序不会 Crash。
  2. 属性动画有时没有效果的解决办法:

    • 给你的对象加上 set 和 get 方法,如果你有权限的话
    • 用一个类来包装原始对象,间接为期提供 set 和 get 方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      private static class ViewWrapper{
      private View target;

      public ViewWrapper(View target){
      this.target = target;
      }

      public int getWidth(){
      return target.getWidth();
      }

      public void setWidth(int width){
      target.getLayoutParams().width = width;
      target.requestLayout();
      }
      }
    • 采用 ValueAnimator,监听动画过程,自己实现属性的改变

属性动画的工作原理

属性动画要求动画作用的对象提供该属性的 set 方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用 set 方法。每次传递给 set 方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供 get 方法,因为系统要去获取属性的初始值。

使用动画的注意事项

  1. OOM 问题:帧动画,当图片较多或图片较大时极易出现 OOM,所以应避免使用帧动画。
  2. 内存泄漏:Activity 退出时应及时停止动画。
  3. 兼容性问题:属性动画在 Android 3.0 以下存在兼容问题,应做好适配工作。
  4. View 动画的问题:View 动画是对 View 的影像做动画,并不是真正改变 View 的状态。有时动画后 View 出现显示异常时,调用 view.clearAnimation() 清除下即可。
  5. 不要是 px:在动画时尽量使用 dp,避免适配性问题。
  6. 动画元素的交互:在 Android 3.0 以下系统,不管是 View 动画还是属性动画,新位置均无法触发单击时间;从 Android 3.0 开始,属性动画的单击事件触发位置为移动后的位置,但是 View 动画仍然在原位置。
  7. 硬件加速:在动画过程,建议开启硬件加速,这样会提供动画的流畅性。

本章节配套源码戳我