谈谈Android中的动画

谈谈动画

动画作为展现动态效果不可缺少的一环,还是异常重要的,其主要运用在View中。
按照Android发展史可以分成三个大类:逐帧动画、补间动画、属性动画。
在现在的实际开发里面,使用很多的还是属性动画相关的东西,前两种动画已经不大常用,渐渐淡出了人们的视野。在这里只是简单介绍。

逐帧动画

这种原理就类似于gif图,播放所有已有的帧,通过人眼的短暂视觉残留来完成动画。
等价于Android里面的AnimationDrawable

利用XML进行定义

需要在 <animation-list .../> 标签下使用 <item .../> 子元素标签定义动画的全部帧(引入每一帧对应的资源文件),并指定各帧的持续时间。以完成目的。

该文件创建在res/drawable文件目录之下

1
2
3
4
5
6
<animation-list 
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true|false">
<item android:drawable="xxx" android:duration="xx"/>
<!-- more... -->
</animation-list>

其中oneshot表示该动画是否循环播放,true表示只播放一次,false表示无限循环。

之后在ImageView里面设置对应的资源即可。
只需要获取到对应的AnimationDrawable对象,即可通过start()以及stop()方法来控制动画的开始与停止。

直接使用代码定义

初始化AnimationDrawable对象后,通过addFrame(Drawable,int)来进行加入,分别代表对应帧和持续时间。
直接在ImageView里面setDrawable()来进行绑定。
setOneShot(true)设置动画是都循环播放。
同样的,使用start()以及stop()方法来控制动画的开始与停止。

使用太多较大的图片容易引起OOM

补间动画

顾名思义,就是开发者(我们)只需要给出动画的第一帧以及最后一帧,通过系统自动实现中间的过渡动作。

Android帮提供了以下的几种属性的动画

  • 透明度变换:Alpha
  • 位移:translate
  • 缩放:scale
  • 旋转:rotate

可以通过XML文件对他们进行设置,在代码之中分别对应AlphaAnimation/TranslateAnimation/ScaleAnimation/RotateAnimation这四个类,共同父类为Animation

具体的不会过多展开讲解,可参考该博客

利用XML进行定义

需要将对应的资源文件放于目录res/anim文件下

一些属性:

  • duration 持续时间
  • interpolator 插值器,控制动画的变化速率。

    Android里面自带以下实现:

    • AccelerateDecelerateInterpolator
      在动画开始与结束的地方速率改变比较慢,在中间的时候加速
    • AccelerateInterpolator
      在动画开始的地方速率改变比较慢,然后开始加速
    • AnticipateInterpolator
      开始的时候进行反向效果然后按照预期效果进行
    • AnticipateOvershootInterpolator
      开始的时候进行反向效果然后超出预期值后返回最后需求的状态
    • BounceInterpolator
      动画结束的时候会重复最后一小段时间内的动画效果
    • CycleInterpolator
      动画循环播放特定的次数,速率的大小改变遵从正弦曲线
    • DecelerateInterpolator
      在动画开始快然后速率减小
    • LinearInterpolator
      以指定速率匀速改变
    • OvershootInterpolator
      超出预期值后再回到最后的状态

      这些插值器都能够通过`@android.anim/xxx`进行引用。

      含附录:自带插值器效果图

  • fillAfter动画结束后是否停留在结束位

而在对应的xml属性之中,通过设定fromXxxtoXxx属性来设定对应属性的开始值和终点值。
以移动为例:

1
2
3
4
5
<translate
android:fromXDelta="0"
android:toXDelta="100"
android:fromYDelta="0"
android:toYDelta="100" />

X和Y对应的是X方向与Y方向的操作。
其中对于透明度的设置范围是-1.0-1.0
缩放值跟的是相对应的倍数,例如0.5是缩小一半,2.0是放大一倍
然后在rotate以及scale范围里面还有两个参数pivotX/pivotY来表示操作的中心点(起始点)
对应的有几种表示方式,以pivotX为例子讲解

  • 10:动画开始X方向的点为View左上角的点 在x方向 加上 10像素的位置
  • 10%: 动画开始X方向的点View左上角的点 在x方向 加上 自身宽度乘上10%的位置
  • 10%p:动画开始X方向的点离View左上角的点 在x方向 加上 父控件宽度乘上10%数值的位置

利用Java实现补间动画

该方法通过实例化对应的动画对象来进行相对应的设置。
这些动画的使用都是调用View.startAnimation(anim)开始动画
通过可以通过setDuration(int)方法设置动画运行的时间

  • translate
