一、安卓动画

动画都是由于人眼观看的延迟,通过一帧一帧绘制连续的图像然后通过GPU显示到屏幕上的,所以无论哪种动画(平移动画、旋转等),图像都是随着时间往后推移而不断变换的。

安卓的动画有很多种供我们使用,比如最简单的Tween Animation(补间动画),帧动画,属性动画,甚至矢量动画。

下面是一个非常简单的平移动画

android 插值器设置透明度_动画

图像中
- 第一个小球做的是自由下落然后反弹的动画
- 第二个是小球在做上下浮动的动画
- 第三个动画小球先缓慢下降,之后迅速上升的动画

三个小球都不是做的单纯的单向平移。
那怎么才能将一个很简单的平移动画变成这种非单向的稍微复杂的动画呢?可能有人想到了多个动画放一起,简单的情景不排除这种方法,但是这里讲到的是动画的另外一个非常重要的工具——插值器Interpolator。

在安卓anim文件目录下编写一个动画xml文件,例如

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fillAfter="true"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="0"
    android:toYDelta="500" />

这是一个Y轴位移动画,从0到500的位置,用时2秒。
代码中执行:

final ImageView bounceBall = (ImageView) findViewById(R.id.bounceBall);
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.bounce_translate);
bounceBall.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        bounceBall.startAnimation(animation);
    }
});

运行结果:

android 插值器设置透明度_动画_02


其实可以看出来,小球并不是匀速运动的,而是开始逐渐加速最后又逐渐减速的动画过程,直到到达end。

为什么呢?

这是因为插值器(Interpolator)的存在,但是我们在代码中并没有使用到它,很简单的动画代码,为什么会有加速减速的过程呢?
其实,我们看看Animation的源码可以发现一段代码

protected void ensureInterpolator() {
    if (mInterpolator == null) {
        mInterpolator = new AccelerateDecelerateInterpolator();
    }
}

其中的mInterpolator就是当前动画的插值器,如果动画没有被设置指定插值器,那会默认使用AccelerateDecelerateInterpolator,这个插值器就是导致小球先加速后减速的原因。

二、插值器(Interpolator)

讲了那么多插值器,还没有真正的介绍插值器。插值器的用处,用简单的来说,是处理动画执行变化率的。

用白话的方式去解释(用平移动画来解释,其他的旋转动画、透明动画、缩放动画都一样的):
补间动画都是我们设置了“起始值start”、“末尾值end”和“时长s秒”这三个重要的属性,它的意思就是,在s秒内,控件从start的位置运动到end位置。大致就是这样,但是到底是怎么从start位置运动到end位置的,是加速呢,减速呢,还是先减速后加速还是更加复杂的,这个就是动画执行的过程控制。

插值器我们可以用坐标去分析一下动画运动过程:

android 插值器设置透明度_gpu_03


横坐标代表的是时间流逝,横坐标那个刻度1表示的是总体时间的1,而不是1秒,数学知识学过的总体1

纵坐标代表的是位移进度,同样的1也表示的总体1,比如上面说的控件按照Y轴从0运动到500,那这里的500就表示的总体1

简单点说就是坐标原点表示起始,1表示末尾。

图中的一条直线在数学上就是一条正比例函数,即:s=t,物理上路程s与时间t的这种关系可以叫做匀速运动,斜率不变即匀速,也即是说随着时间匀速变化,该平移运动也是在匀速运动的。

这种解释对照到我们平移运动就是,控件从开始到最后,都是以匀速变化运动到末尾位置的。再来看一个图

android 插值器设置透明度_插值器_04


这个我们对其运动分析:

控件开始匀速运动,在中间的某个时刻运动到了末尾end位置,然后接着它会匀速的运动到开始start位置

这就是Interpolator的原理
随着时间的流逝,控件到底怎么从起始位置运动到末尾位置的。

我们看Interpolator的源码,它是一个接口,并且继承的一个接口TimeInterpolator

/**
 * An interpolator defines the rate of change of an animation. This allows
 * the basic animation effects (alpha, scale, translate, rotate) to be 
 * accelerated, decelerated, repeated, etc.
 */
public interface Interpolator extends TimeInterpolator {
    // A new interface, TimeInterpolator, was introduced for the new android.animation
    // package. This older Interpolator interface extends TimeInterpolator so that users of
    // the new Animator-based animations can use either the old Interpolator implementations or
    // new classes that implement TimeInterpolator directly.
}

里面没有任何方法。

接着看TimeInterpolator源码:

/**
 * A time interpolator defines the rate of change of an animation. This allows animations
 * to have non-linear motion, such as acceleration and deceleration.
 */
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

