Android中的View动画和属性动画

概述

在Android开发的过程中,View的变化是很常见的,如果View变化的过程没有动画来过渡而是瞬间完成,会让用户感觉很不友好,因此学习好Android系统中的动画框架是很重要的。
Android系统提供了两个动画框架:属性动画框架和View动画框架。 两个动画框架都是可行的选项,但是属性动画框架通常是首选的使用方法,因为它更灵活,并提供更多的功能。 除了这两个框架,还可以使用Drawable动画(即逐帧动画,AnimationDrawable),它允许你加载Drawable资源并逐帧地显示它们。
1> View动画框架
View动画框架是旧的框架,只能用于Views。 比较容易设置和能满足许多应用程序的需要。View动画框架中一共提供了AlphaAnimation(透明度动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)、TranslateAnimation(平移动画)四种类型的补间动画;并且View动画框架还提供了动画集合类(AnimationSet),通过动画集合类(AnimationSet)可以将多个补间动画以组合的形式显示出来。View动画框架中动画相关类的继承关系如下图所示:


Animation类和其子类

2> 属性动画框架
与属性动画相比View动画存在一个缺陷,View动画改变的只是View的显示,而没有改变View的响应区域,并且View动画只能对View做四种类型的补间动画。因此Google在Android3.0(API级别11)及其后续版本中添加了属性动画框架,从名称中就可以知道只要某个类具有属性(即该类含有某个字段的set和get方法),那么属性动画框架就可以对该类的对象进行动画操作(其实就是通过反射技术来获取和执行属性的get,set方法),同样属性动画框架还提供了动画集合类(AnimatorSet),通过动画集合类(AnimatorSet)可以将多个属性动画以组合的形式显示出来。属性动画框架中动画相关类的继承关系如下图所示:

Animator类及其子类

3> Drawable 动画
可绘制动画通过一个接一个地加载一系列Drawable资源来创建动画。 这是一个传统的动画,它是用一系列不同的图像创建的,按顺序播放,就像一卷电影;逐帧动画中动画相关类的继承关系如下图所示:


逐帧动画(AnimationDrawable)

预备知识

1. 时间插值器

对于补间动画:时间插值器(TimeInterpolator)的作用是根据时间流逝的百分比计算出动画进度的百分比。有了动画进度的百分比,就可以很容易的计算出动画开始的关键帧与将要显示的帧之间的差异(通过Transformation类的对象表示),下面展示TranslateAnimation类中如何根据动画进度的百分比计算出动画开始的关键帧与将要显示的帧之间的差异:

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    if (mFromXDelta != mToXDelta) {
        dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
    }
    if (mFromYDelta != mToYDelta) {
        dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
    }
    t.getMatrix().setTranslate(dx, dy);
}

上面源码中applyTransformation方法的第一个参数就是通过时间插值器(TimeInterpolator)获取的动画进度的百分比。
然后根据帧之间的差异绘制出将要显示的帧,以此类推从而形成动画的效果。

对于属性动画:时间插值器(TimeInterpolator)的作用是根据时间流逝的百分比计算出动画进度的百分比(即属性值改变的百分比)。

Android提供了常用的时间插值器如下表所示:

Interpolator class Resource ID
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator
AccelerateInterpolator @android:anim/accelerate_interpolator
AnticipateInterpolator @android:anim/anticipate_interpolator
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator
BounceInterpolator @android:anim/bounce_interpolator
CycleInterpolator @android:anim/cycle_interpolator
DecelerateInterpolator @android:anim/decelerate_interpolator
LinearInterpolator @android:anim/linear_interpolator
OvershootInterpolator @android:anim/overshoot_interpolator

上面列举的常用的时间插值器对应的类与时间插值器(TimeInterpolator)之间的继承关系如下图所示:



为了比较直观的感受到Android提供的常用时间插值器的效果,我通过程序绘制出了动画进度的百分比随着时间流逝的百分比变化的波形图(波形图的横向代表时间流逝的百分比,纵向代表动画进度的百分比,时间流逝的百分比我一共选取了11个值,分别为0、0.1、以此类推一直到1),如下所示:

