當數學遇上動畫:講述ValueAnimator
、TypeEvaluator
和TimeInterpolator
之間的恩恩怨怨(3)
上一節我們得到一個重要的結論,借助TimeInterpolator
或者TypeEvaluator
"單獨" 來控制動畫所產生的動畫效果殊途同歸!
此外,上一節結尾我們還說到,項目AnimationEasingFunctions和項目EaseInterpolator本質上是差不多的,都是定義了一些動畫效果對應的函數曲線。前者是將其封裝成了TypeEvaluator
,后者是將其封裝成了Interpolator
!
這一節我們來研究下這些函數曲線。
1 緩動函數曲線
下圖顯示了常見的這些函數曲線,到底這些函數曲線都是什么鬼呢?
這些函數曲線最早是由Robert Penner提出來用于實現補間動畫的"Penner easing functions"
,這些曲線主要分成10類,包括"BACK", "BOUNCE", "CIRCULAR", "ELASTIC", "EXPO", "QUAD", "CUBIC", "QUART", "QUINT", "SINE"
,每一類下面都有緩動進入、緩動退出以及緩動進入和退出三種效果,所以共有30個。這些效果對照著函數曲線來看其實也挺好理解,"QUAD", "CUBIC", "QUART", "QUINT"
分別對應著二次、三次、四次以及五次曲線,"SINE"
對應正弦函數曲線,"EXPO"
對應指數函數曲線等等。其中"BACK"
和"ELASTIC"
有上沖和下沖的效果。
Robert Penner在Github上開源了jQuery的版本實現,隨后也就有了很多不同語言版本的實現,例如Java版本的jesusgollonet/processing-penner-easing以及代碼家的Android版本的AnimationEasingFunctions等等。
這些版本的實現都是4個參數的,分別是起始值b
、數值間隔c
(結束值-起始值)、當前時間t
、時間間隔d
。
//不帶緩動,也就是前面說的“線性”估值器
function noEasing (t, b, c, d) {
return c * (t / d) + b;
}
//帶緩動效果,例如二次曲線形式
easeInQuad: function (t, b, c, d) { //緩動進入
return c*(t/=d)*t + b;
},
easeOutQuad: function (t, b, c, d) {//緩動退出
return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (t, b, c, d) {//緩動進入和退出
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
那為什么與之殊途同歸的EaseInterpolator是1個參數的呢?
//QuadInOut Interpolator
public float getInterpolation(float input) {
if((input /= 0.5f) < 1) {
return 0.5f * input * input;
}
return -0.5f * ((--input) * (input - 2) - 1);
}
這是因為當Interpolator
傳入到后面的TypeEvaluator
的時候就有了起始值、結束值以及時間間隔(時間間隔定義在緩動函數內部,只有部分緩動函數需要這個參數)這3個參數,可以參考下面的代碼來理解,所以說,它們在本質上還是一樣的!
fraction = getInterpolation(input) ==> 這種1個參數形式其實也可以等效于 easingfunction(currentTime, 0, 1, totalTime)
value = evaluate(fraction, startValue, endValue) = startValue + fraction * (endValue - startValue)
2 One more thing
看到這里的話,我們就會想啦,如果我們把函數曲線抽象出來,然后再提供相應的轉換方法,使其輕輕松松地轉換成Interpolator
和TypeEvaluator
的話,如此,豈不善哉?
所以,我就站在眾多巨人們的肩膀上,寫了一個新項目Yava,項目代碼非常簡單,而且代碼很少只有4個重要的類,它實現的功能就是將抽象的函數曲線輕松轉換成立即可用的Interpolator
和TypeEvaluator
,并且提供了常見的30個緩動函數(Easing Functions)的實現,它們既可以當做Interpolator
來用,又可以當做TypeEvaluator
來用,非常方便。
這里我直接把這4個重要類的代碼貼出來吧。
(1) IFunction
接口
/**
* 函數接口:給定輸入,得到輸出
*/
public interface IFunction {
float getValue(float input);
}
(2)AbstractFunction
抽象類
/**
* 抽象函數實現,既可以當做簡單函數使用,也可以當做Interpolator或者TypeEvaluator去用于制作動畫
*/
public abstract class AbstractFunction implements IFunction, Interpolator, TypeEvaluator<Float> {
@Override
public float getInterpolation(float input) {
return getValue(input);
}
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
return startValue + getValue(fraction) * (endValue - startValue);
}
}
(3)Functions
類
/**
* 工具類,將自定義的函數快速封裝成AbstractFunction
*/
class Functions {
public static AbstractFunction with(final IFunction function) {
return new AbstractFunction() {
@Override
public float getValue(float input) {
return function.getValue(input);
}
};
}
}
(4)EasingFunction
枚舉:包含了30個常見的緩動函數
/**
* 常見的30個緩動函數的實現
*/
public enum EasingFunction implements IFunction, Interpolator, TypeEvaluator<Float> {
/* ------------------------------------------------------------------------------------------- */
/* BACK
/* ------------------------------------------------------------------------------------------- */
BACK_IN {
@Override
public float getValue(float input) {
return input * input * ((1.70158f + 1) * input - 1.70158f);
}
},
BACK_OUT {
@Override
public float getValue(float input) {
return ((input = input - 1) * input * ((1.70158f + 1) * input + 1.70158f) + 1);
}
},
BACK_INOUT {
@Override
public float getValue(float input) {
float s = 1.70158f;
if ((input *= 2) < 1) {
return 0.5f * (input * input * (((s *= (1.525f)) + 1) * input - s));
}
return 0.5f * ((input -= 2) * input * (((s *= (1.525f)) + 1) * input + s) + 2);
}
},
//other easing functions ......
//如果這個function在求值的時候需要duration作為參數的話,那么可以通過setDuration來設置,否則使用默認值
private float duration = 1000f;//目前只有ELASTIC***這三個是需要duration的,其他的都不需要
public float getDuration() {
return duration;
}
public EasingFunction setDuration(float duration) {
this.duration = duration;
return this;
}
//將Function當做Interpolator使用,默認的實現,不需要枚舉元素去重新實現
@Override
public float getInterpolation(float input) {
return getValue(input);
}
//將Function當做TypeEvaluator使用,默認的實現,不需要枚舉元素去重新實現
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
return startValue + getValue(fraction) * (endValue - startValue);
}
//幾個數學常量
public static final float PI = (float) Math.PI;
public static float TWO_PI = PI * 2.0f;
public static float HALF_PI = PI * 0.5f;
}
這個項目的緩動函數的實現參考自EaseInterpolator中的實現,但是這個項目的代碼和EaseInterpolator以及AnimationEasingFunctions這兩個項目都完全不一樣,非常簡單易懂,既保留了原有項目應有的功能,同時為項目的使用場景提供了更多的可能,任何你想使用Interpolator
或者TypeEvaluator
都能使用它。
舉個例子,以上一節中的彈跳動畫效果為例,現在可以直接使用EasingFunction.BOUNCE_OUT
作為Interpolator
或者TypeEvaluator
來使用:
第一種方式:使用線性插值器和自定義的TypeEvaluator
ObjectAnimator animator1 = new ObjectAnimator();
animator1.setTarget(textView1);
animator1.setPropertyName("translationY");
animator1.setFloatValues(0f, -100f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());
animator1.setEvaluator(EasingFunction.BOUNCE_OUT); //這里將EasingFunction.BOUNCE_OUT作為TypeEvaluator來使用
animator1.start();
第二種方式:使用自定義的Interpolator和"線性估值器"
ObjectAnimator animator2 = new ObjectAnimator();
animator2.setTarget(textView2);
animator2.setPropertyName("translationY");
animator2.setFloatValues(0f, -100f);
animator2.setDuration(1000);
animator2.setInterpolator(EasingFunction.BOUNCE_OUT); //這里將EasingFunction.BOUNCE_OUT作為Interpolator來使用
animator2.setEvaluator(new FloatEvaluator());
animator2.start();
如果你想使用自己定義的函數來制作動畫,可以使用Functions
的with
方法,傳入一個實現了IFunction
接口的類就行,返回值你既可以當做Interpolator
,也可以當做TypeEvaluator
來使用
代碼示例:
ObjectAnimator animator1 = new ObjectAnimator();
animator1.setTarget(textView1);
animator1.setPropertyName("translationY");
animator1.setFloatValues(0f, -100f);
animator1.setDuration(1000);
animator1.setInterpolator(new LinearInterpolator());
animator1.setEvaluator(Functions.with(new IFunction() { //自定義為TypeEvaluator
@Override
public float getValue(float input) {
return input * 2 + 3;
}
}));
animator1.start();
或者這樣:
ObjectAnimator animator2 = new ObjectAnimator();
animator2.setTarget(textView2);
animator2.setPropertyName("translationY");
animator2.setFloatValues(0f, -100f);
animator2.setDuration(1000);
animator2.setInterpolator(Functions.with(new IFunction() { //自定義為Interpolator
@Override
public float getValue(float input) {
return input * 2 + 3;
}
}));
animator2.setEvaluator(new FloatEvaluator());
animator2.start();
為了方便查看定義出來的Interpolator
和TypeEvaluator
的效果,我將前面兩個項目中的可視化部分整理到項目Yava中,樣例應用還包含了上一節的用來作驗證的例子,最終效果如下:
恭喜你終于看完了,也恭喜自己終于寫完了。至此,你可能還有一個疑惑,那就是那些函數曲線是怎么想出來的?這個...我也不知道,我也想知道,別問我,去問Robert Penner吧 ??
最后,我還準備寫另一個Android動畫效果庫wava,神一樣的代碼家還做了一個超厲害的項目AndroidViewAnimations,目前我的wava
只是基于它做些改進,后期我打算加上一些很特別的東西,暫時不表,歡迎關注項目wava ??