1
2
3
4
5
6
7
Animation translateAnimation = new TranslateAnimation(05000500);
/**
* 1. fromXDelta :视图在水平方向x 移动的起始值
* 2. toXDelta :视图在水平方向x 移动的结束值
* 3. fromYDelta :视图在竖直方向y 移动的起始值
* 4. toYDelta:视图在竖直方向y 移动的结束值
*/
  • scale
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Animation scaleAnimation= new ScaleAnimation(0,2,0,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
/**
* 1. fromX :动画在水平方向X的结束缩放倍数
* 2. toX :动画在水平方向X的结束缩放倍数
* 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
* 4. toY:动画在竖直方向Y的结束缩放倍数
* 5. pivotXType:缩放轴点的x坐标的模式
* 6. pivotXValue:缩放轴点x坐标的相对值
* 7. pivotYType:缩放轴点的y坐标的模式
* 8. pivotYValue:缩放轴点y坐标的相对值
* pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
* pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
* pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
*/
  • Rotate
1
2
3
4
5
6
7
8
9
10
Animation rotateAnimation = new RotateAnimation(0,270,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
/**
* 1. fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
* 2. toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
* 3. pivotXType:旋转轴点的x坐标的模式
* 4. pivotXValue:旋转轴点x坐标的相对值
* 5. pivotYType:旋转轴点的y坐标的模式
* 6. pivotYValue:旋转轴点y坐标的相对值
* pivotXType具体值以及作用同上
*/
  • alpha
1
2
3
Animation alphaAnimation = new AlphaAnimation(1,0);
// 1. fromAlpha:动画开始时视图的透明度(取值范围: -1 ~ 1)
// 2. toAlpha:动画结束时视图的透明度(取值范围: -1 ~ 1)

动画的设置优先级:子动画>动画集。即,子动画设置了无限循环,动画集设置了只运行一次,运行的时候还是无限循环的模式。

当然,也可以通过自己需求对补间动画进行定义,继承Animation类,并重写applyTranformation()方法
具体的就不详细讲述了,因为:

我们为什么不用属性动画呢?

属性动画

属性动画直白来说,是对任意对象的任意属性进行动画过渡,是补间动画的加强版
其优点:

  • 针对所有的对象
  • 改变了View的属性值,在动画的时候已经对应改变了view的属性(响应点击事件)
  • 可扩展性(自定义属性效果)

我们通常使用以下两个类

  • ValueAnimator
    在属性动画里面使用到的时间引擎,计算对应的属性值
  • ObjectAnimator
    是上者的子类,对指定对象的属性执行动画

为了健壮性,一般是在代码里面声明对象,在这里就不介绍XML设置的方式

ValueAnimator

通过不断控制值的变化,在不断手动赋值给对象的属性,从而实现动画效果。

过程:

  • 设置动画的运行时长,动画效果以及开始和结束的属性值
  • 设置估值器
    该类描述动画如何从初始值过渡到结束值的逻辑
  • 添加AnimatorUpdateListener,该接口会直接回调当前状态的Animation,通过animation.getAnimatedValue()(记得强制转换,因为该方法返回的是Object)获取到当前的值
  • 将值设置到给需要进行变换的属性上面
  • 通知View进行重绘
    • postInvalidate()
    • invalidate()

在这里面主要是使用ValueAnimator.ofXxx(...values)静态方法来获取相对应的ValueAnimator
含有ofInt()/ofFloat()/ofObject()/ofArgb()/ofPropertyValuesHolder()这几种方法
分别对应其中的过渡值:整形值、浮点值、对象、颜色值、存储属性的动画改变值
实际开发里面常用前面三种,在使用的时候传入对应属性的变化值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Animator animator = ValueAnimator.ofFloat(0, 3f);
// 设置运行时间
animator.setDuration((long) (time * 1000));
// 设置重复次数
animator.setRepeatCount(0);
// 设置重复播放动画模式
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
animmator.setRepeatMode(ValueAnimator.RESTART);
// 设置插值器
animator.setInterpolator(new LinearInterpolator());
// 添加监听
animator.addUpdateListener(animation -> {
float f = (float) animation.getAnimatedValue();
// 对view进行操作
postInvalidate();
});
animator.start();

但是对于对象的过渡,我们需要自定义估值器(自定义类实现TypeEvaluator接口)
复写evaluate(float fraction, Object startValue, Object endValue)方法
分别对应:

  • fraction:表示动画完成度(根据它来计算当前动画的值)
  • startValue、endValue:动画的初始值和结束值

最后需要返回对象经过过渡逻辑计算后的值

本质上还是在操作值,不过将多个值全部封装到了一个对象里面

ObjectAnimator

直接传入对象以及对象的属性名以及操作值来实现动画效果

该类相比于ValueAnimator来说,只是少了赋值给对应属性这一步

新建对象与ValueAnimator相似,也是通过ofXxx方法,不过传入的参数有区别,加入了对应的新参数
(Object object, String property, ....values)

  • 操作对象
  • 操作属性
  • 变化值

使用样例如下

1
2
3
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationX", 0, 200f);
animator.setDuration((long) (actionTime * 1000));
animator.start();

对于ofPropertyValuesHolder()来说,只需要传入(Object,PropertyValuesHolder ... values)即可

PropertyValuesHolder

存放做动画的对应属性以及期间的变化值,声明方式与ValueAnimator类似,不过在之前需要传入属性名

对于该类,在最开始有一个简单解释:

此类包含有关属性的信息以及该属性在动画期间应采用的值。
PropertyValuesHolder对象可用于使用ValueAnimator或ObjectAnimator创建动画,这些动画可并行操作多个不同的属性。

可等价于动画集合AnimatorSet,在ObjectAnimatorValueAnimator之中调用并传入参数即可。

1
2
3
4
5
6
PropertyValuesHolder upX = PropertyValuesHolder.ofFloat("translationX", 0, -20);
PropertyValuesHolder upY = PropertyValuesHolder.ofFloat("translationY", 0, -20);
ObjectAnimator animatorUp = ObjectAnimator.ofPropertyValuesHolder(this, upX, upY)
.setDuration((long) (actionTime * 1000));
animatorUp.setStartDelay((long) (actionTime * 100));
animatorUp.start();

动画监听

除了AnimatorUpdateListener来监听过程中的值,也能够通过添加AnimatorListener来实现动画过程的监听以完成交互
实现该接口需要重写以下方法:

  • onAnimationStart
    动画开始时
  • onAnimationRepeat
    动画重复时
  • onAnimationCancel
    动画取消时
  • onAnimationEnd
    动画结束时

Animation中通过addListener方法传入。(动画对象都可以调用addListener方法来实现监听)
AnimatorUpdateListener只能够用于ValueAnimator之中(只有该类暴露了设置的接口)

假若重写四个方法太繁琐,可以使用AnimatorListenerAdapter来指定复写某方法

通过自定义对象属性来实现动画效果

前提:

  • 为对象设置需要操作属性的get以及set属性
    以下方法针对没有直接的getset方法的情况
    • 继承原始类,对外暴露该属性的getset
    • 用一个类来包装原始对象,进行扩展(设计模式–装饰模式)
  • 通过TypeEvaluator类实现属性变化的逻辑
  • 调用ofObjecct()方法

手动设置对应属性的时候,注意:

  • 对外暴露对应的set/get方法
  • 对应的set方法对该属性的改变会通过某种方式反映出来
    例如view.setWidth()view.getWidth()并不会完成预期效果

ViewPropertyAnimator

为了面向对象而新增的一个类,可以理解为是属性动画的一种简写方式。

通过View.animate().xxx().xxx()进行链式调用。
animate()方法后返回了一个ViewPropertyAnimator对象,之后所有方法都是在这个基础上进行调用。

一个简单例子:

1
2
3
4
5
6
7
8
9
10
11
view.animate().alpha(0f);
// 单个动画设置:将按钮变成透明状态
view.animate().alpha(0f).setDuration(3 * 1000).setInterpolator(new BounceInterpolator());
// 单个动画效果设置以及参数设置
view.animate().alpha(0f).x(50).y(50);
/**
* 组合动画:将按钮变成透明状态再移动到(50,50)处
* 特别注意:
* 动画会自启动,无需调用start()方法.因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动
* 该机制对于组合动画也同样有效,只要不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自启动
*/

AnimatorSet

承载一个动画集合,可以通过相关的方法调整展示的顺序。play()内部调用Builder()方法创建对应的对象。
例如:

1
2
3
4
AnimatorSet s = new AnimatorSet();
s.play(anim1).with(anim2);
s.play(anim2).before(anim3);
s.play(anim4).after(anim3);

xml中使用<set ... />标签来结合多个Animation后在代码里面通过以下方式加载动画

1
2
3
4
5
6
7
// 加载动画资源
Animation anim = AnimationUtils.loadAnimation(this,R.anim.xxx);
//设置动画结束后保留结束状态
anim.setFillAfter(true);
// 之后通过ImageView里面的startAnimation进行动画的开始
ImageView im = findViewById(xxx);
im.startAnimation(anim);

其他动画

layoutAnimation

针对于ViewGroup里面的子元素,譬如说ListView

  • XML:<layoutanimation .../>
  • 代码: LayoutAnimationController

XML之中指定ViewGrouplayoutAnimation属性即可生效

Activity切换效果

使用overridePendingTransiton(int start,int exit),分别传入动画的id,在startActivity(intent)finish()方法后生效

使用动画应该注意的

  • 避免使用帧动画(AnimationDrawable
    防止图片过大、过多出现OOM的情况
  • 防止内存泄漏:Activity退出关闭时关闭无限循环的动画
  • 使用view动画后若要做可见性操作请先clearAAnimation()清除
  • 尽量使用dp而不是px
  • 元素交互:
    在3.0后,属性动画的单击事件触发位置为移动后的位置,但是view动画还是在原来的位置
  • 建议开启硬件加速
    提高动画流畅性

附录:效果图






AccelerateDecelerateInterpolator
AccelerateInterpolator






AnticipateInterpolator
AnticipateOvershootInterpolator






BounceInterpolator
CycleInterpolator






DecelerateInterpolator
LinearInterpolator





OvershootInterpolator

点我回城