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