詳解Android如何實現(xiàn)自定義的動畫曲線
前言
最近在寫動畫相關(guān)的篇章,經(jīng)常會用到 Curve
這個動畫曲線類,那這個類到底怎么實現(xiàn)的?如果想自己來一個自定義的動畫曲線該怎么弄?本篇我們就來一探究竟。
曲線
Curve 類定義
查看源碼, Curve
類定義如下:
abstract?class?Curve?extends?ParametricCurve<double>?{ ??const?Curve(); ??@override ??double?transform(double?t)?{ ????if?(t?==?0.0?||?t?==?1.0)?{ ??????return?t; ????} ????return?super.transform(t); ??} ?? ??Curve?get?flipped?=>?FlippedCurve(this); }
看上去好像沒定義什么, 實際這里只是做了兩個處理,一個是明確的數(shù)據(jù)類型為 double
,另一個是對 transform
做了重載,也只是對參數(shù) t 做了特殊處理,保證參數(shù) t 的范圍在0-1之間,且起點值0.0和終點值1.0不被轉(zhuǎn)換函數(shù)轉(zhuǎn)換。主要定義在上一層的ParametricCurve
。文檔是建議子類重載transformInternal
方法,那我們就繼續(xù)往上看ParametricCurve
這個類的實現(xiàn),代碼如下:
abstract?class?ParametricCurve<T>?{ ??const?ParametricCurve(); ??T?transform(double?t)?{ ????assert(t?!=?null); ????assert(t?>=?0.0?&&?t?<=?1.0,?'parametric?value?$t?is?outside?of?[0,?1]?range.'); ????return?transformInternal(t); ??} ??@protected ??T?transformInternal(double?t)?{ ????throw?UnimplementedError(); ??} ??@override ??String?toString()?=>?objectRuntimeType(this,?'ParametricCurve'); }
可以看到,實際上 transform
方法除了做參數(shù)合法性驗證以外,其實就是調(diào)用了transformInternal
方法,因此子類必須要實現(xiàn)該方法,否則會拋出UnimplementedError
異常。
實例解析
上面的源碼可以看到,關(guān)鍵在于參數(shù) t
。這個參數(shù) t
代表什么呢?注釋里說的是:
Returns the value of the curve at point
t
. — 返回 t 點的曲線對應(yīng)的值。
因此 t
可以認(rèn)為是曲線的橫坐標(biāo),而為了保證曲線的一致性,做了歸一化處理,也就是t
的取值都是在0-1之間。這么說可能有點抽象,我們來看2個例子來對比就明白了,先看最簡單 Curves.linear
的實現(xiàn)。
class?_Linear?extends?Curve?{ ??const?_Linear._(); ??@override ??double?transformInternal(double?t)?=>?t; }
超級簡單吧,直接返回 t,其實對應(yīng)我們的數(shù)學(xué)的函數(shù)就是:
y?=?f(t)?=?t
對應(yīng)的曲線就是一條斜線。也就是說在設(shè)定的動畫時間內(nèi),會完成從0-1的線性轉(zhuǎn)變,也就是變化是均勻的。線性這個很好理解,我們再來看一個減速曲線decelerate
的實現(xiàn)。
class?_DecelerateCurve?extends?Curve?{ ??const?_DecelerateCurve._(); ??@override ??double?transformInternal(double?t)?{ ????t?=?1.0?-?t; ????return?1.0?-?t?*?t; ??} }
我們先看一下_DecelerateCurve 的計算表達(dá)式是什么。
回憶一下我們高中物理學(xué)的勻減速運動,加速度為負(fù)(即減速)的距離計算公式:
上面的減速曲線其實就可以看做是初始速度是2,加速度也是2的減速運動。為什么要是2這個值呢,這是因為 t 的取值范圍是0-1,這樣計算完的結(jié)果的取值范圍還是0-1。你肯定會問,為什么要保證曲線的計算結(jié)果要是0-1?我們來假設(shè)計算結(jié)果不為0-1會發(fā)生什么情況,比如我們要在屏幕上移動一個組件為60像素。假設(shè)動畫曲線初始值不為0。那就意味著一開始的移動距離是跳變的。同樣的,如果結(jié)束值不為1.0,意味著在最后一個點的距離值不是60.0,那么就意味著結(jié)束時需要從最后一個點跳到最終的60像素的位置(動畫需要保證最終的移動距離是60像素)這樣意味著動畫會出現(xiàn)跳變的效果,繪制曲線的話會是下面的樣子(綠色是正常的,紅線是異常的)。這樣的動畫體驗是很糟糕的!因此,這是一個關(guān)鍵點,如果你的自定義曲線的 transformInternal
方法的返回值范圍不是0-1,就意味著動畫會出現(xiàn)跳變,導(dǎo)致動畫缺幀的感覺。
image.png
有了這個基礎(chǔ),我們就可以解釋動畫曲線的基本機制了,實際上就是在給定的動畫時間(Duration
)范圍內(nèi),完成組件的初始狀態(tài)到結(jié)束狀態(tài)的轉(zhuǎn)變,這個轉(zhuǎn)變是沿著設(shè)定的 Curve
類完成的,而其橫坐標(biāo)是0-1.0,曲線的初始值和結(jié)束值分別是0和1.0,而至于中間值是可以低于0或超過1的。我們可以想像是我們沿著設(shè)定的曲線運動,最終無論如何都會達(dá)到設(shè)定的目的地,而至于怎么走,拐多少道彎,速度怎么變化都是曲線控制的。但是,如果你的曲線初始值不為0或結(jié)束值不為1,就像是跳懸崖的那種感覺!
正弦動畫曲線
我們來一個正弦曲線的動畫驗證一下上面的說法。
class?SineCurve?extends?Curve?{ ??final?int?count; ??const?SineCurve({this.count?=?1})?:?assert(count?>?0); ??@override ??double?transformInternal(double?t)?{ ????return?sin(2?*?count*?pi?*?t); ??} }
count 參數(shù)用于控制周期,即達(dá)到目的地之前可以多來幾個來回。這里我們發(fā)現(xiàn),初始值是0,但是一個周期(2π)結(jié)束值也是0,這樣在動畫結(jié)束前會出現(xiàn)跳變的結(jié)果。來看一下示例代碼,這個示例是讓圓形向下移動60像素。
AnimatedContainer( ??decoration:?BoxDecoration( ????color:?Colors.blue, ????borderRadius:?BorderRadius.circular(30.0), ??), ??transform:?Matrix4.identity()..translate(0.0,?up???60.0?:?0.0,?0.0), ??duration:?Duration(milliseconds:?3000), ??curve:?SineCurve(count:?1), ??child:?ClipOval( ????child:?Container( ??????width:?60.0, ??????height:?60.0, ??????color:?Colors.blue, ????), ??), )
運行效果如下,注意看最后一幀從0的位置直接跳到了60的位置。
跳動動畫
這個怎么調(diào)呢,我們來看一下正弦曲線的樣子。
正弦曲線
如果我們要滿足0-1范圍的要求,那么要往后再移動90度才能夠達(dá)到。但是,這樣還有個問題,這樣破壞了周期性,比如設(shè)置 count=2
的時候結(jié)果又不對了。我們來看一下規(guī)律,實際上只有第一個周期需要多移動90度(圖中箭頭指向的點),后面的都是按360度(即2π)為周期了。也就是角度其實是按2.5π,4.5π,6.5π……規(guī)律來的,對應(yīng)的角度公式其實就是:
所以調(diào)整后的正弦曲線代碼為:
class?SineCurve?extends?Curve?{ ??final?int?count; ??const?SineCurve({this.count?=?1})?:?assert(count?>?0); ??@override ??double?transformInternal(double?t)?{ ????//?需要補償pi/2個角度,使得起始值是0.終止值是1,避免出現(xiàn)最后突然回到0 ????return?sin(2?*?(count?+?0.25)?*?pi?*?t); ??} }
再看調(diào)整后的效果,是不是絲滑般地過渡了?
總結(jié)
本篇介紹了 Flutter 動畫曲線類的原理和控制動畫的機制,實際上 Curve 類就是在指定的時間內(nèi),沿曲線完成從起點到終點的過渡。但是為了保證動畫平滑過渡,應(yīng)該保證自定義曲線的transformInternal
方法返回值的起始值和結(jié)束值分別是0和1。
到此這篇關(guān)于詳解Android如何實現(xiàn)自定義的動畫曲線的文章就介紹到這了,更多相關(guān)Android動畫曲線內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Studio實現(xiàn)簡單計算器APP
這篇文章主要為大家詳細(xì)介紹了Android Studio實現(xiàn)簡單計算器APP,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-03-03android Retrofit2+okHttp3使用總結(jié)
本篇文章主要介紹了android Retrofit2+okHttp3使用總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android異常 java.lang.IllegalStateException解決方法
這篇文章主要介紹了Android異常 java.lang.IllegalStateException解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android網(wǎng)絡(luò)請求-sign參數(shù)的設(shè)置方式
這篇文章主要介紹了Android網(wǎng)絡(luò)請求-sign參數(shù)的設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android studio開發(fā)小型對話機器人app(實例代碼)
這篇文章主要介紹了Android studio開發(fā)一個小型對話機器人app,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Flutter List數(shù)組避免插入重復(fù)數(shù)據(jù)的實現(xiàn)
這篇文章主要介紹了Flutter List數(shù)組避免插入重復(fù)數(shù)據(jù)的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09