AccelerateDecelerateInterpolator
AccelerateInterpolator
AnticipateInterpolator
AnticipateOvershootInterpolator
BounceInterpolator
CycleInterpolator
DecelerateInterpolator
LinearInterpolator
OvershootInterpolator

2. 类型估值器

类型估值器(TypeEvaluator)是针对于属性动画框架的,对于View动画框架是不需要类型估值器(TypeEvaluator)的。
类型估值器(TypeEvaluator)的作用是根据属性值改变的百分比计算出改变后的属性值。由于属性动画实际上作用的是对象的属性,而属性的类型是不同的,因此Android内置了一些常用的类型估值器来操作不同类型的属性,如下图所示:


TypeEvaluator及其子类

View动画框架

使用View动画框架可以在Views上执行补间动画。 补间动画是指只要指定动画的开始、动画结束的"关键帧",而动画变化的"中间帧"由系统计算并补齐;无论动画怎样改变View的显示区域(移动或者改变大小),持有该动画的View的边界不会自动调整来适应View的显示区域, 即使如此,该动画仍将被绘制在超出其视图边界并且不会被剪裁, 但是,如果动画超过父视图的边界,则会出现裁剪。在Android中的View动画框架中一共提供了AlphaAnimation(透明度动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)、TranslateAnimation(平移动画)四种类型的补间动画。

FILE LOCATION:
    res/anim/filename.xml
    The filename will be used as the resource ID.
COMPILED RESOURCE DATATYPE:
    Resource pointer to an Animation.
RESOURCE REFERENCE:
    In Java: R.anim.filename
    In XML: @[package:]anim/filename

语法:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

<set>标签表示补间动画的集合,对应于AnimationSet类,所以上面语法中的<set>标签可以包含多个补间动画的标签;并且补间动画的集合中还可以包含补间动画的集合。
<set>标签的相关属性如下所示:

android:interpolator
Interpolator resource. 设置动画集合所采用的插值器,默认值为@android:anim/accelerate_decelerate_interpolator

android:shareInterpolator
Boolean. 表示集合中的动画是否共享集合的插值器。当值为true且集合没有设置插值器,
此时集合中的动画就会使用默认的插值器@android:anim/accelerate_decelerate_interpolator,
但是你也可以为集合中的动画单独指定所需的插值器。

AlphaAnimation(透明度动画)

上面语法中的<alpha>标签代表的就是透明度动画,顾名思义透明度动画就是通过不断改变View的透明度实现动画的效果。
<alpha>标签相关属性如下所示:

android:fromAlpha
Float. 设置透明度的初始值,其中0.0是透明,1.0是不透明的。

android:toAlpha
Float. 设置透明度的结束值,其中0.0是透明,1.0是不透明的。

举例如下:
首先展示一下动画效果:



实现代码如下:
首先通过xml定义一个透明度动画(AlphaAnimation),代码如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <alpha
        android:duration="2000"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />
</set>

接着将上面定义的透明度动画(AlphaAnimation)设置为ImageView的src,然后通过startAnimation播放动画,代码如下:

<ImageView
    android:id="@+id/image"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_gravity="center"
    android:src="@drawable/second_pic"
    android:scaleType="centerCrop"
    android:alpha="1.0" >
</ImageView>

loadAnimation = AnimationUtils.loadAnimation(getActivity(),
        R.anim.base_animation_alpha);
loadAnimation.setFillAfter(true);
image.startAnimation(loadAnimation);

ScaleAnimation(缩放动画)

上面语法中的<scale>标签代表的就是缩放动画,顾名思义缩放动画就是通过不断缩放View的宽高实现动画的效果。
<scale>标签相关属性如下所示:

android:fromXScale
Float. 水平方向缩放比例的初始值,其中1.0是没有任何变化。
android:toXScale
Float. 水平方向缩放比例的结束值,其中1.0是没有任何变化。
android:fromYScale
Float. 竖直方向缩放比例的初始值,其中1.0是没有任何变化。
android:toYScale
Float. 竖直方向缩放比例的结束值,其中1.0是没有任何变化。
android:pivotX
Float. 缩放中心点的x坐标
android:pivotY
Float. 缩放中心点的y坐标

举例如下:
首先展示一下动画效果:



实现代码如下:
首先通过xml定义一个缩放动画(ScaleAnimation),代码如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <scale
        android:duration="5000"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0.0"
        android:toYScale="0.0" />
</set>

接着将上面定义的缩放动画(ScaleAnimation)设置为ImageView的src,然后通过startAnimation播放动画,代码如下:

ImageView对应的layout代码同上,这里就不赘叙了。

loadAnimation = AnimationUtils.loadAnimation(getActivity(),
        R.anim.base_animation_scale);
loadAnimation.setFillAfter(true);
image.startAnimation(loadAnimation);

TranslateAnimation(平移动画)

上面语法中的<translate>标签代表的就是平移动画,顾名思义平移动画就是通过不断移动View的位置实现动画的效果。
<translate>标签相关属性如下所示:

android:fromXDelta
Float or percentage. 移动起始点的x坐标. 表示形式有三种:
1 相对于自己的左边界的距离,单位像素值。(例如 "5")
2 相对于自己的左边界的距离与自身宽度的百分比。(例如  "5%")
3 相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:toXDelta
Float or percentage. 移动结束点的x坐标. 表现形式同上
android:fromYDelta
Float or percentage. 移动起始点的y坐标. 表示形式有三种:
1 相对于自己的上边界的距离,单位像素值。(例如 "5")
2 相对于自己的上边界的距离与自身高度的百分比。(例如  "5%")
3 相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p")
android:toYDelta
Float or percentage. 移动结束点的y坐标. 表现形式同上

举例如下:
首先展示一下动画效果:


实现代码如下:
首先通过xml定义一个平移动画(TranslateAnimation),代码如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="2000"
        android:fromXDelta="20"
        android:fromYDelta="20"
        android:toXDelta="100"
        android:toYDelta="100" />
</set>

接着将上面定义的平移动画(TranslateAnimation)设置为ImageView的src,然后通过startAnimation播放动画,代码如下:

ImageView对应的layout代码同上,这里就不赘叙了。

loadAnimation = AnimationUtils.loadAnimation(getActivity(),
        R.anim.base_aniamtion_translate);
loadAnimation.setFillAfter(true);
image.startAnimation(loadAnimation);

RotateAnimation(旋转动画)

上面语法中的<rotate>标签代表的就是旋转动画,顾名思义旋转动画就是通过不断旋转View实现动画的效果。
<rotate>标签相关属性如下所示:

android:fromDegrees
Float. 旋转初始的角度。
android:toDegrees
Float. 旋转结束的角度。
android:pivotX
Float or percentage. 旋转中心点x坐标,表示形式有三种:
1 相对于自己的左边界的距离,单位像素值。(例如 "5")
2 相对于自己的左边界的距离与自身宽度的百分比。(例如 "5%")
3 相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:pivotY
Float or percentage. 旋转中心点y坐标,表示形式有三种:
1 相对于自己的上边界的距离,单位像素值。(例如 "5")
2 相对于自己的上边界的距离与自身宽度的百分比。(例如 "5%")
3 相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p")

举例如下:
首先展示一下动画效果:


实现代码如下:
首先通过xml定义一个旋转动画(RotateAnimation),代码如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <rotate
        android:duration="1000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="+360" />
</set>

接着将上面定义的旋转动画(RotateAnimation)设置为ImageView的src,然后通过startAnimation播放动画,代码如下:

ImageView对应的layout代码同上,这里就不赘叙了。

loadAnimation = AnimationUtils.loadAnimation(getActivity(),
        R.anim.base_anmation_rotate);
image.startAnimation(loadAnimation);

补间动画(Tween animation)两个常用的特殊场景

