插值器与估值器

先来了解以下动画的定义:

动画是指由许多静止的画面,以一定的速度(如每秒16张)连续播放时,肉眼因视觉残象产生错觉,而误以为画面活动的作品。

Android中的动画也遵循这个原理,只不过它的每一帧都是通过插值器与估值器动态计算出一个矩阵,然后应用到控件上得到的。

在计算的过程中,插值器负责计算动画执行的百分比,估值器负责根据百分比来计算属性值

举个例子:

ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.setDuration(1000);

这里,我们以 ValueAnimator 举例,做一个值动画,让 1 在 1000ms 内匀速变到 100。

假设这个动画有11帧,那么在第一帧的时候,是保持原来的位置不动,随着时间的流逝,在过了100ms之后,来到了第2帧,这个时候插值器就派上用场了。根据匀速运行的计算,在 100ms 的时候,动画应该执行了 10%,所以会出现一个时间 t 与动画值的函数:

f(t) = t / duration * 100

记住这个函数,后面的东西都是围绕它来的

让我们看看线性插值器的代码,来对比一下:

android.view.animation.LinearInterpolator#getInterpolation

    public float getInterpolation(float input) {
        return input;
    }
	// 转换成函数就是 f(x) = x,这个f(x) 相当于上面函数的 t / duration
	// 也就是说,这里只是计算了动画执行的百分比,还没有计算到动画的具体值

这里的 input 参数,表示的是时间的流逝百分比,它返回的是动画执行的百分比

这与上面的 f(t) 函数的意义是一样的,只不过 f(t) 的参数是 t, 而 getInterpolation 的参数是 t/duration。

回想一下,刚接触函数的时候,f(x) = x 这个函数,就是一条直线,所以这个插值器叫线性插值器。

我们再来看看加速插值器的代码:

android.view.animation.AccelerateInterpolator#getInterpolation

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

这里我们只考虑 mFactor == 1.0f 的情况,所以有这样的一个函数 f(x) = x * x 。

那么这个函数是什么意思呢?首先,输入范围是 【0,1】,经过函数变换之后,还是【0,1】。这个是很重要的,虽然你可以返回大于1或者小于0的值,但是大于1的会被当成最后一帧处理,小于0的作为第一帧处理。具体请看 android.animation.KeyframeSet#getValue 这个方法。

再回想一下 f(x) = x * x 这个函数的图像,在【0,1】的范围,它的斜率是从低到高的,所以表现出来的就是一个加速行为。想不通的可以这样想,在前面的 【0, 0.5】 的范围内,f(x) 的值从0只增加到了0.25,而在后面的【0.5,1】的范围内,f(x) 的值从 0.25 增加到了 1,所以是增加的速度越来越快。

那么,这里我们自定义一个插值器:

    public float getInterpolation(float input) {
        return 1 - input;
    }

猜猜,它会是一个什么样的行为?其实就是一个反向的线性插值器,比如,从 1 到 100,这个插值器会从 100 变到 1。


再看估值器,一般的我们很少会自定义估值器,除非有特殊的需要,比如计算颜色,颜色的算法不像数值的计算,按照百分比乘一下就完事了,因为颜色涉及到 RGB 3个值,所以需要将 RGB 分开来计算。

我们还是看看自带的几个估值器的代码。

先看简单的 int 估值器,这个我们也能写出来,动画的百分比知道了,需要变化的范围我们也知道,用百分比乘以变化范围再加上起始值就ok了。

android.animation.IntEvaluator#evaluate

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

嗯,和我们想的一摸一样,下一个。

看看 ARGB 的估值器:

android.animation.ArgbEvaluator#evaluate

    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >>  8) & 0xff) / 255.0f;
        float startB = ( startInt        & 0xff) / 255.0f;

        int endInt = (Integer) endValue;
        float endA = ((endInt >> 24) & 0xff) / 255.0f;
        float endR = ((endInt >> 16) & 0xff) / 255.0f;
        float endG = ((endInt >>  8) & 0xff) / 255.0f;
        float endB = ( endInt        & 0xff) / 255.0f;

        // convert from sRGB to linear
        startR = (float) Math.pow(startR, 2.2);
        startG = (float) Math.pow(startG, 2.2);
        startB = (float) Math.pow(startB, 2.2);

        endR = (float) Math.pow(endR, 2.2);
        endG = (float) Math.pow(endG, 2.2);
        endB = (float) Math.pow(endB, 2.2);

        // compute the interpolated color in linear space
        float a = startA + fraction * (endA - startA);
        float r = startR + fraction * (endR - startR);
        float g = startG + fraction * (endG - startG);
        float b = startB + fraction * (endB - startB);

        // convert back to sRGB in the [0..255] range
        a = a * 255.0f;
        r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
        g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
        b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

        return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
    }

嗯,看上去代码很多,但是我们这里取 R 来分析,G、B 是一样的。

// 拿到起始颜色值的16-24位
float startR = ((startInt >> 16) & 0xff) / 255.0f;

// 拿到终止颜色值的16-24位
float endR = ((endInt >> 16) & 0xff) / 255.0f;

// 将起始颜色与终止颜色转化为线性的,便于用百分比计算
startR = (float) Math.pow(startR, 2.2);
endR = (float) Math.pow(endR, 2.2);

// 这里我们就很熟悉了,与 int 是一样的
float r = startR + fraction * (endR - startR);

// 再将线性的转回 RGB
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;

这样一看,是不是条理很清晰。

总结一下:

  • 插值器负责计算动画执行的百分比
  • 估值器起始就是根据动画的百分比、动画变化量(插值器计算得到的)来计算动画值,如果不能直接计算的,需要转换一下再计算。嗯,就这么多。

版权声明:本文为weixin_43130724原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。