Android中你不得不知道的动画知识 (一)

达芬奇密码2018-06-27 13:55

动画基础

在日常开发中,我们往往不是仅仅局限于实现产品相应的功能就可以了,为了达到更好的用户体验我们还会加入一些比较好看的动画效果。今天我就来总结一下动画相关的一些知识。

Android系统提供了很多丰富的API去实现UI的动画,最主要的划分为如下几类:

  • Property Animation:即属性动画,他是从Android 3.0(API level 11)以后开始加入的,这种动画不局限于一般的View,可以给任何Object添加,包括没有在屏幕上渲染的对象。而且属性动画非常灵活,提供了简单的扩展方法,可以定义任何类型的动画,适配比较复杂的动画场景。
  • Drawable Animation:即帧动画,这种动画相对来说比较简单,他的原来跟早期动画片差不多,将一组连续的动作拆分成一系列的序列帧,然后快速播放这组序列帧,利用视觉暂留产生动画效果。每一个drawable就相当于一个帧。
  • View Animation:视图动画是针对View的动画,通过设置,可以再一定时间范围内改变View的位置,透明度,大小以及旋转角度从而实现动画效果。

每一种动画的实现方法与实现的复杂度都是不尽相同的,针对的应用场景也是不是一样的,针对每一种动画我们慢慢展开来讲一下。

另外,Airbnb发布了一个Lottie动画库,实现原理与上面集中都是不同,他可以直接将AE里面的视频转化成动效,并且实现很高效,性能更加优良,让我们几行代码实现复杂绚丽的动画,这个具体后面也会讲到。

最后结合这些,看一下项目中的实际应用效果。

Drawable Animation

帧动画允许你实现像播放幻灯片一样的效果,这种动画的实质其实就是一组连续的drawable,这种动画的XML定义文件一般放在res/drawable目录下面。具体的XML使用方法可以看下官方文档.

使用说明

我们可以使用Java代码或者XML来定义一组序列帧,不过Android官方还是推荐我们使用XML的方式来定义。规则如下:

  • 以为根节点,包含一个或者多个item,有如下属性
    1. android:oneshot,true代表只执行一次,false代表循环执行
    2. item,表示动画的第一帧
  • 子节点为,包含如下属性:
    1. android:drawable 动画中的某一帧
    2. android:duration 该帧显示的时间

实例

在drawable下面定义如下xml文件:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
    <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

然后在代码中开启动画:

AnimationDrawable rocketAnimation;

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
  rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
  rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}

public boolean onTouchEvent(MotionEvent event) {
  if (event.getAction() == MotionEvent.ACTION_DOWN) {
    rocketAnimation.start();
    return true;
  }
  return super.onTouchEvent(event);
}

以上就是帧动画的使用方法,比较简单。

View Animation

视图动画,也叫补间动画,可以对一个View执行一系列的简单变换,比如大小,位置,旋转角度和透明度等等。同样的,补间动画也可以通过Java代码和XML文件两种方式来定义。和补间动画相关的class主要有:

  • Animation:指一个补间动画,是抽象类
  • AnimationSet:指一组补间动画,可以包含多个Animation,并且可以同时执行几个动画,比如在修改透明度的动画下一起修改位置
  • AlphaAnimation:透明度动画
  • RotateAnimation:旋转动画
  • ScaleAnimation:缩放动画
  • TranslateAnimation:位移动画
Animation类

Animation是补间动画所有类的基类,它提供了一些通用的动画属性方法。

xml属性 Java方法 说明
android:detachWallpaper setDetachWallpaper(boolean) 是否在壁纸上运行
android:duration setDuration(long) 动画持续时间,毫秒为单位
android:fillAfter setFillAfter(boolean) 控件动画结束时是否保持动画最后的状态
android:fillBefore setFillBefore(boolean) 控件动画结束时是否还原到开始动画前的状态
android:fillEnabled setFillEnabled(boolean) 与android:fillBefore效果相同
android:interpolator setInterpolator(Interpolator) 设定插值器(指定的动画效果,譬如回弹等)
android:repeatCount setRepeatCount(int) 重复次数
android:repeatMode setRepeatMode(int) 重复类型有两个值,reverse表示倒序回放,restart表示从头播放
android:startOffset setStartOffset(long) 调用start函数之后等待开始运行的时间,单位为毫秒
android:zAdjustment setZAdjustment(int) 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal

上面这些属性还是比较好理解的,唯独这个插值器可能会比较难以理解,下面我们就来了解一下什么是插值器。

定义:插值器是用来定义动画的变化速率的,他可以让动画的变化(透明度变化,位置变化,旋转角度变化,大小变化)是加速,减速或者是匀速。

系统为我们提供了很多个插值器:

