Android 动画深入分析

动画的三个分类

  1. View 动画
  2. 帧动画
  3. 属性动画

View动画

四个动画效果

View动画的作用对象是View,它有四个动画效果:平移动画、缩放动画、旋转动画、透明度动画。

View动画的四个变化效果对应着Animation的四个子类:

  1. 平移动画:TranslateAnimationXML中对应的标签为<translate>
  2. 缩放动画:ScaleAnimationXML中对应的标签为<scale>
  3. 旋转动画:RotateAnimationXML中对应的标签为<rotate>
  4. 透明度动画:AlphaAnimationXML中对应的标签为<alpha>

<set>标签

<set>标签表示动画的集合,对应着AnimationSet类,其中可以包含着若干的动画,也可以有子动画。

标签有两个属性:

  1. android:interpolator="@anim/...":选定集合所使用的插值器。
  2. android:shareInterpolator=["true"|"false"]:是否让集合中的动画和集合使用相同的插值器,如果为false,则需要为每一个动画指定一个插值器。

自定义View动画

通过继承抽象类Animation,并重写其中的intializeapplyTransformation方法。

intialize方法做初始化工作。

applyTransformation方法中进行相应的矩阵变换,很多时候需要采用Camera类来简化矩阵变换的过程。

View动画的特殊使用场景

LayoutAnimation

LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当这个ViewGroup的子元素出场时都会具有这个动画效果。

常常被用在ListView中,让其中的item在出场时都具有动画效果。

使用步骤:

  1. XML中定义LayoutAnimation,标签为<layoutAnimation>。其中有两个参数:android:delayandroid:animationOrder。两者的作用如下:

    1. android:delay:使子元素的出场时间向后延迟指定的时间,单位为毫秒。
    2. android:animationOrder:指定子元素的出场顺序。有三种选项:normal顺序出场、reverse逆序出场、random随机出场。
  2. XML中指定出场时的动画。

  3. XML中的ViewGroup部分使用android:layoutAnimation参数指定第1步中的LayoutAnimation文件,便可以为ViewGroup中的子元素指定出场动画。

    除了可以在XML中指定ViewGroupandroid:layoutAnimation属性,也可以在代码中通过LayoutAnimationController来实现。

Activity的切换效果

View动画也可以对Activity的切换效果进行设置,效果如NavigationFragment切换效果。

这个效果是可以自定义的,我们可以通过overridePendingTransition(int enterAnim, int exitAnim)方法,这个方法startActivity(Intent intent)函数或者finish()函数之后被调用才能生效,否则动画效果将不起作用。

两个参数分别是设置的入场动画退场动画的资源Id。

使用方法例子如下:

1
2
3
Intent intent = new Intent(...);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
1
2
3
4
5
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}

Fragment的切换效果

由于Fragment类是在API11中才被新加入的,所以我们使用support-v4这个兼容包。

我们使用FragmentTransaction类中的setCustomAnimation()方法来添加切换动画,该处的动画需要是View动画,而不能是属性动画


帧动画

通过<animation-list>标签,并在其中预设好一组图片,类似于电影的播放,按顺序依次播放。

系统中提供AnimationDrawable类来使用帧动画。

应避免使用尺寸较大的图片,以防止OOM的发生。


属性动画

属性动画可以对任意对象的属性进行动画而不仅仅是View

动画的默认时间间隔为300ms,默认的帧率为10ms/帧

达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。

常用的几个动画类是:ValueAnimatorObjectAnimatorAnimatorSet

其中,ObjectAnimator继承自ValueAnimatorAnimatorSet是动画集合,可以定义一组动画。

Nineoldandroids

API11前,可以使用nineoldandroids开源动画库完成类似属性动画的效果,其使用的语法与API11后属性动画的语法完全一致,不同的是,nineoldandroids通过View动画来完成属性动画的效果。

使用方式

属性动画可以通过代码的方式完成,也可以在XML文件中定义。属性动画需要定义在res/animator中。

