1. 程式人生 > >當數學遇上動畫(2)

當數學遇上動畫(2)

當數學遇上動畫:講述 ValueAnimator、TypeEvaluator和TimeInterpolator之間的恩恩怨怨(2)

上一節的結論是,ValueAnimator就是由TimeInterpolatorTypeEvaluator這兩個簡單函式組合而成的一個複合函式。如下圖所示:

上一節我們還將TimeInterpolatorTypeEvaluator看作是工廠流水線上的兩個小員工,那麼ValueAnimator就是車間主管,TimeInterpolator這個小員工面對的是產品的半成品,他負責控制半成品輸出到下一個生產線的速度,而下一個生產線上的小員工TypeEvaluator

的任務就是打磨半成品得到成品,最後將成品輸出。

本小節進一步深究TimeInterpolatorTypeEvaluator在動畫實現過程中承擔的作用以及它們之間的聯絡與差異。

還是先說結論,藉助TimeInterpolator或者TypeEvaluator“單獨”來控制動畫所產生的動畫效果殊途同歸!

1 兩種特殊情況下的ValueAnimator

(1)上一節提到過,假設TimeInterpolatorLinearInterpolator(線性插值器,f(t)=t),也就是說時間比率不被“篡改”的話,那麼ValueAnimator對應的函式其實就簡化成了TypeEvaluator函式(F=g(x,a,b)=g(f(t),a,b)=g(t,a,b)

),即動畫實際上只由TypeEvaluator來控制。

這裡可以理解為,TimeInterpolator這個員工請假了,但是工廠為了不停止生產安排了一個自動機器人代替他的工作,它只會勻速地將半成品輸入到下一個生產線。

(2)同理,我們假設TypeEvaluator“LinearTypeEvaluator”(線性估值器,並沒有這個說法,所以加上引號,計算方式就是g(x,a,b)=a+x*(b-a))的話,那麼ValueAnimator對應的函式也可以簡化,F=g(x,a,b)=g(f(t),a,b)=a+f(t)*(b-a),即動畫實際上只由TimeInterpolator來控制。

同樣的,這裡可以理解為,TypeEvaluator

這個員工請假了,預設也有個自動機器人採用預設的操作將半成品加工成最終成品輸出。

(3)綜上所述,我們來思考上一節留下的問題,即TimeInterpolatorTypeEvaluator到底啥關係?
其實TimeInterpolator是用來控制動畫速度的,而TypeEvaluator是用來控制動畫中值的變化曲線的。
雖然它們本質的作用是不同的,但是它們兩個既可以聯手來控制動畫,也可以"單獨"來控制動畫(並非真的單獨,而是另一方有個預設操作)。

為什麼說TimeInterpolatorTypeEvaluator對於製作動畫有著殊途同歸的作用呢?

不難想象,在某些定製的情況下,上面兩種特殊情況下的構造出來的ValueAnimator所產生的動畫效果是一樣的!那如何來驗證我們的這個結論呢?我們可以通過構造兩個不同的特殊情況下的ValueAnimator來驗證。

下面的程式碼顯示了兩個ValueAnimator,都是在1s中內將float型別的數值從0變化到1。第一個ValueAnimator使用的是LinearInterpolator和自定義的TypeEvaluator,第二個ValueAnimator使用的是自定義的TimeInterpolator"LinearTypeEvaluator"。列印輸出的是兩個ValueAnimator每次值變化的時候的大小。

Java
1234567891011121314151617181920212223242526272829303132333435363738394041 ValueAnimator animator1=newValueAnimator();animator1.setFloatValues(0.0f,1.0f);animator1.setDuration(1000);animator1.setInterpolator(newLinearInterpolator());//傳入null也是LinearInterpolatoranimator1.setEvaluator(newTypeEvaluator(){@OverridepublicObjectevaluate(floatfraction,ObjectstartValue,ObjectendValue){return100*fraction;}});animator1.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){@OverridepublicvoidonAnimationUpdate(ValueAnimator animation){Log.e("demo 1",""+animation.getAnimatedValue());}});ValueAnimator animator2=newValueAnimator();animator2.setFloatValues(0.0f,1.0f);animator2.setDuration(1000);animator2.setInterpolator(newInterpolator(){@OverridepublicfloatgetInterpolation(floatinput){return100*input;}});animator2.setEvaluator(newTypeEvaluator(){@OverridepublicObjectevaluate(floatfraction,ObjectstartValue,ObjectendValue){returnfraction;}});animator2.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){@OverridepublicvoidonAnimationUpdate(ValueAnimator animation){Log.e("demo 2",""+animation.getAnimatedValue());}});animator1.start();animator2.start();

列印輸出的結果如下圖所示,從圖中可以看出,兩個ValueAnimator的在真實時間序列中的輸出結果是一樣的,也就說明如果將它們作用在同一個View元件的某個屬性上的話,那麼產生的動畫效果是完全一樣的。例如,可以將兩個ValueAnimator改成ObjectAnimator,並將其作用在兩個不同的TextView的translationY屬性上,你可以看到一樣的動畫效果。所以說,在特殊的單獨控制動畫的情況下,TimeInterpolatorTypeEvaluator對於製作動畫有著殊途同歸的作用。(注意結論的前提,那就是在我們理解了ValueAnimator內部動畫原理之後自己定製的一些特殊情況,它們並非總是能夠產生一樣的動畫效果)

