Android 动画深入分析
动画的三个分类
View
动画- 帧动画
- 属性动画
View
动画
四个动画效果
View
动画的作用对象是View
,它有四个动画效果:平移动画、缩放动画、旋转动画、透明度动画。
View
动画的四个变化效果对应着Animation
的四个子类:
- 平移动画:
TranslateAnimation
,XML
中对应的标签为<translate>
- 缩放动画:
ScaleAnimation
,XML
中对应的标签为<scale>
- 旋转动画:
RotateAnimation
,XML
中对应的标签为<rotate>
- 透明度动画:
AlphaAnimation
,XML
中对应的标签为<alpha>
<set>
标签
<set>
标签表示动画的集合,对应着AnimationSet
类,其中可以包含着若干的动画,也可以有子动画。
标签有两个属性:
android:interpolator="@anim/..."
:选定集合所使用的插值器。android:shareInterpolator=["true"|"false"]
:是否让集合中的动画和集合使用相同的插值器,如果为false
,则需要为每一个动画指定一个插值器。
自定义View
动画
通过继承抽象类Animation
,并重写其中的intialize
和applyTransformation
方法。
intialize
方法做初始化工作。
applyTransformation
方法中进行相应的矩阵变换,很多时候需要采用Camera
类来简化矩阵变换的过程。
View
动画的特殊使用场景
LayoutAnimation
LayoutAnimation
作用于ViewGroup
,为ViewGroup
指定一个动画,这样当这个ViewGroup
的子元素出场时都会具有这个动画效果。
常常被用在ListView
中,让其中的item
在出场时都具有动画效果。
使用步骤:
在
XML
中定义LayoutAnimation
,标签为<layoutAnimation>
。其中有两个参数:android:delay
和android:animationOrder
。两者的作用如下:android:delay
:使子元素的出场时间向后延迟指定的时间,单位为毫秒。android:animationOrder
:指定子元素的出场顺序。有三种选项:normal
顺序出场、reverse
逆序出场、random
随机出场。
在
XML
中指定出场时的动画。在
XML
中的ViewGroup
部分使用android:layoutAnimation
参数指定第1步中的LayoutAnimation
文件,便可以为ViewGroup
中的子元素指定出场动画。除了可以在
XML
中指定ViewGroup
的android:layoutAnimation
属性,也可以在代码中通过LayoutAnimationController
来实现。
Activity
的切换效果
View
动画也可以对Activity
的切换效果进行设置,效果如Navigation
的Fragment
切换效果。
这个效果是可以自定义的,我们可以通过overridePendingTransition(int enterAnim, int exitAnim)
方法,这个方法startActivity(Intent intent)
函数或者finish()
函数之后被调用才能生效,否则动画效果将不起作用。
两个参数分别是设置的入场动画和退场动画的资源Id。
使用方法例子如下:
1 |
|
1 |
|
Fragment
的切换效果
由于Fragment
类是在API11
中才被新加入的,所以我们使用support-v4
这个兼容包。
我们使用FragmentTransaction
类中的setCustomAnimation()
方法来添加切换动画,该处的动画需要是View
动画,而不能是属性动画。
帧动画
通过<animation-list>
标签,并在其中预设好一组图片,类似于电影的播放,按顺序依次播放。
系统中提供AnimationDrawable
类来使用帧动画。
应避免使用尺寸较大的图片,以防止OOM
的发生。
属性动画
属性动画可以对任意对象的属性进行动画而不仅仅是View
。
动画的默认时间间隔为300ms
,默认的帧率为10ms/帧
。
达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。
常用的几个动画类是:ValueAnimator
、ObjectAnimator
和AnimatorSet
。
其中,ObjectAnimator
继承自ValueAnimator
,AnimatorSet
是动画集合,可以定义一组动画。
Nineoldandroids
API11
前,可以使用nineoldandroids
开源动画库完成类似属性动画的效果,其使用的语法与API11
后属性动画的语法完全一致,不同的是,nineoldandroids
通过View
动画来完成属性动画的效果。
使用方式
属性动画可以通过代码的方式完成,也可以在XML
文件中定义。属性动画需要定义在res/animator
中。
上述三个动画类在XML
文件中的表示方式如下:
AnimatorSet
对应的标签为<set>
。
其中的属性android:ordering=["together"|"sequentially"]
有两个可选值:
together
表示集合中所有的子动画同时播放。sequentially
表示集合中的子动画依次播放。
ObjectAnimator
对应的标签为<objectAnimator>
。
部分属性即解释如下:
android:propertyName="string"
:表示属性动画的作用对象的属性的名称。android:startOffset="int"
:表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画。android:repeatCount="int"
:表示动画的重复次数,默认值为0
,-1
表示无限循环。android:repearMode=["restart"|"reverse"]
:表示动画的重复模式。"restart"
表示连续重复,即每次都重新开始播放。"reverse"
表示逆向重复,即第一次正放动画,第二次倒放动画,第三次正放动画,第四次倒放动画,如此反复。
"android:valueType=["intType"|"floatType"]
:表示android:perpertyName
所指定的属性的类型,如果指定的属性为颜色,那么不需要指定该属性,系统会自动对颜色类型的属性做处理。
ValueAnimator
对应的标签为<animator>
其属性比ObjectAnimator
少了一个android:perpertyName
,其余的属性都是一样的,故不多介绍。
理解插值器和估值器
插值器和估值器是实现非匀速动画的重要手段。
TimeInterpolator
时间插值器。作用为根据时间的流逝的百分比计算当前属性值改变的百分比。
预设的有多种插值器,常见的三种如下:
LinearInterpolator
——线性插值器:匀速动画AccelerateDecelerateInterpolator
——加速减速插值器:动画两头慢中间快DecelerateInterpolator
——减速插值器:动画越来越慢
三个类都实现了Interpolator
接口,接口内部只有一个用来计算属性值的改变的百分比的getInterpolation
方法需要实现。
TypeEvaluator
类型估值算法,也叫估值器。作用是根据属性值改变的百分比来计算改变后的属性值。
系统预置的有3种:
IntEvaluator
:针对整形属性FloatEvaluator
:针对浮点型属性ArgbEvaluator
:针对Color属性
三个类都实现了TypeEvalator
接口,接口内部只有一个用来计算改变后的属性值的evaluate
方法需要实现。
计算顺序
系统会根据当前的帧所在时刻与动画播放的总时间求出时间的流逝的百分比,再通过插值器计算出当前属性值改变的百分比,后用此值通过估值器计算出当前的属性改变后的确定的值是多少并通过set
方法对属性赋值。
故属性动画要求对象的该属性有set
方法(必须有)和get
方法(可选)。
自定义插值器和估值器
我们可以自定义插值器和估值器,来实现自己想要的动画效果。实现方式也很简单。
因为插值器和估值器都是一个接口,且内部都只有一个方法,所以我们只需要派生一个类并实现接口就可以了,然后就可以做出千奇百怪的动画效果了。即:
- 自定义插值器需要实现
Interpolator
或者TimeInterpolator
接口 - 自定义估值器需要实现
TypeEvaluator
接口。
属性动画的监听器
监听器用于监听动画的播放过程,主要有两个接口:AnimatorUpdateListener
和AnimatorListener
。
AnimatorUpdateListener
1 |
|
该接口会监听整个动画过程,即每播放一帧,onAnimationUpdate
就会被调用一次。
AnimatorListener
1 |
|
该接口会监听动画的开始、结束、取消以及重复播放。同时为了方便开发,系统还提供了AnimatorListenerAdapter
这个类,它是AnimatorListener
的适配器类,这样我们就可以有选择地实现上面的4个方法了。
对任意属性做动画
属性动画可以对任意对象的属性进行动画而不仅仅是View
。
属性动画的工作流程
属性动画要求动画作用的对象提供该属性的set
方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set
方法。每次传递给set
方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get
方法,因为系统要去获取属性的初始值。即:
必须要有该属性的
set
方法(如果这条不满足则会无效果)如果没有传递初始值,那么必须要有该属性的
get
方法(如果这条不满足则程序直接Crash)
动画不生效时的解决方法
当有些对象的内部并没有该属性的set
方法时,动画会无效果,那么我们有三个方法去解决这个问题:
如果有权限的话,直接给对象加上
get
和set
方法很多时候,我们是没有权限的去添加方法的,比如我们给
Button
或者TextView
等等,因为这些都是Android SDK内部实现的,我们无法更改。用一个类来包装原始对象,间隔为其提供
get
和set
方法因为属性动画可以对任意对象进行动画,所以我们可以用一个类来包装原始对象,并在该类中为原始对象提供
get
和set
方法。然后对该类进行属性动画,即可修改原始对象的属性值。
利用
ValueAnimator
,监听动画过程,自己实现属性的改变使用
AnimatorUpdateListener
接口监听整个动画过程,在其中的onAnimationUpdate
方法中,对每一帧通过调用插值器和估值器计算当前的属性值,并通过set
方法直接为该属性赋值,实现属性的改变。
注:自己写的set
方法中,在对属性值进行修改后,需要调用view.requestLayout()
方法重绘界面,否则无法显示更改后的界面。
属性动画的工作原理
ObjectAnimatior#start
方法会调用其父类ValueAnimator#start
方法,在父类的start
方法中会调用AnimationHandler#start
方法,AnimationHandler
是一个Runnable
,我已属性动画需要运行在有Looper
的线程中。
中间通过一系列的方法调用,然后会使用ValueAnimator#doAnimationFrame
方法,在此方法中又调用了animationFrame
方法,内部又调用了animateValue
方法,其中的calculateValue
方法就是计算每帧动画所对应的属性的值,其中的setupValue
方法通过反射调用对象的get
方法,setAnimationValue
方法通过反射的方式调用了对象的set
方法。
使用动画的注意事项
来源:《Android开发艺术探索》