1 通过布局动画(LayoutAnimation)给ViewGroup的子View指定入场动画。
举例如下:
首先展示一下动画效果:



实现代码如下:
首先通过xml定义ViewGroup的子View入场动画,代码如下:

res/anim/zoom_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator" >
  <scale
        android:duration="500"
        android:fromXScale="0.1"
        android:fromYScale="0.1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.0"
        android:toYScale="1.0" />
  <alpha
        android:duration="500"
        android:fromAlpha="0"
        android:toAlpha="1.0" />
</set>

接着为ListView设置布局动画(LayoutAnimation),代码如下所示:

LayoutAnimationController lac=new LayoutAnimationController(AnimationUtils.loadAnimation(this, R.anim.zoom_in));
lac.setDelay(0.5f);
lac.setOrder(LayoutAnimationController.ORDER_RANDOM);
mListView.setLayoutAnimation(lac);
//mListView.startLayoutAnimation(); //可以通过该方法控制动画在何时播放。

上面是通过java代码来为ListView设置布局动画(LayoutAnimation)的,其实通过xml也可以为ListView设置布局动画(LayoutAnimation),代码如下所示:

<ListView
    android:id="@+id/listView"
    android:layoutAnimation="@anim/anim_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</ListView>

res/anim/anim_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animationOrder="random"
    android:animation="@anim/zoom_in"/>

2 通过补间动画(Tween animation)为Activity自定义切换动画
Android系统为Activity设置了默认的切换动画,这个动画我们是可以进行自定义的。通过调用Activity类的overridePendingTransition(int enterAnim, int exitAnim)方法可以实现自定义Activity的切换动画,注意这个方法必须在startActivity和finish调用之后被调用,否者没有效果。

Drawable动画(逐帧动画)

逐帧动画是用来逐帧显示预先定义好的一组图片,类似于电影播放。对应于AnimationDrawable类。

FILE LOCATION:
    res/drawable/filename.xml
    The filename will be used as the resource ID.
COMPILED RESOURCE DATATYPE:
    Resource pointer to an AnimationDrawable.
RESOURCE REFERENCE:
    In Java: R.drawable.filename
    In XML: @[package:]drawable.filename

不同于补间动画,逐帧动画资源文件放在drawable文件夹下。
语法:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource_name"
        android:duration="integer" />
</animation-list>

<animation-list>标签用来包含逐帧动画的每一帧。
<animation-list>标签的相关属性如下所示:

android:oneshot
Boolean. 当设置为true时动画只会播放一次,否者会循环播放。

<item>用来表示逐帧动画的每一帧图片。
<item>标签的相关属性如下所示:

android:drawable
Drawable resource. 设置当前帧对应的Drawable 资源。
android:duration
Integer. 设置显示该帧的时间, 单位为毫秒(milliseconds)。

举例如下:
首先展示一下动画效果:


实现代码如下:
首先通过xml定义一个逐帧动画(AnimationDrawable),代码如下:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true" >
    <item
        android:drawable="@drawable/first_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/second_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/third_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/fourth_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/fifth_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/sixth_pic"
        android:duration="1000"/>
</animation-list>

然后将上面定义的AnimationDrawable作为View的背景并且通过AnimationDrawable来播放动画,代码如下:

image.setImageResource(R.drawable.anim_list);
AnimationDrawable animationDrawable = (AnimationDrawable) image.getDrawable();
animationDrawable.start();
//animationDrawable.stop(); //如果oneshot为false,必要时要停止动画

上面是通过xml定义一个逐帧动画,也可以完全通过代码来实现和上面相同效果,如下所示:

//代码定义、创建、执行动画
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.addFrame(getResources().getDrawable(R.drawable.first_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.second_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.third_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.fourth_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.fifth_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.sixth_pic), 1000);
animationDrawable.setOneShot(true);
image.setImageDrawable(animationDrawable);
animationDrawable.start();

注意:对于一些手机,在Activity的onCreate()方法中调用AnimationDrawable的start()方法不起效果,可能是由于播放动画的ImageView还没有准备好,此时就需要在onCreate之后的生命周期的方法中调用,比如onResume方法。

