一、属性动画简介
Android 中动画有很多种,属性动画就是其中的一种。所谓的属性动画,就是在指定的时间内,通过改变对象的属性达到变化效果的动画。在 Android 中,属性动画系统是一个强健的框架,几乎可以为任何内容添加动画效果。实现属性动画也是通过 Android 的属性动画系统实现,开发者只需要定义动画的一些属性即可完成,这些属性如下:
- 时长(duration):指定动画的时长。默认时长为 300 毫秒。
- 时间插值(interpolator):指定如何根据当前动画的已播放时长来计算属性的值。
- 重复计数和行为(repeatCount、repeatMode):指定是否在时长结束后重复播放动画以及重复播放动画多少次、循环行为(reverse、restart)。
- Animator 集:将动画分成由多个动画集组成,这些动画集可以一起播放、按顺序播放或者在指定的延迟时间后播放。
- 帧刷新延迟:指定动画帧的刷新频率。默认设置为每 10 毫秒刷新一次,但应用刷新帧的速度最终取决于整个系统的繁忙程度以及系统为底层计时器提供服务的速度。
二、属性动画的工作原理
2.1 属性动画的计算
属性动画是通过在指定时间内改变对象的属性达到变化的动画,按照一定的帧速,计算出每帧属性的变化量,实现各种动画效果(如:匀速动画、加速动画效果等)。
上图描绘了属性动画中,主类之间是如何相互协作的。
ValueAnimator
对象跟踪动画的时间,例如动画的已运行时长以及添加了动画效果的属性的当前值。ValueAnimator
包含TimeInterpolator
和TypeEvaluator
。TimeInterpolator
:插值器,用于定义动画插值,即动画完成比例与动画时间比例之间的关系(动画完成比例跟动画时间比例不一定一致)。TypeEvaluator
:类型评估程序,用于定义在动画中如何计算添加了动画效果的属性的值,即属性值跟时间之间的关系(随着时间如何变化)。
要开始动画,首先要创建一个 ValueAnimator
对象,并为需要添加动画效果的属性设置起始值和结束值,并设置动画的时长。然后调用 start()
接口,动画就会播放。
2.2 属性动画与视图动画的区别
视图动画系统仅提供为 View
对象添加动画效果的功能,因此,如果您想为非 View
对象添加动画效果,则必须实现自己的代码才能做到。视图动画系统也存在一些限制,它仅能为 View
对象的某些方面添加动画效果。例如:视图动画系统可以为视图的位置变化、形状变化添加动画,但是无法为背景颜色变化添加动画。
视图动画系统的另一个缺点就是它只会修改视图绘制位置,而不会修改实际的视图本身,换句话说,视图动画只是实现一种视图的动画效果,视图本身还是停留在原来的位置。例如:为 Button
对象添加视图移动的动画,在按钮移动动画播放过程中,按钮本身的点击事件并不会随着动画移动,如果要实现点击事件随动画变化,就需要自己另外实现了。
有了属性动画系统,就可以完全摆脱这些束缚,还可以为任何对象(视图和非视图)的任何属性添加动画效果,并且实际修改的是对象本身。属性动画系统在执行动画方面也更为强健。概括地讲,通过属性动画系统可以为要添加动画效果的属性(例如颜色、位置或大小)分配 Animator
,还可以定义动画的各个方面,例如多个 Animator
的插值和同步。
不过,视图动画也是有它的有点,视图动画系统的设置需要的时间较短,需要编写的代码也较少。在开发过程中,如果视图动画可以完成需要执行的所有操作,或者现有代码已按照您需要的方式运行,则无需使用属性动画系统。在某些用例中,也可以针对不同的情况同时使用这两种动画系统。
2.3 常用 API 概览
前面讲到了ValueAnimator
、 TimeInterpolator
和 TypeEvaluator
几个类,可能大家有点蒙圈,其实在实际使用过程中,有现成的插值器是可以使用的。在 android.animation 包下面可以找到属性动画的大多数 API(类),另外,视图动画系统在 android.view.animation 包下面定义了许多插值器,这些插值器是可以直接在属性动画系统中使用的。
2.3.1 Animator
属性动画由抽象类 Animator
提供了创建动画的基本结构,但是必须通过扩展该类才能实现属性值动画,系统 API 已经包含了常用的扩展类供开发者使用。
-
ValueAnimator
:Animator
抽象类的直接子类,是属性动画的主计时引擎,它也可计算要添加动画效果的属性的值。它具有计算动画值所需的所有核心功能,同时包含每个动画的计时详情、有关动画是否重复播放的信息、用于接收更新事件的监听器以及设置待评估自定义类型的功能。为属性添加动画效果分为两个步骤:计算添加动画效果之后的值,以及对要添加动画效果的对象和属性设置这些值。ValueAnimator
不会执行第二个步骤,因此,您必须监听由ValueAnimator
计算的值的更新情况,并使用您自己的逻辑修改要添加动画效果的对象。 -
ObjectAnimator
:ValueAnimator
的子类,用于设置目标对象和对象属性以添加动画效果。此类会在计算出动画的新值后相应地更新属性。在大多数情况下,直接使用ObjectAnimator
可以极大地简化对目标对象的值添加动画效果的过程。不过,有时需要直接使用ValueAnimator
,因为ObjectAnimator
存在一些限制,无法达到需要的效果。例如要求目标对象具有特定的访问器方法。 -
AnimatorSet
:这个类提供一种将动画分组的机制,也就是将多个动画(或者动画跟动画组)组合在一起,以使它们彼此相对运行。通过这个类可以将动画设置为一起播放、按顺序播放或者在指定的延迟时间后播放。
2.3.2 插值器
前面讲到插值器用于定义动画完成比例与动画时间比例之间的关系。系统定义了一些插值器,开发者可以直接使用,当然,在必要时可以实现自己的插值器,实现自己的插值器需要继承 TimeInterpolator
类。系统常用的插值器有以下几个:
AccelerateDecelerateInterpolator
:该插值器的变化率在开始和结束时缓慢但在中间会加快。AccelerateInterpolator
:该插值器的变化率在开始时较为缓慢,然后会加快。AnticipateInterpolator
:该插值器先反向变化,然后再急速正向变化。AnticipateOvershootInterpolator
:该插值器先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值。BounceInterpolator
:该插值器的变化会跳过结尾处。CycleInterpolator
:该插值器的动画会在指定数量的周期内重复。DecelerateInterpolator
:该插值器的变化率开始很快,然后减速。LinearInterpolator
:该插值器的变化率是恒定不变。OvershootInterpolator
:该插值器会急速正向变化,再超出最终值,然后返回。TimeInterpolator
:该接口用于实现您自己的插值器。
2.3.3 评估程序
前面提到评估程序是用于定义在动画中如何计算添加了动画效果的属性的值,即属性值跟时间之间的关系的,根据动画时间,计算出属性当前的值。系统定义的评估程序有以下几种:
IntEvaluator
:这是用于计算int
类型属性的值的默认评估程序。FloatEvaluator
:这是用于计算float
类型属性的值的默认评估程序。ArgbEvaluator
:这是用于计算颜色属性的值(用十六进制值表示)的默认评估程序。TypeEvaluator
:此接口用于创建自定义的评估程序。如果要添加动画效果的对象属性不是int
、float
或颜色,就可以自定义的评估程序,自定义评估程序必须实现TypeEvaluator
接口,才能计算出对象属性在添加动画效果之后的值。
三、使用入门
3.1 使用 ValueAnimator 添加动画效果
借助 ValueAnimato
r 类,可以为动画播放期间某些类型的值添加动画效果,只需指定一组要添加动画效果的 int
、float
或颜色值即可。
可以通过调用 ValueAnimator
的任一工厂方法来获取 ValueAnimator
实例对象:ofInt()
、ofFloat()
或 ofObject()
。然后设置相应的动画参数(时长、重复模式、重复次数等等),最后调用 start()
方法开始动画。示例:
ValueAnimator.ofFloat(1.0f, 2.0f).apply {
duration = 1000 // 动画时长
repeatMode = ValueAnimator.REVERSE // 重复模式(重新开始)
repeatCount = ValueAnimator.INFINITE // 重复次数(无限循环)
start()
}
注意事项:前面提到,
ValueAnimator
为属性添加动画效果分为两个步骤,计算属性值和设置对象的属性值。ValueAnimator
计算出属性值,但是不会执行第二步(设置对象属性值) ,因此,必须添加AnimatorUpdateListener
监听由ValueAnimator
计算的值的更新情况,并将新值设置到对象的属性中。
通过添加 AnimatorUpdateListener
监听计算值的更新,在 onAnimationUpdate()
将 ValueAnimator
计算的值,设置到对象的相应属性中,让动画展现出来。
ValueAnimator.ofFloat(1.0f, 2.0f).apply {
duration = 1000 // 动画时长
repeatMode = ValueAnimator.REVERSE // 重复模式(重新开始)
repeatCount = ValueAnimator.INFINITE // 重复次数(无限循环)
addUpdateListener { // 添加 AnimatorUpdateListener 监听
// ValueAnimator 无法自动将更新的属性值设置到对象的属性中,只能在监听中实现对象属性值的设置。
// 监听到计算值的更新,并设置到对象属性中
val value = it.animatedValue as Float
imageView.scaleX = value
imageView.scaleY = value
}
start()
}
- 实现效果
温馨提示:
ValueAnimator
的工厂方法(ofInt()
、ofFloat()
或ofObject()
)是支持传入多个值的,至少有两个值才会有动画效果。ValueAnimator
有自己默认的插值器,默认的插值器是AccelerateDecelerateInterpolator
。
3.2 使用 ObjectAnimator 添加动画效果
在前面已经提到 ObjectAnimator
是 ValueAnimator
的子类,它继承了父类的动画引擎和值计算等,灵活性虽然不及 ValueAnimator
,但是 ObjectAnimator
支持通过属性名称为目标对象属性添加动画效果,这可以极大地简化为对象添加动画效果的过程,而且动画属性会自动更新,无需再实现 ValueAnimator.AnimatorUpdateListener
。
实例化 ObjectAnimator
与 ValueAnimator
的过程类似,但实例化 ObjectAnimator
对象时可以传入该对象需要添加动画的属性的名称(以字符串形式),以及实现动画效果的属性值(列表)。
- 示例代码
ObjectAnimator.ofFloat(imageView, "scaleY", 1.0f, 2.0f).apply {
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
duration = 1000
start()
}
第一个参数是目标对象,第二个参数是属性名称,第三个参数是个数组,是属性值列表。
-
实现效果
要使ObjectAnimator
正确更新属性实现动画效果,必须执行以下操作: -
传入的属性名称(即要添加动画效果的对象属性)必须具有
set<PropertyName>()
形式的 setter 函数(采用驼峰式大小写形式)。由于ObjectAnimator
会在动画过程中自动更新属性,所以必须能够使用此 setter 方法访问该属性。例如,上面提到的scaleX
,则需要存在setScaleX()
方法,并且有权限调用。如果此 setter 方法不存在,有三个选择可以处理:- 如果有权限修改类,可将 setter 方法添加到类中。
- 使用有权限更改的封装容器类,并在该封装容器使用有效的 setter 方法接收值并将其转发给原始对象。
- 改用
ValueAnimator
。
-
如果在
ObjectAnimator
的工厂方法中,仅指定一个值,则系统会假定它是动画的结束值。因此,要添加动画效果的对象属性必须具有用于获取属性起始值的 getter 函数。getter 函数必须采用get<PropertyName>()
形式。例如,上面提到的scaleX
必须要有getScaleX()
方法。 -
要添加动画效果的属性的 getter(如果需要)和 setter 方法的操作对象必须与
ObjectAnimator
指定的起始值和结束值的类型相同。 -
根据要添加动画效果的属性或对象,可能需要对视图调用
invalidate()
方法,以强制屏幕使用添加动画效果之后的值重新绘制自身。可以在onAnimationUpdate()
回调中执行此操作(视图的所有属性 setter(如 setAlpha() 和 setScaleX())都会使视图失效,因此,在使用新值调用这些方法时,您无需使视图失效)。
3.3 使用 AnimatorSet 编排多个动画
前面介绍的属性动画是单个动画(当然,你可以使用 ValueAnimator
同时为多个属性设置动画效果,但这个局限于同时执行),通过 AnimatorSet
可以将多个动画编排在一起(也可以嵌套 AnimatorSet
),并可控制不同的动画之间的播放关系(之前播放、一起播放、之后播放等)。AnimatorSet
构建完成之后,调用 start()
启动动画。
- 示例
// 定义X方向缩放动画
val scaleXAnim = ObjectAnimator.ofFloat(imageView, "scaleY", 1.0f, 2.0f).apply {
duration = 1000
}
// 定义Y方向缩放动画
val scaleYAnim = ObjectAnimator.ofFloat(imageView, "scaleX", 1.0f, 2.0f).apply {
duration = 1000
}
// 定义缩放AnimatorSet
val scaleAnimSet = AnimatorSet().apply {
// 同时播放X缩放动画和Y缩放动画
play(scaleXAnim).with(scaleYAnim)
}
// 定义透明度动画
val alphaAnim = ObjectAnimator.ofFloat(imageView, "alpha", 1.0f, 0.0f).apply {
duration = 1000
}
// 定义AnimatorSet
AnimatorSet().apply {
// 先播放缩放AnimatorSet,接着播放透明度动画
play(scaleAnimSet).before(alphaAnim)
// 启动AnimatorSet
start()
}
- 效果
注意事项:
1. 组合成AnimatorSet
的Animator
在定义是不要调用start()
方法;
2.AnimatorSet
开始播放动画需要调用start()
方法;
3.AnimatorSet
3.4 动画监听
属性动画监听有两个,分别是 Animator.AnimatorListener
和 ValueAnimator.AnimatorUpdateListener
,都是用来监听动画播放期间的重要事件。
-
Animator.AnimatorListener
:监听动画的各个状态,回调方法有:onAnimationStart()
- 在动画开始播放时回调。onAnimationEnd()
- 在动画结束播放时回调,无论何种方式结束动画都会调用。onAnimationRepeat()
- 在动画重复播放时回调。onAnimationCancel()
- 在动画取消播放时回调,取消的动画也会调用onAnimationEnd()
。
-
ValueAnimator.AnimatorUpdateListener
:监听动画的更新事件,回调方法有:onAnimationUpdate()
- 在动画的每一帧回调。使用此监听可以在动画播放过程中实时获取生成的计算值,通过回调方法参数ValueAnimator
对象的getAnimatedValue()
方法可以获取当前计算值,通过此计算值可以更新动画效果。如果使用ValueAnimator
实现属性动画,则必须通过此监听来更新动画(ValueAnimator
不会自动更新动画)。在此回调方法中更改视图的动画效果,可能需要对视图调用invalidate()
方法更新视图。如果调用视图的方法进行更新动画,并且改方法包含了invalidate()
,则无需再调用invalidate()
,例如:视图的setScaleX()
、setAlpha()
等方法。
如果不需要实现
Animator.AnimatorListener
接口的所有方法,则可以扩展AnimatorListenerAdapter
类,AnimatorListenerAdapter
类提供了方法的空实现。
3.5 在 XML 资源中定义属性动画
Android 提供了强大的 XML 资源定义,也包括了属性动画。属性动画 XML 资源放在 res/animator
目录下。
3.5.1 XML资源定义 ValueAnimator
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatMode="reverse"
android:repeatCount="infinite"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="2.0">
</animator>
在代码中使用 AnimatorInflater.loadAnimator()
方法加载动画,并调用 start()
方法播放动画。
(AnimatorInflater.loadAnimator(this@LayoutAnimActivity, R.animator.animator_scale) as ValueAnimator).apply {
addUpdateListener {
// ValueAnimator 无法自动将更新的属性值设置到对象的属性中,只能在监听中实现对象属性值的设置。
val value = it.animatedValue as Float
imageView.scaleX = value
imageView.scaleY = value
}
start()
}
说明:因为
ValueAnimator
不会自动更新视图属性,所以,在 XML 定义ValueAnimator
时,在代码中加载动画时,仍需要添加AnimatorUpdateListner
并在回调方法中更新视图属性,实现动画效果。
在代码中定义 ValueAnimator
时,可以指定多个值,但在 XML 中只能定义 valueFrom
和 valueTo
两个值。不过在 API 23 开始,XML 定义动画增加了关键帧的概念,而且每个关键帧(使用 propertyValuesHolder
和 keyframe
标签定义)还可以定义不同的插值器。
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatMode="reverse"
android:repeatCount="infinite">
<propertyValuesHolder>
<keyframe android:valueType="floatType" android:fraction="0" android:value="1.0" />
<keyframe android:valueType="floatType" android:fraction="0.5" android:value="1.2" />
<keyframe android:valueType="floatType" android:fraction="1" android:value="2.0" />
</propertyValuesHolder>
</animator>
添加关键帧用
propertyValuesHolder
标签封装,每一个关键帧用keyframe
标签定义。android:fraction
是关键帧在动画过程中的位置(占比分数)。
3.5.2 XML 资源定义 ObjectAnimator
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatMode="reverse"
android:repeatCount="infinite"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha">
</objectAnimator>
在代码中使用 AnimatorInflater.loadAnimator()
方法加载动画,设置目标,并调用 start()
方法播放动画。
(AnimatorInflater.loadAnimator(this@LayoutAnimActivity, R.animator.animator_alpha) as ObjectAnimator).apply {
target = imageView // ObjectAnimator 需要设置目标
start()
}
在 API 23 开始,同样可以使用关键帧(使用 propertyValuesHolder
和 keyframe
标签定义),用来定义更加复杂的动画,而且每个关键帧还可以定义不同的插值器。
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatMode="reverse"
android:repeatCount="infinite">
<propertyValuesHolder android:propertyName="alpha" android:valueType="floatType">
<keyframe android:valueType="floatType" android:fraction="0.0" android:value="1.0" />
<keyframe android:valueType="floatType" android:fraction="0.5" android:value="0.2" />
<keyframe android:valueType="floatType" android:fraction="1.0" android:value="0.0" />
</propertyValuesHolder>
</objectAnimator>
注意事项:
1. 在 XML 中定义ObjectAnimator
的时候,必须指定添加动画的属性,在代码中加载动画之后,也需要设置目标,并调用start()
才会启动动画;
2. 如果使用了propertyValuesHolder
,必须在propertyValuesHolder
标签内指定添加动画的属性名称;
3. 通过propertyValuesHolder
可以实现同时为多个属性添加动画(类似AnimatorSet
的效果),但是每个属性需要定义一个propertyValuesHolder
标签(只有在 API 23 开始才支持)。
在 ObjectAnimator
使用 propertyValuesHolder
可以实现同时为多个属性添加动画效果
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatMode="reverse"
android:repeatCount="infinite">
<propertyValuesHolder android:propertyName="alpha" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="0.0" />
<propertyValuesHolder android:propertyName="scaleX" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="2.0" />
<propertyValuesHolder android:propertyName="scaleY" android:valueType="floatType" android:valueFrom="1.0" android:valueTo="2.0" />
</objectAnimator>
3.5.3 XML 资源定义 AnimatorSet
在 ObjectAnimator
使用 propertyValuesHolder
同时为多个属性添加动画效果,仅API 23开始,低于使用 AnimatorSet
实现,一下示例跟上面有同样效果
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<set>
<objectAnimator
android:duration="500"
android:propertyName="scaleX"
android:valueTo="1.5"
android:valueType="floatType"
android:repeatCount="infinite"
android:repeatMode="reverse"/>
<objectAnimator
android:duration="500"
android:propertyName="scaleY"
android:valueTo="1.5"
android:valueType="floatType"
android:repeatCount="infinite"
android:repeatMode="reverse" />
</set>
<objectAnimator android:propertyName="alpha"
android:duration="500"
android:valueFrom="1.0"
android:valueTo="0.5"
android:valueType="floatType"
android:repeatCount="infinite"
android:repeatMode="reverse" />
</set>
四、编后语
Android 的属性动画非常强大,应用适当的话可以节省很多开发时间,本篇文章先介绍入门基础,后续会发表一些关于属性动画的使用技巧。