當數學遇上動畫(2)
當數學遇上動畫:講述 ValueAnimator、TypeEvaluator和TimeInterpolator之間的恩恩怨怨(2)
上一節的結論是,ValueAnimator
就是由TimeInterpolator
和TypeEvaluator
這兩個簡單函式組合而成的一個複合函式。如下圖所示:
上一節我們還將TimeInterpolator
和TypeEvaluator
看作是工廠流水線上的兩個小員工,那麼ValueAnimator
就是車間主管,TimeInterpolator
這個小員工面對的是產品的半成品,他負責控制半成品輸出到下一個生產線的速度,而下一個生產線上的小員工TypeEvaluator
本小節進一步深究TimeInterpolator
和TypeEvaluator
在動畫實現過程中承擔的作用以及它們之間的聯絡與差異。
還是先說結論,藉助TimeInterpolator
或者TypeEvaluator
“單獨”來控制動畫所產生的動畫效果殊途同歸!
1 兩種特殊情況下的ValueAnimator
(1)上一節提到過,假設TimeInterpolator
是LinearInterpolator
(線性插值器,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)綜上所述,我們來思考上一節留下的問題,即TimeInterpolator
和TypeEvaluator
到底啥關係?
其實TimeInterpolator
是用來控制動畫速度的,而TypeEvaluator
是用來控制動畫中值的變化曲線的。
雖然它們本質的作用是不同的,但是它們兩個既可以聯手來控制動畫,也可以"單獨"
來控制動畫(並非真的單獨,而是另一方有個預設操作)。
為什麼說TimeInterpolator
和TypeEvaluator
對於製作動畫有著殊途同歸的作用呢?
不難想象,在某些定製的情況下,上面兩種特殊情況下的構造出來的ValueAnimator
所產生的動畫效果是一樣的!那如何來驗證我們的這個結論呢?我們可以通過構造兩個不同的特殊情況下的ValueAnimator
來驗證。
下面的程式碼顯示了兩個ValueAnimator
,都是在1s中內將float型別的數值從0變化到1。第一個ValueAnimator
使用的是LinearInterpolator
和自定義的TypeEvaluator
,第二個ValueAnimator
使用的是自定義的TimeInterpolator
和"LinearTypeEvaluator"
。列印輸出的是兩個ValueAnimator
每次值變化的時候的大小。
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
屬性上,你可以看到一樣的動畫效果。所以說,在特殊的單獨控制動畫的情況下,TimeInterpolator
和TypeEvaluator
對於製作動畫有著殊途同歸的作用。(注意結論的前提,那就是在我們理解了ValueAnimator
內部動畫原理之後自己定製的一些特殊情況,它們並非總是能夠產生一樣的動畫效果)
2 簡單動畫例項分析:彈跳!
經過前面的分析,我們差不多理解了ValueAnimator
是怎麼藉助TimeInterpolator
和TypeEvaluator
來實現動畫的了。在實現動畫的時候,為了簡便,我們常常可以選擇將TimeInterpolator
設定為LinearInterpolator
或者將TypeEvaluator
設定為"LinearTypeEvaluator"
這兩種特殊的方式。
舉個栗子!假設我們要來實現彈跳的動畫效果。首先我們要確定一個彈跳效果的函式曲線,自己想不太好想,我們先來看看專案EaseInterpolator中的EaseBounceOutInterpolator
內部表示的函式曲線的形態。如下圖所示,它是一個分段函式,每個段內都是一個簡單的二次曲線。如果將這個曲線作用在View元件的translationY
屬性上,那麼元件將在垂直方向上來回地跳動從而就形成了彈跳的效果。
我們先看下EaseBounceOutInterpolator
的核心方法getInterpolation
的實現,它其實就是刻畫了上面的函式曲線。
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:
Java12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | //第一個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 |