属性动画框架

与属性动画相比View动画存在一个缺陷,View动画改变的只是View的显示,而没有改变View的响应区域,并且View动画只能对View做四种类型的补间动画,因此Google在Android3.0及其后续版本中添加了属性动画框架。同样属性动画框架还提供了动画集合类(AnimatorSet),通过动画集合类(AnimatorSet)可以将多个属性动画以组合的形式显示出来。

下面是我关于属性动画框架工作原理的总结:
顾名思义只要某个类具有属性(即该类含有某个字段的set和get方法),那么属性动画框架就可以对该类的对象进行动画操作(其实就是通过反射技术来获取和执行属性的get,set方法),因此属性动画框架可以实现View动画框架的所有动画效果并且还能实现View动画框架无法实现的动画效果。属性动画框架工作原理可以总结为如下三步:
1 在创建属性动画时如果没有设置属性的初始值,此时Android系统就会通过该属性的get方法获取初始值,所以在没有设置属性的初始值时,必须提供该属性的get方法,否者程序会Crash。
2 在动画播放的过程中,属性动画框架会利用时间流逝的百分比获取属性值改变的百分比(即通过时间插值器),接着利用获取的属性值改变的百分比获取改变后的属性值(即通过类型估值器)。
3 通过该属性的set方法将改变后的属性值设置到对象中。

还要注意一点,虽然通过set方法改变了对象的属性值,但是还要将这种改变用动画的形式表现出来,否者就不会有动画效果,所以属性动画框架本身只是不断的改变对象的属性值并没有实现动画效果。

FILE LOCATION:
    res/animator/filename.xml
    The filename will be used as the resource ID.
COMPILED RESOURCE DATATYPE:
    Resource pointer to a ValueAnimator, ObjectAnimator, or AnimatorSet.
RESOURCE REFERENCE:
    In Java: R.animator.filename
    In XML: @[package:]animator/filename

语法:

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

在上面的语法中,<set>对应AnimatorSet(属性动画集合)类,<objectAnimator>对应ObjectAnimator类,<animator>标签对应ValueAnimator类;并且属性动画集合还可以包含属性动画集合。
<set>标签相关属性如下所示:

android:ordering
该属性有如下两个可选值:
together:表示动画集合中的子动画同时播放。
sequentially:表示动画集合中的子动画按照书写的先后顺序依次播放。
该属性的默认值是together。

通过ObjectAnimator实现属性动画

<objectAnimator>标签相关属性如下所示:

android:propertyName
String. Required. 属性动画作用的属性名称 
android:duration
int. 表示动画的周期,默认值为300毫秒
android:valueFrom
float, int, or color. 表示属性的初始值
android:valueTo
float, int, or color. Required. 表示属性的结束值
android:startOffset
int. 表示调用start方法后延迟多少毫秒开始播放动画
android:repeatCount
int. 表示动画的重复次数,-1代表无限循环,默认值为0,表示动画只播放一次。
android:repeatMode
表示动画的重复模式,该属性有如下两个可选值:
repeat:表示连续重复
reverse:表示逆向重复
android:valueType
Keyword.  表示android:propertyName所指定属性的类型,有intType和floatType两个可选项,
分别代表属性的类型为整型和浮点型,另外如果属性是颜色值,那么就不需要指定
android:valueType属性,Android系统会自动对颜色类型的属性做处理。
默认值为floatType。

利用ObjectAnimator实现与补间动画中4个实例相同的动画效果,代码如下:

//利用ObjectAnimator实现透明度动画
ObjectAnimator.ofFloat(mImageView, "alpha", 1, 0, 1)
        .setDuration(2000).start();
//利用AnimatorSet和ObjectAnimator实现缩放动画
final AnimatorSet animatorSet = new AnimatorSet();
image.setPivotX(image.getWidth()/2);
image.setPivotY(image.getHeight()/2);
animatorSet.playTogether(
        ObjectAnimator.ofFloat(image, "scaleX", 1, 0).setDuration(5000),
        ObjectAnimator.ofFloat(image, "scaleY", 1, 0).setDuration(5000));
