动画效果不仅能带来视觉上的享受,还能保证交互的连贯性,使整个 APP 一举一动看上去更加优雅美观。在 Android 系统中,动画主要有两大类,一类是 Animation,即针对单个对象做出的一些效果;另一类是 Transitin,即两个场景或者说两个 UI 界面切换时的效果,可以理解为转场效果。其中 Animation 又可以分为 View Animation、Property Animation、Drawable Animation 和 Physics-based Animation。
在官方之前的文档中,View Animation 被分类为 Tween Animation(补间动画) 和 Frame-by-frame Animation(帧动画),而随着 API 21 中 AnimatedVectorDrawable 以及 Support Library 25.3.0 中 DynamicAnimation 的引入,动画分类也发生了变化。Frame Animation 与 AnimatedVectorDrawable 被归类为 Drawable Animation,Tween Animation 即 View Animation,新增了 Physics-based Animation。本篇文章只记录 View Animation 相关的内容。
View Animation 概览
先补充一下基础概念:
帧:最复古的动画表现形式应该是手翻书了,通过很多页相关联的图像快速翻动达到动画效果。手翻书中的某一页就可以理解为是一帧画面,一般来讲只要帧速率达到 60 FPS (frame per second 即每秒播放 60 帧)就能给人以连贯的动画感觉。
补间动画:补间动画就是指定开始和结束时的两帧(称为关键帧),在动画执行的过程中其他帧由系统计算得出。比如指定一个 TextView 开始位置和结束位置,动画过程中系统根据时间计算出 TextView 在某个时刻应该处于哪个位置并绘制出来,连续的绘制刷新就造成动画效果。
View Animation 是从 API 1 开始就有的一套动画系统,我们可以通过 View Animation 给 View 对象设置补间动画。补间动画可以处理 View 的一些简单转换,如位置、尺寸大小、旋转角度、透明度等,所有有关的类都在 android.view.animation
包下。补间动画有两种申明方式,一种是在 XML 文件中定义动画细节,在 Java 代码中简单调用,另外一种是完全在 Java 代码中定义并调用。一般都建议在 XML 文件中定义,这样定义可读性更高且可复用,具体区别在下文的例子中可以很明显的看出。
补间动画相关的 XML 文件在 res/anim/
路径下,一个 xml 文件可定义一种单独的动画。补间动画的四种转换在 XML 语法中可分别用 <translate>
,<scale>
,<rotate>
,<alpha>
四个标签定义,还可以使用 <set>
标签将不同的动画组合到一起执行。先看一个简单的例子感受一下,定义一个旋转 45° 的动画,在 XML 文件和 Java 代码中分别应该怎么做:
1.在 XML 中定义补间动画
在 /res/anim/
目录下新建一个 xml 文件,命名为 rotate.xml
并定义:
1 | <rotate xmlns:android="http://schemas.android.com/apk/res/android" |
调用的时候在 Java 代码中这么做:
1 | mIv = findViewById(R.id.img); |
2.在 Java 代码中定义补间动画
完全在 Java 代码中定义:
1 | mIv = findViewById(R.id.img); |
这两种方式定义的动画效果是一样的,但是可以感觉到,在 XML 文件中定义更为直观,而且可以在多个 Activity/Fragment 中重复利用,维护起来也更方便。
animation 包分析
以上对补间动画有了一个简单的了解,既然关于补间动画的类在 android.view.animation
包下,要全面了解补间动画的知识当然要进入 animation 包下一探究竟。
看到这么多类不要慌,对这些类进行整理之后再看其实很简单,以下为整理之后的关系:
这样看起来舒服多了,其中真正核心的是 Animation 抽象类及其子类,通过其他类的加持可以实现一些骚操作。
abstract class Animation
这是所有补间动画类的抽象类,其中定义了补间动画的通用属性和行为。
在 XML 文件中使用的 <rotate>
等标签是 Animation 子类的表示,不能直接在 XML 文件中定义 Animation 本身,但是 Animation 也提供了各动画子类通用的一些 XML 属性:
属性名称 | 说明 |
---|---|
android:detachWallpaper | boolean,只对有 wallpaper 的 Window Animation 有效。表示 wallpaper 是否跟随 Window 一起动画 |
android:duration | long,动画持续时间,以毫秒为单位,不可为负数 |
android:fillAfter | boolean,动画结束后是否应用变换 |
android:fillBefore | boolean,是否在动画开始前应用第一帧的变换 |
android:fillEnabled | boolean,设置为 true 则应用 fillBefore 的值 |
android:interpolator | Interpolator,设置插值器 |
android:repeatCount | int,设置动画重复次数,默认为 0,-1 表示一直重复 |
android:repeatMode | enum,设置动画重复时是倒序(reverse)还是顺序(restart),默认为 restart |
android:startOffset | long,动画延迟执行的时间,以毫秒为单位 |
android:zAdjustment | enum,调整动画内容在 Z 轴的顺序,normal-> 在当前顺序;bottom-> 强制在最下层;top-> 强制在最上层 |
以上每一个属性都有对应的 setter/getter 方法可调用。需要留意下三个 fill 相关的属性,先看一下一个动画完整的生命周期:
整个动画的时间 = startOffset + runtime(duration),当一个动画调用 start 方法开始时,此时为 startTime,经过 startOffset 后正式开始执行,startOffset 结束时会立即进入第一帧的状态。我们在定义动画时只要定义第一帧和最后一帧的状态。现在再看三个 fill 属性:
- fillAfter:表示动画结束后是否应用最后一帧的状态,true 表示应用,默认为 false,这个属性很简单,仅此而已
- fillBefore:表示动画在 startTime 之前是否应用第一帧的状态,默认为 true。fillBefore 的效果受 fillEnabled 的值影响,当 fillEnabled 为 true 时采用 fillBefore 的值,当 fillEnabled 为 false 时忽略 fillBefore 的值,效果同 fillBefore 为 true。
- fillEnabled:默认为 false,和 fillBefore 结合使用,不然会出现期望之外的效果
这三个属性在使用时要多加注意,在第一帧的状态设置为 View 的当前状态时,fillBefore 等同于没有效果。
接下来是几个常用方法:
setInterpolator(Interpolator i):为动画设置插值器。插值器后面会提到,是一个比较有意思的存在。
setAnimationListener(Animation.AnimationListener listener):为动画设置监听器,监听动画发生的事件。
start():开始动画,进入 startTime
cancel():取消动画,会立刻将动画设置为最后一帧的状态并回调 AnimationListener 的 onAnimationEnd 方法。如果手动 cancel 的话,下次启动动画之前需要调用 reset() 方法将 Animation 重置为初始状态。
内部接口 Animation.AnimationListener:当 Animation 设置了监听器后,发生相应的事件都会回调 AnimationListener 的方法,主要包含三个抽象方法–onAnimationEnd(Animation animation) 监听 animation 结束事件;onAnimationRepeat(Animation animation) 监听 animation 重复时的事件;onAnimationStart(Animation animation) 监听 animation 开始的事件。
AlphaAnimation
AlphaAnimation 类用于控制 View 对象的透明度动画,完成透明度在 0-1 之间的动画效果,0.0 为透明,1.0 为不透明。对应的 XML 标签为 <alpha>
,各属性分别为
属性名称 | 说明 |
---|---|
android:fromAlpha | float,动画开始帧的透明度 |
android:toAlpha | float,动画结束帧的透明度 |
Animation 子类的应用也很简单,子类的特定属性指定关键帧的属性值,加上通用属性的配置,就可以在 XML 文件中定义一个 Animation:
1 | <alpha xmlns:android="http://schemas.android.com/apk/res/android" |
使用方法也很简单,和第一个简单的例子一样:
1 | mIv = findViewById(R.id.img); |
或者也可以这样:
1 | mIv = findViewById(R.id.img); |
当然也可以使用构造方法在 Java 代码中直接实例化一个 AlphaAnimation 对象(一开始有提到,这种方法不推荐),那么 AlphaAnimation 的构造方法有两个:
AlphaAnimation(Context context, AttributeSet attrs):这个构造方法就是通过 XML 定义时会调用的
AlphaAnimation(float fromAlpha, float toAlpha):这个构造方法在 Java 代码中设置属性值来实例化对象
Animation 的所有子类使用方法一致,所以后三个子类列出对应的属性即可。
RotateAnimation
RotateAnimation 类用于控制 View 对象在 X-Y 平面的旋转动画。默认以 View 的坐标系中 (0,0) 为旋转中心点,可自定义。对应的 XML 标签为 <rotate>
,各属性为
属性名称 | 说明 |
---|---|
android:fromDegree | float,动画开始帧的旋转角度,单位为° |
android:toDegree | float,动画结束帧的旋转角度 |
android:pivotX | float/10%/10%p,旋转中心点的 X 轴坐标,坐标系为 View 自身坐标系。float 表示准确像素值,10% 表示相对 View 自身 10% 的宽度,10%p 表示相对父容器 10% 的宽度 |
android:pivotY | float/10%/10%p,旋转中心点的 Y 轴坐标,坐标系为 View 自身坐标系。float 表示准确像素值,10% 表示相对 View 自身 10% 的高度,10%p 表示相对父容器 10% 的高度 |
对构造方法感兴趣可以上官方文档查看。
ScaleAnimation
ScaleAnimation 类用于控制 View 对象的缩放动画。默认缩放的中心点为 View 的坐标系 (0,0) 点,可自定义。对应的 XML 标签为 <scale>
,各属性为
属性名称 | 说明 |
---|---|
android:fromXScale | float,动画开始帧的 X 轴的缩放比,1.0 表示没有变化 |
android:toXScale | float,动画结束帧的 X 轴的缩放比,1.0 表示没有变化 |
android:fromYScale | float,动画开始帧的 Y 轴的缩放比,1.0 表示没有变化 |
android:toYScale | float,动画结束帧的 Y 轴的缩放比,1.0 表示没有变化 |
android:pivotX | float/10%/10%p,旋转中心点的 X 轴坐标,坐标系为 View 自身坐标系。float 表示准确像素值,10% 表示相对 View 自身 10% 的宽度,10%p 表示相对父容器 10% 的宽度 |
android:pivotY | float/10%/10%p,旋转中心点的 Y 轴坐标,坐标系为 View 自身坐标系。float 表示准确像素值,10% 表示相对 View 自身 10% 的高度,10%p 表示相对父容器 10% 的高度 |
TranslateAnimation
TranslationAnimation 类用于控制 View 对象的平移动画。对应 XML 标签为 <translate>
,以下所有属性都可以使用 float/10%/10%p 三种格式的值,以 View 自身坐标系为参考系
属性名称 | 说明 |
---|---|
android:fromXDelta | 动画开始帧的 X 轴偏移 |
android:toXDelta | 动画结束帧的 X 轴偏移 |
android:fromYDelta | 动画开始帧的 Y 轴偏移 |
android:toYDelta | 动画结束帧的 Y 轴偏移 |
AnimationSet
AnimationSet 类用于将多个 Animation 以 List
AnimationSet 需要注意以下属性的不同行为:
- duration、repeatMode、fillBefore、fillAfter 等属性值将下发到个子项 Animation
- repeatCount、fillEnabled 将被 AnimationSet 忽略
- startOffset、shareInterpolator 应用于 AnimationSet 自身。
AnimationSet 对应的 XML 标签为 <set>
,各属性如下
属性名称 | 说明 |
---|---|
android:interpolator | Interpolator,定义插值器 |
android:shareInterpolator | boolean,将所有子类设置为同一个插值器 |
AnimationSet 可以看作是由多个 Animation 组成的一个 Animation,通过设置单个 Animation 的属性值可以达到多个动画同时或按顺序执行的效果,例子如下:
XML 文件–animation_set.xml:
1 | <set xmlns:android="http://schemas.android.com/apk/res/android"> |
Java 代码:
1 | mIv = findViewById(R.id.img); |
效果如下:
AnimationUtils
AnimationUtils 是一个封装好的工具类,借助它的静态方法可以很便利的载入动画对象。
- long currentAnimationTimeMillis():返回当前的动画时间,这个时间用来设置 startTime。
- Animation loadAnimation(Context context, int id):从资源文件中导入 Animation 对象。上面的例子已经用过了,很方便
- Interpolator loadInterpolator(Context context, int id):从资源文件中导入 Interpolator 对象。这个 Interpolator 马上就来了…
- LayoutAnimationController loadLayoutAnimation(Context context, int id):从资源文件中导入 LayoutAnimationController 对象。这个 LayoutAnimationController 也会在下面出现
- Animation makeInAnimation(Context c, boolean fromLeft):创建一个入场动画,使用滑动和淡入淡出效果(就是 alpha 和 translate 属性结合使用的动画),默认从右侧滑入,fromLeft 为 true 表示从左侧滑入
- Animation makeOutAnimation(Context c, boolean toRight):创建一个出场动画,使用滑出和淡出效果,默认从左侧滑出,toRight 为 true 则从右侧滑出
- Animation makeChildBottomAnimation(Context c):创建一个入场动画,使用上滑和淡入效果
这些方法都很简单,都是一些辅助方法,写几行代码试一下就可以看到效果了。
Interpolator
终于到它了,上面说到 Interpolator 是一个很有意思的类,现在就来了解下它有趣之处在哪。
Interpolator 中文名称插值器。插值是数学领域的一个名词,指通过一些已知离散点的取值估算出其他点的值,对专业解释感兴趣可以自行补充。插值器简单来说就是函数,在动画中表现为目标取值对于时间的函数,由于我们设置好了动画目标取值的范围(就是开始帧和结束帧的值)和时间范围(就是 duration),通过插值器这个函数,可以改变动画过程中目标取值相对时间的变化速率,再通俗点说,插值器可以使动画的目标取值非匀速变化,是实现一些复杂动画的关键。当然,自己实现一个插值器不难,但是刚开始还是有一种望而却步的感觉,所以 Android 系统已经很贴心的提供了很多插值器,足够日常使用了,等能够熟练使用自带的插值器再去自定义就不难了,本文并不涉及自定义插值器,毕竟贪多嚼不烂,目前了解自带的插值器效果即可。
从源码里看,Interpolator 只是继承了 TimeInterpolator 接口,没有任何多余的代码。TimeInterpolator 只有一个 getInterpolation 方法,TimeInterpolator 是 API Level 11 才引入的新接口,与新的动画系统有关,此处不介绍,只要知道 Interpolator 只提供了一个 getInterpolator 方法(推断 API Level 11 之前,Interpolator 接口应该也是有这个方法的,新增接口后把方法交给了父接口,这个方法就是自定义插值器的关键),具体实现交由各个子类就可以了。Interpolator 有一个抽象实现–BaseInterpolator,BaseInterpolator 的方法也不用管,需要关注的是它的 10 个具体子类:
- AccelerateInterpolator:加速变化。可设置构造函数中的 factor 参数调整变化因子,默认为 1.0f
- DecelerateInterpolator:减速变化。可设置构造函数中的 factor 参数调整变化因子,默认为 1.0f
- AccelerateDecelerateInterpolator:前半段呈加速变化,后半段减速变化
- AnticipateInterpolator:emm…开始时有一个回退效果,然后加速变化。可设置构造函数中的 tension 参数调整回弹系数,默认为 2.0f
- OvershootInterpolator:emm…(跑太快)结束时有一个溢出效果,其他时间段相当于 DecelerateInterpolator。可设置构造函数中的 tension 参数调整回弹系数,默认为 2.0f
- AnticipateOvershootInterpolator:上面两者的结合
- BounceInterpolator:结束时有回弹效果,弹珠自由落体那种。。
- CycleInterpolator:可根据 cycles 参数指定循环播放次数,一个来回为一个循环,cycles 默认为 1,看图体验,图中例子 cycles 为 2
- LinearInterpolator:匀速变化
- PathInterpolator:API 21 引入。可自定义 path 指定相应时刻的动画完成度。path 上点的横坐标表示时间完成度,纵坐标表示动画完成度,都按百分比计算。path 可以用函数 y = f(x) (0 <= x <= 1) 所绘制的曲线来表示。需要注意两点:同一个 x 值不能有两个或以上的 y 值与之对应;x 的值必须是从 0 - 1 的连续的值且每一个 x 值都有一个 y 值与之对应。关于 path 的要求可看下图:
如定义 path 为:
1 | Path path = new Path(); |
相应效果如下:
LayoutAnimationController
LayoutAnimationController 主要用于控制 ViewGroup 中各个子 view 的动画播放时机。简单来说,当对一个 ViewGroup 设置 Animation 时,不做其他设置的情况下,ViewGroup 会作为一个整体开始执行这个 Animation,而 LayoutAnimationController 可将这个 Animation 分发给各个子 View 并设置各自的延迟时间播放顺序等,让它们独立执行。它的使用方式也有两种,XML 声明和 Java 代码直接编写,使用之前先了解相关属性,在 XML 中使用 <layoutAnimation>
标签:
属性名称 | 说明 |
---|---|
android:animation | 需要使用的动画 |
android:animationOrder | enum,子 View 动画执行顺序。normal->正常顺序;random->随机开始;reverse->倒序 |
android:delay | 子 View 动画延迟系数。float 表示绝对倍数,也可用 10% 或 10%p 的形式表示,默认为 0.5 |
android:interpolator | 为延迟时间设置插值器 |
每个属性都有对应的 setter/getter 方法。
1.在 XML 中使用 LayoutAnimationController:
先定义 layoutAnimation 文件 layout_top_to_bottom.xml
:
1 | <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" |
Animation 文件 slide_top_to_bottom
:
1 | <set xmlns:android="http://schemas.android.com/apk/res/android"> |
在布局文件中目标 ViewGroup 节点加入 android:layoutAnimation="@anim/layout_top_to_bottom"
属性即可使用。效果如下:
2.在 Java 代码中使用 LayoutAnimationController:
1 | AnimationSet set = new AnimationSet(false); |
效果就不贴图了,这两种方法各有利弊,使用时自由选择,当然也可以结合,定义 Animation 由 XML 负责,Java 代码负责设置调用。
LayoutAnimationController 有一个子类–GridLayoutAnimationController,GridLayoutAnimationController 和 LayoutAnimationController 作用是一样的,从名字可以看出,GridLayoutAnimationController 是控制 GridView 结构中子 View 的动画的,而且还可以控制执行方向,因此它也新增属于自己的属性,在 XML 中使用 <gridLayoutAnimation>
:
属性名称 | 说明 |
---|---|
android:columnDelay | float/%,一列子 View 之间的动画延迟系数,默认为 0.5 |
android:rowDelay | float/%,一行子 View 之间的动画延迟系数,默认为 0.5 |
android:direction | enum,动画顺序执行的方向,可以指定多个值。bottom_to_top(2)–>从下往上按行逐个执行;left_to_right(0)–>从左往右按列逐个执行;right_to_left(1)–>从右往左按列逐个执行;top_to_bottom(0)–>从上往下按行逐个执行 |
android:directionPriority | enum,行列执行的优先性。column–>列优先;row–>行优先;none–>行列同时 |
GridLayoutAnimationController 默认是行列同时逐个子 View 从左往右,从上往下执行动画,对应的参数值为:direction = 0, directionPriority = none, columnDelay = 0.5f, rowDelay = 0.5f;如下图:
direction 和 directionPriority 两个属性也不难,动手设置看个一两遍就理解了~
ViewAnimation 实战
View Animation 相关的东西虽然有点多,但是并不复杂,挨个敲一遍都很好理解。再实现几个简单的小例子熟悉一下,先看第一个效果:
以下是动画的核心代码:
XML 文件 shake
:
1 | <translate xmlns:android="http://schemas.android.com/apk/res/android" |
Java 代码:
1 | // 载入 Animation 实例 |
没了,,就这么简单!!完整代码在此
那…再来看一个简单的例子,比上一个还简单,只是给一个 Button 平移的效果:
这个例子简单到不用上代码就知道怎么写了,不过上图的动画发生了一些奇怪的事情值得关注,Button 在平移之前点击会增加计数,平移之后再点击缺什么都没发生,反而点击 Button 平移之前的位置触发了它的点击事件。emmm…好好想想是怎么回事…
这就是 View Animation 的弊端了,它的本质只是刷新了 View 的绘制区域,View 对象并没有一起作动画,所以说 View Animation 可以理解为障眼法,绘制区域变了,本质还在原地。这么看来,要成功的将对象本身和绘制区域绑定到一起,需要借助 AnimationListener 的力量了,在 onAnimationEnd 方法将 View 对象的参数进行相应的变化:
1 | animation.setAnimationListener(new Animation.AnimationListener() { |
这样做确实可以解决问题,但是这么做很明显,最基础的就是要做适配,而且这么做确实麻烦,一个稍微复杂点的动画绝对能把自己做到怀疑人生…所以啊,Google 在 Android 3.0 引入了一套新的动画系统–Property Animation,也就是常听到的属性动画,它从本质上解决了View Animation 存的的问题,并且功能及其强大,可以说是实实在在的动画。如果感兴趣的话就期待《Android 动画》的下一篇吧~
最后,本文为学习 Android 动画系统过程中所作的总结,如果不当之处欢迎指出~奉上本文源码:传送门