先来了解以下动画的定义:
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;
这样一看,是不是条理很清晰。
总结一下:
- 插值器负责计算动画执行的百分比
- 估值器起始就是根据动画的百分比、动画变化量(插值器计算得到的)来计算动画值,如果不能直接计算的,需要转换一下再计算。嗯,就这么多。