animatorSet.start();
//利用AnimatorSet和ObjectAnimator实现平移动画
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
        ObjectAnimator.ofFloat(image, "translationX", 20, 100).setDuration(2000),
        ObjectAnimator.ofFloat(image, "translationY", 20, 100).setDuration(2000));
animatorSet.start();
//利用ObjectAnimator实现旋转动画
image.setPivotX(image.getWidth()/2);
image.setPivotY(image.getHeight()/2);
ObjectAnimator.ofFloat(image, "rotation", 0, 360)
        .setDuration(1000).start();

上面是通过代码的形式实现的属性动画,对于通过xml定义属性动画的方式不是很常用,因为属性的起始和结束值大多是在程序运行的时候动态获取的。大家可以根据上面的语法自己尝试一下。

通过ValueAnimator实现属性动画

<animator>标签相关属性如下所示:

与<objectAnimator>标签相比,除了没有android:propertyName属性和valueFrom 属性是Required的之外,
其他的属性都相同并且属性的作用也一样。

其实ValueAnimator类就是一个数值生成器,也就是没有上面关于属性动画框架工作原理的第1步和第3步,ObjectAnimator作为ValueAnimator的子类,实现了这两步。你只要给ValueAnimator提供一个初始值、结束值和周期时间,ValueAnimator就会按照属性动画框架工作原理的第2步中的步骤生成具有一定规则的数字。

无法使用属性动画或者属性动画不起作用的情况和解决方法

无法使用属性动画或者属性动画不起作用的情况如下:
1 该字段没有没有set和get方法
2 该属性的set方法仅仅改变了对象的属性值,但是没有将这种改变用动画的形式表现出来
解决方法如下:
1 如果你又权限的话,给这个字段添加get和set方法,比如在自定义View中。
2 使用一个包装类来封装该字段对应的类,间接为该字段提供get和set方法。
例如使ImageView从当前高度变化到600,代码如下所示:

ViewWrapper viewWrapper = new ViewWrapper(mImageView);
ObjectAnimator.ofInt(viewWrapper, "height", 600).setDuration(5000).start();

public class ViewWrapper {

    private View view;

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

    public int getHeight(){
        return view.getLayoutParams().height;
    }

    public void setHeight(int height){
        view.getLayoutParams().height = height;
        view.requestLayout();
    }
}

3 通过ValueAnimator实现动画效果
举例如下:
首先展示一下运行的效果:



实现代码如下:

if (mBtHiddenView.getVisibility() == View.GONE) {
    animateOPen(mBtHiddenView);
} else {
    animateClose(mBtHiddenView);
}


private void animateOPen(final View view) {
    view.setVisibility(View.VISIBLE);
    ValueAnimator valueAnimator = createDropAnimator(view, 0, mHiddenViewHeight);
    valueAnimator.setInterpolator(new AccelerateInterpolator());
    valueAnimator.setDuration(1000).start();
}

private void animateClose(final View view) {
    ValueAnimator valueAnimator = createDropAnimator(view, mHiddenViewHeight, 0);
    valueAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            // TODO Auto-generated method stub
            super.onAnimationEnd(animation);
            view.setVisibility(View.GONE);
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(1000).start();
}

private ValueAnimator createDropAnimator(final View view, int start, int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // TODO Auto-generated method stub
            LayoutParams params = view.getLayoutParams();
            params.height = (int) animation.getAnimatedValue();
            view.setLayoutParams(params);
        }
    });
    return valueAnimator;
}

属性动画常用的特殊场景

1> 属性动画可以为ViewGroup的子View的显示和隐藏设置过渡动画。Android系统中已经提供了默认过渡动画(在layout文件中将ViewGroup的animateLayoutChanges属性打开就可以使用系统提供的默认过渡动画)。Android系统中一共提供了如下所示的4种类型的过渡动画:

