本文通過代碼層面去分析Flutter動(dòng)畫的實(shí)現(xiàn)過程汽纤,介紹了Flutter中的Animation庫以及Physics庫动猬。
1. 介紹
本文會從代碼層面去介紹Flutter動(dòng)畫,因此不會涉及到Flutter動(dòng)畫的具體使用。
1.1 Animation庫
Flutter的animation庫只依賴兩個(gè)庫绅项,Dart庫以及physics庫。animation是采用Dart編寫的比肄,所以依賴Dart庫是很正常的快耿。physics庫是什么呢?
Simple one-dimensional physics simulations, such as springs, friction, and gravity, for use in user interface animations.
physics庫是一個(gè)簡單的物理模擬的庫芳绩,包含彈簧掀亥、阻尼、重力等物理效果妥色。前篇文章介紹過Flutter動(dòng)畫搪花,F(xiàn)lutter動(dòng)畫兩個(gè)分類中的一個(gè)就是基于物理的動(dòng)畫(Physics-based animation)。所以可以猜測出animation庫中有一部分代碼嘹害,是實(shí)現(xiàn)了另一種動(dòng)畫--補(bǔ)間動(dòng)畫(Tween Animation)撮竿。
通過這種庫的劃分,也可以大致猜測出笔呀,基于物理動(dòng)畫的庫是后續(xù)添加的幢踏。這說明了什么呢?
- 補(bǔ)間動(dòng)畫是現(xiàn)代移動(dòng)端相對基礎(chǔ)的動(dòng)畫類型许师,這個(gè)是必須的房蝉;
- 基于物理動(dòng)畫是在體驗(yàn)上的改善添加上去的,大致可以猜測出為iOS端的體驗(yàn)優(yōu)化微渠;
1.2 Physics庫
Flutter基于物理的動(dòng)畫惨驶,實(shí)際上是相當(dāng)簡單的。目前實(shí)現(xiàn)了彈簧敛助、阻尼粗卜、重力三種物理效果,整個(gè)庫的代碼量也不多纳击。詳細(xì)的代碼在下面的部分介紹续扔,在此處攻臀,我們先來說下基于物理的動(dòng)畫庫的原理。
基于物理的動(dòng)畫纱昧,給我們的感覺會更真實(shí)刨啸,這是因?yàn)槠涓先藗內(nèi)粘I畹母泄佟@缱鲆粋€(gè)球體下落的動(dòng)畫识脆,如果是勻速的下落设联,給人的感覺會不夠真實(shí),實(shí)際的生活經(jīng)驗(yàn)告訴我們灼捂,球體自由下落應(yīng)該是會有先慢后快的一個(gè)過程离例。如果讓我們自己去實(shí)現(xiàn)這么一個(gè)動(dòng)畫效果,我們會怎么去處理呢悉稠?
高中物理我們學(xué)習(xí)過自由落體相關(guān)的概念宫蛆,其中的位移計(jì)算公式:
s = 1/2 * g * t * t
從公式中我們知道,自由落體的位移跟時(shí)間不是線性關(guān)系的猛。我們可以根據(jù)這個(gè)公式耀盗,來實(shí)時(shí)的計(jì)算出位移來。
如果是摩擦阻尼或者彈簧呢卦尊,也都有相關(guān)的物理公式叛拷,我們所謂的基于物理的動(dòng)畫庫,也就是基于此類公式來實(shí)現(xiàn)的岂却,本質(zhì)上還是補(bǔ)間動(dòng)畫胡诗,只不過過程遵循物理規(guī)律比較復(fù)雜罷了。
2. Animation庫
講解這一部分淌友,也考慮過將Flutter的動(dòng)畫原理先介紹一下。想了想骇陈,前一篇文章介紹過這些普世的動(dòng)畫原理震庭,F(xiàn)lutter只不過是特定平臺的實(shí)現(xiàn),無非是實(shí)現(xiàn)手段的不同你雌,因此器联,F(xiàn)lutter動(dòng)畫原理的解析,放到本文最后的小節(jié)部分婿崭,在代碼的基礎(chǔ)上去解釋拨拓,筆者覺得更加好理解。
2.1 animation.dart
animation.dart定義了動(dòng)畫的四種狀態(tài)氓栈,以及核心的抽象類Animation渣磷。
2.1.1 動(dòng)畫的四種狀態(tài)
這個(gè)文件中定義了Animation的四種狀態(tài):
- dismissed:動(dòng)畫的初始狀態(tài)
- forward:從頭到尾播放動(dòng)畫
- reverse:從尾到頭播放動(dòng)畫
- completed:動(dòng)畫完成的狀態(tài)
2.1.2 Animation類
Animation類是Flutter動(dòng)畫中核心的抽象類,它包含動(dòng)畫的當(dāng)前值和狀態(tài)兩個(gè)屬性授瘦。定義了動(dòng)畫的一系列回調(diào),
- 動(dòng)畫過程中值變化的回調(diào):
void addListener(VoidCallback listener);
void removeListener(VoidCallback listener);
- 狀態(tài)的回調(diào)函數(shù):
void addStatusListener(AnimationStatusListener listener);
void removeStatusListener(AnimationStatusListener listener);
2.2 curve.dart
A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
看到這段英文醋界,首先會想到什么竟宋?沒錯(cuò),插值器形纺。Curve也是一個(gè)抽象類丘侠,定義了時(shí)間與數(shù)值的一個(gè)接口。
double transform(double t);
例如一個(gè)線性的插值器逐样,實(shí)現(xiàn)代碼如下蜗字。
class _Linear extends Curve {
const _Linear._();
@override
double transform(double t) => t;
}
該文件下面定義了非常多類型的插值器,具體的實(shí)現(xiàn)不一一展開了脂新。Flutter定義了一系列的插值器挪捕,封裝在Curves類中,有下面13種效果戏羽。
- linear
- decelerate
- ease
- easeIn
- easeOut
- easeInOut
- fastOutSlowIn
- bounceIn
- bounceOut
- bounceInOut
- elasticIn
- elasticOut
- elasticInOut
如果上面的13種還不滿足需求的話担神,還可以使用Cubic類來進(jìn)行自定義的構(gòu)造∈蓟ǎ可以看出這塊兒實(shí)現(xiàn)參考了web中的相關(guān)實(shí)現(xiàn)妄讯。
2.3 tween.dart
該文件定義了一系列的估值器,F(xiàn)lutter通過抽象類Animatable來實(shí)現(xiàn)估值器酷宵。關(guān)于Animatable亥贸,我們可以先看下其定義。
An object that can produce a value of type
T
given an [Animation<double>]
as input.
可以根據(jù)不同的輸入浇垦,產(chǎn)出不同的數(shù)值炕置。通過重載下面的函數(shù)來產(chǎn)生不同的估值器。
T transform(double t);
它的最主要的子類是Tween男韧,一個(gè)線性的估值器朴摊,實(shí)現(xiàn)如下,非常的簡單此虑,就是一個(gè)線性函數(shù)甚纲。
T lerp(double t) {
assert(begin != null);
assert(end != null);
return begin + (end - begin) * t;
}
@override
T transform(double t) {
if (t == 0.0)
return begin;
if (t == 1.0)
return end;
return lerp(t);
}
在Tween的基礎(chǔ)上實(shí)現(xiàn)了不同類型的估值器。
- ReverseTween
- ColorTween
- SizeTween
- RectTween
- IntTween
- StepTween
- ConstantTween
還可以通過自定義的插值器去實(shí)現(xiàn)估值器朦前,例如通過curve實(shí)現(xiàn)的估值器CurveTween介杆。
2.4 animation_controller.dart
動(dòng)畫的控制,就在這個(gè)文件下面實(shí)現(xiàn)韭寸,其中最重要的部分是AnimationController春哨,它派生自Animation類。
AnimationController的功能有如下幾種:
- 播放一個(gè)動(dòng)畫(forwaed或者reverse)恩伺,或者停止一個(gè)動(dòng)畫赴背;
- 設(shè)置動(dòng)畫的值;
- 設(shè)置動(dòng)畫的邊界值;
- 創(chuàng)建基于物理的動(dòng)畫效果癞尚。
默認(rèn)情況下耸三,AnimationController是線性的產(chǎn)生0.0到1.0之間的值,每刷新一幀就產(chǎn)出一個(gè)數(shù)值浇揩。AnimationController在不使用的時(shí)候需要dispose仪壮,否則會造成資源的泄漏。
2.4.1 TickerProvider
提到AnimationController必須要先說一下TickerProvider胳徽。
An interface implemented by classes that can vend Ticker objects.
TickerProvider定義了可以發(fā)送Ticker對象的接口积锅,
Ticker createTicker(TickerCallback onTick);
Ticker能干什么呢?
Tickers can be used by any object that wants to be notified whenever a frame triggers.
它的主要作用是獲取每一幀刷新的通知养盗,作用就顯而易見了缚陷,相當(dāng)于給動(dòng)畫添加了一個(gè)動(dòng)起來的引擎。
2.4.2 AnimationController
現(xiàn)在再次回到AnimationController往核。上面為什么要先說一下TickerProvider呢箫爷,這是因?yàn)锳nimationController的構(gòu)造函數(shù)中需要一個(gè)TickerProvider參數(shù)。
結(jié)合上面介紹的插值器聂儒、估值器以及Ticker回調(diào)虎锚,AnimationController大致的工作流程,我相信很多人都可以理出來了衩婚。
隨著時(shí)間的流逝窜护,插值器根據(jù)時(shí)間產(chǎn)生的值作為輸入,提供給估值器非春,產(chǎn)生動(dòng)畫的實(shí)際效果值柱徙,結(jié)合Ticker的回調(diào),渲染出當(dāng)前動(dòng)畫值的圖像奇昙。這也是補(bǔ)間動(dòng)畫的工作原理护侮。
AnimationController具體的源碼不做分析了,可以看到Flutter的動(dòng)畫實(shí)現(xiàn)的其實(shí)是相當(dāng)?shù)脑即⒛停珹nimationController需要一個(gè)觸發(fā)刷新的回調(diào)羊初,輸出也是值的改變,并不像成熟平臺里面的配合View去做動(dòng)畫弧岳。
3. Physics庫
Physics庫基本上就是插值器的實(shí)現(xiàn)部分,這部分比較簡單
Simulation定義了基于物理動(dòng)畫的相關(guān)接口业踏,具體有位置禽炬、速度、是否完成以及公差(Tolerance)
double x(double time);
double dx(double time);
GravitySimulation的實(shí)現(xiàn)如下勤家,其中_a加速度腹尖,_x是初始距離,_v是初始速度:
@override
double x(double time) => _x + _v * time + 0.5 * _a * time * time;
@override
double dx(double time) => _v + time * _a;
相信學(xué)過高中物理的讀者伐脖,對這公式不會陌生热幔。其他幾種具體實(shí)現(xiàn)不在此處一一展開了哈乐设。如果擴(kuò)展這個(gè)物理動(dòng)畫庫的話,也很好去擴(kuò)展绎巨,掌握一些物理公式近尚,就可以去仿照實(shí)現(xiàn)了。
4. Ticker
關(guān)于動(dòng)畫的驅(qū)動(dòng)场勤,在此簡單的說一下戈锻,Ticker是被SchedulerBinding所驅(qū)動(dòng)。SchedulerBinding則是監(jiān)聽著Window.onBeginFrame回調(diào)和媳。
Window.onBeginFrame的作用是什么呢格遭,是告訴應(yīng)用該提供一個(gè)scene了,它是被硬件的VSync信號所驅(qū)動(dòng)的留瞳。
具體可以查看sky_engine下面的window.dart的實(shí)現(xiàn)拒迅,不做展開了。
5. 小節(jié)
本篇文章簡單的從代碼的層面解析了一下Flutter的動(dòng)畫她倘,更深入的Ticker這塊兒璧微,感興趣的讀者可以自行去了解,這塊兒涉及到sky_engine下面的代碼帝牡。
本篇文章往毡,筆者依然試圖繞過代碼去講解一些普適性的東西,但是Flutter這塊兒代碼實(shí)現(xiàn)的確實(shí)挺簡單的靶溜,造成的問題是調(diào)用起來費(fèi)勁开瞭。
基于物理的動(dòng)畫,我們要知道深層次的是物理公式罩息,有這個(gè)基礎(chǔ)嗤详,我們才可以制作出符合感官的動(dòng)畫效果。其本質(zhì)也是補(bǔ)間動(dòng)畫瓷炮,過程可以被計(jì)算出來葱色。
可以說的寬泛一些,一般的動(dòng)畫娘香,大部分都是補(bǔ)間動(dòng)畫苍狰,如果我們自行去設(shè)計(jì)一套動(dòng)畫系統(tǒng),插值器烘绽、估值器淋昭、驅(qū)動(dòng)部分以及動(dòng)畫的管理部分,這四個(gè)模塊之間相互協(xié)調(diào)輸出一幀一幀的動(dòng)畫過場安接。絕大部分平臺的動(dòng)畫設(shè)計(jì)翔忽,也都逃不過這些因素,只不過實(shí)現(xiàn)的方式各不相同。
如果文中有錯(cuò)誤的地方歇式,煩請指正驶悟,筆者水平有限,再次感謝材失。
6. 后話
筆者建了一個(gè)Flutter學(xué)習(xí)相關(guān)的項(xiàng)目痕鳍,Github地址,里面包含了筆者寫的關(guān)于Flutter學(xué)習(xí)相關(guān)的一些文章豺憔,會定期更新额获,也會上傳一些學(xué)習(xí)Demo,歡迎大家關(guān)注恭应。