2 簡單動畫例項分析:彈跳!

經過前面的分析,我們差不多理解了ValueAnimator是怎麼藉助TimeInterpolatorTypeEvaluator來實現動畫的了。在實現動畫的時候,為了簡便,我們常常可以選擇將TimeInterpolator設定為LinearInterpolator或者將TypeEvaluator設定為"LinearTypeEvaluator"這兩種特殊的方式。

舉個栗子!假設我們要來實現彈跳的動畫效果。首先我們要確定一個彈跳效果的函式曲線,自己想不太好想,我們先來看看專案EaseInterpolator中的EaseBounceOutInterpolator內部表示的函式曲線的形態。如下圖所示,它是一個分段函式,每個段內都是一個簡單的二次曲線。如果將這個曲線作用在View元件的translationY屬性上,那麼元件將在垂直方向上來回地跳動從而就形成了彈跳的效果。

我們先看下EaseBounceOutInterpolator的核心方法getInterpolation的實現,它其實就是刻畫了上面的函式曲線。

Java
12345678910111213141516171819202122232425262728293031 //傳入的引數input就是動畫的時間比率值fractionpublicfloatgetInterpolation(floatinput){if(input<(1/2.75))return(7.5625f*input*input);elseif(input<(2/2.75))return(7.5625f*(input-=(1.5f/2.75f))*input+0.75f);elseif(input<(2.5/2.75))return(7.5625f*(input-=(2.25f/2.75f))*input+0.9375f);elsereturn(7.5625f*(input-=(2.625f/2.75f))*input+0.984375f);}我們再看下AnimationEasingFunctions專案中實現這個效果的Easing函式類BounceEaseOut,它繼承自BaseEasingMethod,而BaseEasingMethod類實現了TypeEvaluator介面。BounceEaseOut類整理出來得到的核心方法evaluate的實現如下:@OverridepublicfinalFloatevaluate(floatfraction,Number startValue,Number endValue){floatt=mDuration*fraction;//已經過去的時間floatb=startValue.floatValue();//起始值floatc=endValue.floatValue()-startValue.floatValue();//結束值與起始值之間的差值floatd=mDuration;//總的時間間隔,t/d 就是已經過去的時間佔總時間間隔的比率if((t/=d)<(1/2.75f)){returnc*(7.5625f*t*t)+b;}elseif(t<(2/2.75f)){returnc*(7.5625f*(t-=(1.5f/2.75f))*t+.75f)+b;}elseif(t<(2.5/2.75)){returnc*(7.5625f*(t-=(2.25f/2.75f))*t+.9375f)+b;}else{returnc*(7.5625f*(t-=(2.625f/2.75f))*t+.984375f)+b;}}

仔細看下這兩個函式的實現很不難發現,如果EaseBounceOutInterpolator+"LinearEvaluator"(IntEvaluator或者FloatEvaluator)得到的結果與LinearInterpolator+BounceEaseOut(TypeEvaluator)得到的結果是一樣的啊!我們可以再寫個例子作用在兩個View上看下效果。

例子程式碼,作用在兩個不同的TextView上的兩個不同的ObjectAnimator:

Java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 //第一個ObjectAnimatorfinalObjectAnimator animator1=newObjectAnimator();animator1.setTarget(textView1);animator1.setPropertyName("translationY");animator1.setFloatValues(0f,-100f);animator1.setDuration(1000);animator1.setInterpolator(newLinearInterpolator());//使用線性插值器animator1.setEvaluator(newTypeEvaluator<Number>(){//自定義的TypeEvaluator@OverridepublicNumber evaluate(floatfraction,Number startValue,Number endValue){floatt=animator1.getDuration()*fraction;//已經過去的時間floatb=startValue.floatValue();//起始值floatc=endValue.floatValue()-startValue.floatValue();//結束值與起始值之間的差值floatd=animator1.getDuration();//總的時間間隔,t/d 就是已經過去的時間佔總時間間隔的比率if((t/=d)<(1/2.75f)){returnc*(7.5625f*t*t)+b;}elseif(t<(2/2.75f)){returnc*(7.5625f*(t-=(1.5f/2.75f))*t+.75f)+b;}elseif(t<(2.5/2.75)){returnc*(7.5625f*(t-=(2.25f/2.75f))*t+.9375f)+b;}else{returnc*(7.5625f*(t-=(2.625f/2.75f))*t+.984375f)+b;}}});animator1.start();//第二個ObjectAnimatorfinalObjectAnimator animator2=newObjectAnimator();animator2.setTarget(textView2);animator2.setPropertyName("translationY");animator2.setFloatValues(0f,-100f);animator2.setDuration(1000);animator2.setInterpolator(newTimeInterpolator(){//自定義的TimeInterpolator@OverridepublicfloatgetInterpolation(floatinput){if