APPEARING 
当通过 设置子View的可见性为VISIBLE或者通过addView方法添加子View 来显示子View时,
子View就会执行该类型的动画。
该类型动画的周期为300毫秒,默认延迟为300毫秒。
DISAPPEARING
当通过 设置子View的可见性为GONE或者通过removeView方法移除子View 来隐藏子View时,
子View就会执行该类型的动画。
该类型动画的周期为300毫秒,默认延迟为0毫秒。
CHANGE_APPEARING
当显示子View时,所有的兄弟View就会立即依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒,然后才会执行显示子View的动画。
该类型动画的周期为300毫秒,默认延迟为0毫秒。
CHANGE_DISAPPEARING
当隐藏子View的动画执行完毕后,所有的兄弟View就会依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒。
该类型动画的周期为300毫秒,默认延迟为300毫秒。

注意 上面描述的都是系统默认的行为,我们可以做适当的改变。

如果感觉Android系统提供的过渡动画不够炫,你也可以自定义过渡动画,下面就举例说明一下如何自定义过渡动画,首先展示一下效果图:


布局相关代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <Button
        android:id="@+id/btn_add_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加图片" />
    <Button
        android:id="@+id/btn_remove_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="移除图片" />
</LinearLayout>
<LinearLayout
    android:id="@+id/ll_image"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="24dp"
    android:gravity="center_horizontal"
    android:animateLayoutChanges="true"
    android:orientation="vertical"/>
</LinearLayout>

在java代码中为id为ll_image的LinearLayout设置自定义的过渡动画,如下所示:

llImageView = (LinearLayout) root.findViewById(R.id.ll_image);

LayoutTransition transition = new LayoutTransition();

transition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_APPEARING, 0);

ObjectAnimator appearingAnimator = ObjectAnimator
        .ofPropertyValuesHolder(
                (Object) null,
                PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("alpha", 0, 1.0f));
transition.setAnimator(LayoutTransition.APPEARING, appearingAnimator);
transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
transition.setStartDelay(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));

ObjectAnimator disappearingAnimator = ObjectAnimator
        .ofPropertyValuesHolder(
                (Object) null,
                PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("alpha", 1.0f, 0));
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnimator);
transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setStartDelay(LayoutTransition.DISAPPEARING, 0);

transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));

llImageView.setLayoutTransition(transition);

上面代码依次对CHANGE_APPEARING、APPEARING、DISAPPEARING和CHANGE_DISAPPEARING类型的过渡动画进行了设置,下面就来分析常用的设置方法:

setStagger方法
当多个子View要执行同一个类型的动画时,就可以通过该方法来设置子View之间执行动画的间隙,
默认为0毫秒。

setAnimator方法
为指定类型的过渡动画设置自定义的属性动画。

setDuration方法
为指定类型的过渡动画设置执行动画的周期,默认为300毫秒。

setStartDelay方法
为指定类型的过渡动画设置延迟执行的时间,默认与过渡动画的类型相关,上面已经说过。

setLayoutTransition方法
为ViewGroup设置过渡动画。

经过上面代码的设置后,LinearLayout显示或者隐藏子View时就会执行相关的过渡动画,显示或者隐藏子View的代码如下所示:

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_add_image:{
            ImageView imageView = new ImageView(getContext());
            imageView.setImageResource(R.drawable.second_pic);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(200,200);
            llImageView.addView(imageView, 0, layoutParams);
        }
            break;
        case R.id.btn_remove_image:{
            int count = llImageView.getChildCount();
            if (count > 0) {
                llImageView.removeViewAt(0);
            }
        }
            break;
    }
}

2> Vector(矢量图)中的动画
可以参考我的另外一篇blog Android中的矢量图

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,549评论 15 534
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 1,021评论 0 7
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 993评论 0 24
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 3,293评论 4 20
  • 一、现代前端开发 1.1 ES6 —— 新一代的JavaScript标准 1.1.1 语法特性 const、let...
    杀破狼real阅读 847评论 0 3