Android动画运用

1,417 阅读12分钟

谈谈动画

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

逐帧动画

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

利用XML进行定义

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

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

<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属性来设定对应属性的开始值和终点值。 以移动为例:

<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
Animation translateAnimation = new TranslateAnimation(05000500);
/** 
 * 1. fromXDelta :视图在水平方向x 移动的起始值
 * 2. toXDelta :视图在水平方向x 移动的结束值
 * 3. fromYDelta :视图在竖直方向y 移动的起始值
 * 4. toYDelta:视图在竖直方向y 移动的结束值
 */
  • scale
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
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
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()这几种方法 分别对应其中的过渡值:整形值、浮点值、对象、颜色值、存储属性的动画改变值 实际开发里面常用前面三种,在使用的时候传入对应属性的变化值

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)

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

使用样例如下

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之中调用并传入参数即可。

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对象,之后所有方法都是在这个基础上进行调用。

一个简单例子:

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()方法创建对应的对象。 例如:

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

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

// 加载动画资源
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

点我回城