其中只有一个方法,getInterpolation,它有一个float参数input,和一个float返回值
input参数就是0-1的“时间进度”,float类型的返回值就是0-1的“过程进度”

上面那种最简单的平移动画是用的AccelerateDecelerateInterpolator插值器
AccelerateDecelerateInterpolator源码

/**
 * An interpolator where the rate of change starts and ends slowly but
 * accelerates through the middle.
 */
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {

    public AccelerateDecelerateInterpolator() {
    }

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
    ……
}

它自己实现了getInterpolation的方法,它的运动进程与时间的变化关系式为:
y=cos((t+1)π )/2 + 0.5

用图像表示:

android 插值器设置透明度_gpu_05


高中物理知识,曲线的切线,就表示速度,斜率(切线倾斜度)越大表示速度越大

该AccelerateDecelerateInterpolator图像表明:

很明显,它的切线斜率是不断变化的,从一开始到中间的一段时间,曲线斜率(即速度)越来越大,但是最后它的速度就会越来越小。

这就是为什么上面的那个简单的平移动画是先加速,然后减速直到到达末尾位置的原因了。

三、几种安卓提供的Interpolator

其实安卓本身有几种供开发者调用的Interpolator,可以在anim文件目录的动画xml文件中设置

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:fillAfter="true"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="0"
    android:toYDelta="500" />

也可以代码中设置:

animation.setInterpolator(new BounceInterpolator());

除了默认的AccelerateDecelerateInterpolator以外,

AccelerateInterpolator速度随着时间流逝越来越大(加速运动)

android 插值器设置透明度_插值器_06

AnticipateInterpolator开始时缓慢向前,后向后越来越快(类似弹射)

android 插值器设置透明度_android 插值器设置透明度_07

BounceInterpolator小球下落弹跳

android 插值器设置透明度_动画_08

以及CycleInterpolator、LinearInterpolator等等,可以网上去搜搜各种安卓提供的Interpolator。

三、使用自己的Interpolator,想让过程怎么样就怎么样

只要实现安卓提供的Interpolator接口,重写getInterpolation方法就可以去自己制定动画的过程

1. 先编写一个先慢慢加速运动到结束位置,然后很快的运动到起始位置的动画

先在坐标系中画出运动过程:

android 插值器设置透明度_动画_09


根据图像可以知道是两个一次函数:

所以Interpolator可以这么写:

public class HyInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        if (input < 0.9f) {
            return 1.2345679f * input * input;
        } else {
            return -10.0f * input + 10.0f;
        }
    }
}

设置自己的插值器:

final Animation animation = AnimationUtils.loadAnimation(this, animId);
animation.setInterpolator(new HyInterpolator());
final ImageView backBall = (ImageView) findViewById(R.id.backBall);
backBall.setVisibility(View.VISIBLE);
backBall.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        backBall.startAnimation(animation);
    }
});

运行结果:

android 插值器设置透明度_动画_10

2. 我们模拟一个小球落下的动画
定义起始位置为高空start,地面为末尾位置end
小球先应该加速下降(也就是从start到end是加速的),然后减速弹起(不过不会弹起到起始位置)
然后又是加速下降,减速弹起
这样几次来回就是一个小球的落地动画了。

那这个动画的Interpolator应该怎么写呢?(这里需要一定的高中物理基础和高中数学基础)

首先需要根据运动过程大致的去画一个曲线(根据斜率越来越大表示加速,斜率越来越小表示减速)

可以简单点去使用抛物线的曲线。

android 插值器设置透明度_插值器_11

public class HyBounceInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        if (input <= 0.2) return getBinomial(25.0f, 0.0f, 0.0f, input);
        else if (input <= 0.4) return getBinomial(10f, -8.5f, 2.3f, input);
        else if (input <= 0.55) return getBinomial(32.66667f, -27.7f, 6.353333f, input);
        else if (input <= 0.7) return getBinomial(20f, -27f, 9.8f, input);
        else if (input <= 0.8) return getBinomial(40.0f, -57.0f, 21f, input);
        else if (input <= 0.9) return getBinomial(6f, -11.2f, 6.12f, input);
        else return getBinomial(6f, -10.4f, 5.4f, input);
    }

    private float getBinomial(float a, float b, float c, float t) {
        return a * t * t + b * t + c;
    }
}

代码中使用:

Animation animation = AnimationUtils.loadAnimation(this, animId);
animation.setInterpolator(new HyBounceInterpolator());

就可以了

android 插值器设置透明度_android 插值器设置透明度_12