一. 简介
属性动画(Property Animation)是Android 3.0(API 11)之后提供的动画框架,在之前Android 提供了 帧动画(Frame Animation) 和 补间动画(Tween Animation),帧动画就是把一个动画分成多张图片,然后把这些图片连贯起来播放,原理和动画片类似;补间动画是针对View 进行的平移、旋转、缩放、透明度变化 等动画效果;属性动画则是通过改变属性值来实现动画效果,不再局限于View。
属性动画和补间动画的对比:
作用对象
补间动画的作用对象仅为View,而属性动画则可作用于非View 对象。有些情况下,可能需要对View 的某个对象进行操作,而不是整个View 对象,比如动态改变View 的颜色,此时补间动画便无法实现。对View 的影响
补间动画只改变View 的视觉效果,并不改变View 的属性,比如将一个Button 从屏幕左上角 移动至 右下角,使用补间动画的话,移动完成之后 Button 的点击区域还是左上角,因为它的位置属性并没有改变。而属性动画,顾名思义,改变的就是属性。动画效果
补间动画效果比较单一,仅支持平移、旋转、缩放、透明度变化,而属性动画比较灵活,可以实现多种复杂的动画效果。
在现在的开发过程中,由于属性动画已经可以实现几乎所有补间动画能实现的功能,所以我们最常用的还是属性动画,本篇将简介属性动画的基本使用。
二. 工作原理及相关类
1. 属性动画的工作过程
从上面的工作过程可以看到,属性动画框架中最常用也是最重要的几个类,就是ValueAnimator、ObjectAnimator、Interpolator 的实现类 和 TypeEvaluator 的实现类。
2. 相关类简介
Animators:
1)ValueAnimator,属性动画的主要计时引擎,它还计算要设置动画的属性的值。它具有计算动画值的所有核心功能,包含每个动画的持续时间,有关动画是否重复进行,更新属性值的事件侦听器以及设置自定义类型的估值器功能。属性动画有两个部分:计算动画过程中的属性值,给对象和属性上设置这些值。ValueAnimator 仅负责计算值,因此必须监听ValueAnimator 计算的值的更新,并使用按照自己的逻辑修改要设置动画的对象。
主要方法:
方法签名 | 用途 |
---|---|
public static ValueAnimator ofInt (int... values) | ofInt() 作用有两个: 1. 创建动画实例 2. 将传入的多个Int参数进行平滑过渡: 如果传入0和1,表示将值从0平滑过渡到1,如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推。 ValueAnimator.ofInt() 内置了整型估值器,直接采用默认的,不需要设置,即默认设置了如何从初始值 过渡到 结束值。 |
public static ValueAnimator ofFloat (float... values) | 类似ofInt |
public static ValueAnimator ofArgb (int... values) | api 21 时加入,传入的参数是颜色的int 值,提供颜色值的平滑变换,和ofInt 的区别在于使用的估值器不同 |
public static ValueAnimator ofObject (TypeEvaluator evaluator, Object... values) | 可传入对象作为参数创建Animator 实例,由于可以传任意对象,所以需要自己提供估值器 |
public ValueAnimator setDuration (long duration) | 设置动画持续时长,单位毫秒 |
public void setRepeatCount (int value) | 设置动画重复次数 |
public void setRepeatMode (int value) | 设置动画重复模式,有两种取值:RESTART 或 REVERSE,顾名思义就是重新开始 和 倒着执行 |
public void setStartDelay (long startDelay) | 设置延迟多长时间开始动画,单位毫秒 |
public boolean isRunning () | 判断动画是否正在运行 |
public void addUpdateListener (ValueAnimator.AnimatorUpdateListener listener) | 添加值变化时的监听事件,若要使用ValueAnimator 实现动画就必须要添加一个Listener,在数据变化之后改变对象属性来实现动画 |
public void start () | 开始动画,如果没有设置延时开始,动画会立即开始,设置了则会延时一段时间后开始 |
public void cancel () / public void end () | 取消动画 / 结束动画,两者的区别就是调用cancel 会停止在当前状态,end 会将属性值设置为结束值 |
public void addListener (Animator.AnimatorListener listener) | 是父类Animator 的方法,添加Listener,监听动画开始、结束、取消、重复等事件 |
public void pause () / public void resume () | 暂停 / 重新开始动画,调用这两个方法的线程必须和动画开始的线程相同,另外,如果对一个没有暂停的动画调用resume 方法将会被忽略 |
以上,只是比较常用的方法,还有许多诸如getter 等方法,这里不一一列举,详情可参官方文档。
2)ObjectAnimator,是ValueAnimator的子类,允许我们将目标对象和对象属性设置为动画,它在计算动画的新值时会相应地更新属性,我们在大多数情况下可以使用ObjectAnimator,因为它使得在目标对象上设置动画值的过程变得更加容易。但是,ObjectAnimator还有一些限制,例如要求在目标对象上存在特定的acessor方法。
主要方法和ValueAnimator 类似,只是创建对象的ofXXX 方法有些不同,常用如下
方法签名 | 用途 |
---|---|
public static ObjectAnimator ofArgb (Object target, String propertyName, int... values) | 构造并返回一个在颜色值之间设置动画的ObjectAnimator,values 传单个值意味着该值是动画的结束值,在这种情况下,起始值将从正在执行动画的属性的值和第一次调用start() 时的目标对象产生。两个值的话意味着起始值和结束值。两个以上的值意味着起始值,沿途的动画值和结束值 |
public static ObjectAnimator ofFloat (Object target, String xPropertyName, String yPropertyName, Path path) | 使用两个属性沿Path设置动画,“路径”动画以二维方式移动,将坐标(x,y)设置为动画,以跟随线条。坐标是浮点数,它们被分别设置到由xPropertyName和yPropertyName指定的属性。 |
public static ObjectAnimator ofFloat (Object target, String propertyName, float... values) | 为指定对象target 的指定属性propertyName 设置值 |
public static ObjectAnimator ofInt (T target, Property<T, Integer> xProperty, Property<T, Integer> yProperty, Path path) | 和上面提到的ofFloat 类似,只不过参数值的类型是int 值 |
需要注意的是,在给对象的指定属性设置值时,要求对象内必须有属性对应的getter 和setter。更多方法可以查文档。
3)AnimatorSet
提供一种将动画分组在一起的机制,以便它们相互运行,可以将动画设置为一起播放,按顺序播放,或在指定的延迟后播放。主要方法如下
方法签名 | 用途 |
---|---|
public AnimatorSet.Builder play (Animator anim) | 此方法创建一个Builder对象,用于设置动画之间的播放约束。这个初始的play() 方法告诉Builder动画,它是对Builder的后续命令的依赖。例如,调用play(a1).with(a2) 将AnimatorSet 设置为同时播放a1和a2,play(a1).before(a2) 设置AnimatorSet首先播放a1,然后播放a2,而 play(a1).after(a2) 将AnimatorSet 设置为先播放a2,然后播放a1。请注意,play() 是告诉Builder 创建依赖关系的动画的唯一方法,因此对Builder 中各种函数的连续调用都将引用play() 中提供的初始参数作为其他动画的依赖关系。例如,当a1结束时,调用play(a1).before(a2).before(a3) 将同时播放a2和a3; 它没有在a2 和a3 之间建立依赖关系。 |
public void playSequentially (List<Animator> items) | 顺序执行动画列表 |
public void playTogether (Collection<Animator> items) | 同时播放集合中的动画 |
public void setCurrentPlayTime (long playTime) | 设置时间进度到指定的时间点,值应该在0 和 动画集结束的总时长 之间 |
public void reverse () | 反转执行动画,如果使用setCurrentPlayTime 跳到了某时间点,将在该时间点反转,否则将从结束值开始反转,这个方法值适用当前动画,未来的动画将不受影响 |
public AnimatorSet setDuration (long duration) | 设置动画集中每一个动画的执行时长,默认情况每个动画执行自己的时长,调用了这个方法后,每个动画的执行时长将使用设置值 |
更多方法可见这里。
Interpolator:
时间插值器定义如何计算动画中的特定值作为时间的函数。例如,可以指定动画在整个动画中线性发生,这意味着动画在整个时间内均匀移动,或者还可以指定动画以使用非线性时间,例如,在开始时加速并在结束时减速。Android 提供了一些插值器,如果所提供的插值器都不适合需求,可以实现TimeInterpolator接口并创建自定义插值器。
Android 默认提供了9 种插值器
类 | 描述 |
---|---|
LinearInterpolator | 值随时间的变化为线性 |
AccelerateInterpolator | 加速变化 |
DecelerateInterpolator | 减速变化 |
AccelerateDecelerateInterpolator | 先加速后减速变化 |
AnticipateInterpolator | 先反向变化,再正向快速变化 |
OvershootInterpolator | 快速变化到超出结束值一段,再缓慢反向变化回结束值 |
BounceInterpolator | 不断回弹地变化 |
CycleInterpolator | 正弦函数变化 |
Evaluator:
Evaluator(估值器)告诉属性动画系统如何计算给定属性的值,它们获取Animator类提供的时序数据(时序数据的值就是通过Interpolator 计算出的),动画的开始和结束值,并根据此数据计算属性的动画值。
Android 提供了三种估值器:
类 | 描述 |
---|---|
IntEvaluator | Int 值属性的默认估值器 |
FloatEvaluator | Float 值属性的默认估值器 |
ArgbEvaluator | 属性值表示颜色时,使用该估值器 |
如果我们需要做动画的属性的类型不是int 或 float时,就需要实现TypeEvaluator 接口自定义估值器。
三. 应用
1. ValueAnimator
在使用ValueAnimator 实现动画时,首先要创建一个Animator 对象,上面的API 简介提到了一系列的of 方法,可以创建针对不同属性类型的Animator 对象
val intAnim = ValueAnimator.ofInt(0, 1000).apply {
duration = 5000
startDelay = 1000
interpolator = BounceInterpolator()
addUpdateListener { // 在监听值更新的Listener 里给要做动画的对象(本例是一个TextView)的属性赋值
animText.x = (it.animatedValue as Int).toFloat()
animText.rotationY = (it.animatedValue as Int).toFloat()
}
addListener(object : Animator.AnimatorListener { // 监听动画的执行事件
override fun onAnimationStart(animation: Animator?) {
println("-------------start")
}
override fun onAnimationEnd(animation: Animator?) {
println("-------------end")
}
override fun onAnimationCancel(animation: Animator?) {
println("-------------cancel")
}
override fun onAnimationRepeat(animation: Animator?) {
println("-------------repeat")
}
})
}
除了用Java/Kotlin 代码创建外,还可以在XML 中创建动画的相关设置:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="1000"
android:valueType="intType"
android:duration="5000"
android:startOffset="1000"
android:repeatCount="1"
android:repeatMode="reverse">
</animator>
加载XML 并创建对象的代码如下:
val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.value_anim)
注意在使用XML 创建Animator 对象之后,也需要添加Listener 来动态改变对象的属性值。
创建了Animator 对象之后,若要开始动画,只需要调用start 方法即可。
2. ObjectAnimator
ObjectAnimator 是ValueAnimator 的子类,对其进行了一定程度的封装,我们不需要再添加监听值变化的Listener 来手动为属性赋值,我们只需在创建ObjectAnimator 对象时,传入要改变属性的对象及其属性名即可(但要保证该属性有对应的setter 方法),如下:
val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
duration = 5000
}
同样,ObjectAnimator 的相关设置也可以通过XML 来写,
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="800"
android:valueType="floatType"
android:duration="5000"
android:startOffset="1000"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:repeatCount="1"
android:repeatMode="restart"
android:propertyXName="x"
android:propertyYName="y"
android:pathData="M10,10Q200,100,400,600Q600,100,800,0Z">
</objectAnimator>
同样需要加载对象
val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.obj_anim)
xmlAnim.setTarget(textView) // 设置动画作用的对象
开始动画只需调用start 方法即可。
我们知道,属性动画的动画效果就是通过不断改变对象的属性值来实现的,所以要比补间动画更灵活,那么补间动画能实现的View 的四种动画效果,放在属性动画里应该改变哪些值呢,如下表
属性 | 作用 | 数值类型 |
---|---|---|
alpha | 控制View的透明度 | float |
translationX | 控制X方向的位移 | float |
translationY | 控制Y方向的位移 | float |
translationZ | 控制Z方向的位移 | float |
scaleX | 控制X方向的缩放倍数 | float |
scaleY | 控制Y方向的缩放倍数 | float |
rotation | 控制以屏幕方向(可以理解为Z 轴)为轴的旋转度数 | float |
rotationX | 控制以X轴为轴的旋转度数 | float |
rotationY | 控制以Y轴为轴的旋转度数 | float |
这些都是View 的属性,通过对他们的改变就可以实现一些基本的动画效果。
3. AnimatorSet
AnimatorSet 可以将多个动画组合起来进行播放,并且可以给这些动画设置先后顺序等
val path = Path().apply {
moveTo(10f, 10f)
quadTo(200f, 100f, 400f, 600f)
quadTo(600f, 100f,800f, 0f)
}
val pathAnim = ObjectAnimator.ofFloat(textView, View.X, View.Y, path).apply { // 创建一个让View 按照指定Path 运动的动画
duration = 3000
interpolator = AccelerateDecelerateInterpolator()
}
// 颜色改变的动画
val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
duration = 5000
}
// 改变横坐标的动画
val intAnim = ObjectAnimator.ofFloat(textView, "x", 0f).apply {
duration = 3000
}
val animSet = AnimatorSet()
animSet.play(pathAnim) // 调用play 来播放
animSet.play(colorAnim).with(intAnim).after(pathAnim).after(2000) // with 表示同时播放,after 表示在xxx 之后播放,before 表示在xxx 之前播放
animSet.start() // 开始动画