上述三个动画类在XML文件中的表示方式如下:

AnimatorSet

对应的标签为<set>

其中的属性android:ordering=["together"|"sequentially"]有两个可选值:

  1. together表示集合中所有的子动画同时播放。
  2. 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

时间插值器。作用为根据时间的流逝的百分比计算当前属性值改变的百分比

预设的有多种插值器,常见的三种如下:

  1. LinearInterpolator——线性插值器:匀速动画
  2. AccelerateDecelerateInterpolator——加速减速插值器:动画两头慢中间快
  3. DecelerateInterpolator——减速插值器:动画越来越慢

三个类都实现了Interpolator接口,接口内部只有一个用来计算属性值的改变的百分比的getInterpolation方法需要实现。

TypeEvaluator

类型估值算法,也叫估值器。作用是根据属性值改变的百分比来计算改变后的属性值

系统预置的有3种:

  1. IntEvaluator:针对整形属性
  2. FloatEvaluator:针对浮点型属性
  3. ArgbEvaluator:针对Color属性

三个类都实现了TypeEvalator接口,接口内部只有一个用来计算改变后的属性值的evaluate方法需要实现。

计算顺序

系统会根据当前的帧所在时刻与动画播放的总时间求出时间的流逝的百分比,再通过插值器计算出当前属性值改变的百分比,后用此值通过估值器计算出当前的属性改变后的确定的值是多少并通过set方法对属性赋值。

故属性动画要求对象的该属性有set方法(必须有)和get方法(可选)。

自定义插值器和估值器

我们可以自定义插值器和估值器,来实现自己想要的动画效果。实现方式也很简单。

因为插值器和估值器都是一个接口,且内部都只有一个方法,所以我们只需要派生一个类并实现接口就可以了,然后就可以做出千奇百怪的动画效果了。即:

  • 自定义插值器需要实现Interpolator或者TimeInterpolator接口
  • 自定义估值器需要实现TypeEvaluator接口。

属性动画的监听器

监听器用于监听动画的播放过程,主要有两个接口:AnimatorUpdateListenerAnimatorListener

AnimatorUpdateListener

1
2
3
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}

该接口会监听整个动画过程,即每播放一帧,onAnimationUpdate就会被调用一次。

AnimatorListener

1
2
3
4
5
6
public static interface AnimatiorListener {
void onAnimatorStart(Animator animation);
void onAnimatorEnd(Animator animation);
void onAnimatorCancel(Animator animation);
void onAnimatorRepeat(Animator animation);
}

该接口会监听动画的开始、结束、取消以及重复播放。同时为了方便开发,系统还提供了AnimatorListenerAdapter这个类,它是AnimatorListener的适配器类,这样我们就可以有选择地实现上面的4个方法了。

对任意属性做动画

属性动画可以对任意对象的属性进行动画而不仅仅是View

属性动画的工作流程

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法。每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去获取属性的初始值。即:

  • 必须要有该属性的set方法(如果这条不满足则会无效果)

  • 如果没有传递初始值,那么必须要有该属性的get方法(如果这条不满足则程序直接Crash)

动画不生效时的解决方法

当有些对象的内部并没有该属性的set方法时,动画会无效果,那么我们有三个方法去解决这个问题:

  1. 如果有权限的话,直接给对象加上getset方法

    很多时候,我们是没有权限的去添加方法的,比如我们给Button或者TextView等等,因为这些都是Android SDK内部实现的,我们无法更改。

  2. 用一个类来包装原始对象,间隔为其提供getset方法

    因为属性动画可以对任意对象进行动画,所以我们可以用一个类来包装原始对象,并在该类中为原始对象提供getset方法。

    然后对该类进行属性动画,即可修改原始对象的属性值。

  3. 利用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开发艺术探索》


Android 动画深入分析
https://luoyuy.top/posts/ac02c3bbb00b/
作者
LuoYu-Ying
发布于
2022年4月17日
许可协议