java类 xml id值 描述
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 动画始末速率较慢,中间加速
AccelerateInterpolator @android:anim/accelerate_interpolator 动画开始速率较慢,之后慢慢加速
DecelerateInterpolator @android:anim/decelerate_interpolator 动画开始快然后慢
LinearInterpolator @android:anim/linear_interpolator 动画匀速改变
OvershootInterpolator @android:anim/overshoot_interpolator 向前弹出一定值之后回到原来位置

如果系统提供的插值器不能满足你的需要的话,你还可以自己自定义:

public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
    ......
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
    ......
}
AlphaAnimation类

java类 xml id值 描述
android:fromAlpha AlphaAnimation(float fromAlpha, …) 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
android:toAlpha AlphaAnimation(…, float toAlpha) 动画结束的透明度,同上

在Animation的基础上,AlphaAnimation只多了这两个属性,用来控制开始与结束的透明度。

RotateAnimation类

java类 xml id值 描述
android:fromDegrees RotateAnimation(float fromDegrees, …) 旋转开始角度,正代表顺时针度数,负代表逆时针度数
android:toDegrees RotateAnimation(…, float toDegrees, …) 旋转结束角度,正代表顺时针度数,负代表逆时针度数
android:pivotX RotateAnimation(…, float pivotX, …) 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY RotateAnimation(…, float pivotY) 缩放起点Y坐标,同上规律
ScaleAnimation类

java类 xml id值 描述
android:fromXScale ScaleAnimation(float fromX, …) 初始X轴缩放比例,1.0表示无变化
android:toXScale ScaleAnimation(…, float toX, …) 结束X轴缩放比例
android:fromYScale ScaleAnimation(…, float fromY, …) 初始Y轴缩放比例
android:toYScale ScaleAnimation(…, float toY, …) 结束Y轴缩放比例
android:pivotX ScaleAnimation(…, float pivotX, …) 缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY ScaleAnimation(…, float pivotY) 缩放起点Y轴坐标,同上规律

TranslateAnimation类

java类 xml id值 描述
android:fromXDelta TranslateAnimation(float fromXDelta, …) 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:fromYDelta TranslateAnimation(…, float fromYDelta, …) 起始点Y轴从标,同上规律
android:toXDelta TranslateAnimation(…, float toXDelta, …) 结束点X轴坐标,同上规律
android:toYDelta TranslateAnimation(…, float toYDelta) 结束点Y轴坐标,同上规律

AnimationSet类

AnimationSet继承自Animation,是上面四种的组合容器管理类,他可以同时启动多个动画,一般常用的API有;

  • void addAnimation(Animaiton a):添加一个Animation
  • List getAnimations():获取AnimationSet里的所有Animation
  • void setDutation(long durationMillis):动画时长

总的来说,AnimationSet只是对Animation的一种组合而已。

通过xml也可以直接定义一个AnimationSet:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

然后在代码中加载这个动画:

ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation animationDemo = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(animationDemo);

Property Animation

自Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation),它的功能非常强大,弥补了之前补间动画的一些缺陷,几乎是可以完全替代掉补间动画了。

为什么需要Property Animation

  1. 补间动画相对来说局限性比较大,只能实现位移,缩放,旋转和透明度这些变化,想要其他效果就要自己动手实现了。
  2. 补间动画只能对一个View来操作才可以
  3. 补间动画仅仅是改变了View的显示效果,并没有真正的改变View的属性。比较我们将一个Button通过TranslateAnimation将他从位置1移动到了位置2,并且将其停留在了位置2,这时候你去点击位置2是没有点击效果的,而点击位置你会发现却触发了button的点击事情,这就是补间动画的最大问题。我们常常还要通过其他的方式来规避这种问题。

也正是因为这些原因,Android开发团队决定在3.0版本当中引入属性动画这个功能,属性动画把上述的问题全部解决掉了,下面我们就来一起看一看。

新引入的属性动画机制已经不再是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去完成了。

既然属性动画的实现机制是通过对目标对象进行赋值并修改其属性来实现的,那么之前所说的按钮显示的问题也就不复存在了,如果我们通过属性动画来移动一个按钮,那么这个按钮就是真正的移动了,而不再是仅仅在另外一个位置绘制了而已。也就不需要考虑动画带来的View的交互问题了。

ValueAnimator

ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

本质来说,ValueAnimation只是对一个初始值到终点値之间输出一系列的中间值,然后赋值对应的属性,从而实现动画效果。看一个例子:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);  
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start();

运行后会产生一系列的值:

另外,ofFloat()方法是一个变参函数,可以输入任意多的参数,那么他就会从参数1变道参数2,然后变道参数3...

类似的,ValueAnimator还有ofIntofObject两个方法。

此外,还可以

  • 通过setStartDelay()方法来设置动画延迟播放的时间。
  • 通过setRepeatCount()方法来设置动画的循环播放的次数
  • 通过setRepeatMode()方法来设置循环模式,RESTART表示从头开始,REVERSE表示倒序循环播放。

相关阅读:Android中你不得不知道的动画知识 (二)

本文来自网易实践者社区,经作者钟金宝授权发布。