Flutter的動(dòng)畫(huà)體系是怎么運(yùn)作的崔涂,各組件之間的關(guān)聯(lián)關(guān)系及原理什么,隱式動(dòng)畫(huà)始衅、顯式動(dòng)畫(huà)怎么區(qū)分冷蚂,本文將會(huì)進(jìn)行詳細(xì)解答。
將會(huì)按照以下順序進(jìn)行介紹:
- 1.Flutter動(dòng)畫(huà)基本概念
- 2.隱式動(dòng)畫(huà)&顯式動(dòng)畫(huà)
- 3.選擇適合自己的動(dòng)畫(huà)
- 4.動(dòng)畫(huà)類型
- 5.常見(jiàn)動(dòng)畫(huà)模式
Flutter動(dòng)畫(huà)基本概念
什么是動(dòng)畫(huà)
講解動(dòng)畫(huà)之前,需要先介紹一下幀的概念,上面我們看到的小視頻别伏,其實(shí)是由一張張連續(xù)的靜止圖片所構(gòu)成,每一幅圖片就是一幀耍属。
傳統(tǒng)電影每秒播放24幀,現(xiàn)在的手機(jī)每秒刷新60到120幀提揍,我們?cè)谑謾C(jī)上看到的其實(shí)也是每秒刷新的圖像浙垫。
這個(gè)小視頻演示了從0變到光速的動(dòng)畫(huà)辙售,利用的也是視覺(jué)差,假如手機(jī)幀率是每秒60幀,那么你看到的動(dòng)畫(huà),其實(shí)是由渲染出來(lái)的60次圖像所構(gòu)成。那么,在Flutter系統(tǒng)中動(dòng)畫(huà)是怎么形成的吶佩研?
Ticker
首先介紹Ticker,它是Flutter中動(dòng)畫(huà)運(yùn)行的基礎(chǔ)。Ticker是一個(gè)每幀都會(huì)執(zhí)行某個(gè)函數(shù)
的對(duì)象,借助于此,我們可以在每幀的回調(diào)中連續(xù)改變UI視圖的形態(tài)娱挨,這樣視覺(jué)上看到的就是連續(xù)運(yùn)行的動(dòng)畫(huà)了淮韭。
// 創(chuàng)建ticker
var ticker = Ticker((elapsed) => print(‘hello'));
創(chuàng)建完之后占键,需要手動(dòng)開(kāi)啟ticker.start()
元潘,使用完之后還需要手動(dòng)釋放資源ticker.dispose()
翩概,此外還有muted(bool value)评姨、stop()
等操作敲才,管理起來(lái)比較麻煩择葡,容易疏忽阻星。好消息是99%d的場(chǎng)景你并不會(huì)直接使用Ticker,但是在動(dòng)畫(huà)中Ticker又是必不可少的已添,為了方便使用妥箕,系統(tǒng)提供了SingleTickerProviderStateMixin
來(lái)方便開(kāi)發(fā)者,它繼承自TickerProvider
更舞,實(shí)現(xiàn)了createTicker
方法畦幢。
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
Ticker _ticker;
@override
Ticker createTicker(TickerCallback onTick) {/*...*/}
/*...*/
}
這樣當(dāng)我們混入SingleTickerProviderStateMixin之后,自身的類就具備了創(chuàng)建createTicker的能力缆蝉。
AnimationController
接著上圖講解宇葱,我們看到創(chuàng)建AnimationController
時(shí)vsync
參數(shù)傳入this
參數(shù),這是因?yàn)榭罚覀兓烊肓薽ixin之后具備了相應(yīng)的能力黍瞧,這塊的詳細(xì)解釋可以看之前文章深入理解Flutter動(dòng)畫(huà),傳入this之后做了什么事情吶原杂?
我們可以看到印颤,this具備了createTicker
能力后,通過(guò)傳入一個(gè)回調(diào)函數(shù)污尉,就可以創(chuàng)建ticker
了膀哲,再看回調(diào)函數(shù)_tick
做了哪些事情吶?
主要有三件事:
- 根據(jù)創(chuàng)建動(dòng)畫(huà)傳入的最大最小值(默認(rèn)是0到1)每幀生成改變所對(duì)應(yīng)的值被碗。
- 通知值得監(jiān)聽(tīng)者
- 改變動(dòng)畫(huà)狀態(tài)
這樣創(chuàng)建好的AnimationController就具備了每幀刷新double值
的能力某宪,這里強(qiáng)調(diào)的是double值,你或許會(huì)好奇锐朴,我們的動(dòng)畫(huà)可能會(huì)改變color兴喂、rect、position、aligment
衣迷,這和double值有什么關(guān)系吶畏鼓?這之間的關(guān)系稍后會(huì)詳細(xì)講述,這里先把controller講述完壶谒。
AnimationController和它的名字一致是一個(gè)控制器云矫,主要作用是控制動(dòng)畫(huà)、驅(qū)動(dòng)動(dòng)畫(huà)
汗菜,其核心是通過(guò)控制value變量的值來(lái)驅(qū)動(dòng)動(dòng)畫(huà)让禀,這點(diǎn)可以很容易通過(guò)其Api看出來(lái),幾乎所有參數(shù)都是double類型陨界。
動(dòng)畫(huà)正常執(zhí)行forward巡揍,或者翻轉(zhuǎn)reverse通過(guò)_animateToInternal(upperBound)
、_animateToInternal(lowerBound)
菌瘪,注意其參數(shù)腮敌,即是動(dòng)畫(huà)改變范圍的最小值,最大值俏扩。其他操作repeat
重復(fù)動(dòng)畫(huà)則是反復(fù)的從最小值到最大值之間變換糜工,動(dòng)畫(huà)重置Reset
是講value值重置為初始值。
回到之前1秒從0變光速的動(dòng)畫(huà)的例子动猬,我們就可以通過(guò)AnimationController來(lái)實(shí)現(xiàn)
關(guān)于AnimationController基本就講完了啤斗,其核心就是每幀改變一個(gè)double類型的值,還有改變動(dòng)畫(huà)狀態(tài)赁咙,通知值得監(jiān)聽(tīng)者钮莲,后面兩個(gè)動(dòng)作就涉及到了AnimationController的父類Animation
。通過(guò)下圖可以看到其繼承自Animation
類彼水,Animation
泛型類型傳入的也是double類型的值崔拥。
Animation
最簡(jiǎn)單的動(dòng)畫(huà)或許只需要改變值就可以改變UI效果了,但是如果你想要監(jiān)聽(tīng)動(dòng)畫(huà)是否運(yùn)行完成凤覆,當(dāng)前運(yùn)行到哪種程度了链瓦,那么只有一個(gè)值是不夠的,這也是Animation<T>
設(shè)計(jì)的目的盯桦,
abstract class Animation<T> extends Listenable implements ValueListenable<T>
Animation就是結(jié)合了值慈俯、狀態(tài)、并且能夠提供回調(diào)的抽象類拥峦。因此看到需要傳Animation<T>
類型的參數(shù)贴膘,需要使用其子類對(duì)象作為參數(shù),它的子類常用的主要有兩個(gè)AnimationController
和CurvedAnimation
略号。
Animation抽象類中另一個(gè)比較重要的方法就是drive
了刑峡,通過(guò)綁定一個(gè)具有Animatable
類型的對(duì)象來(lái)進(jìn)行動(dòng)畫(huà)值的類型轉(zhuǎn)換洋闽,這就是前面的疑問(wèn),怎么將AnimationController中的double值轉(zhuǎn)換為想要的類型突梦,比如color诫舅、rect、position宫患、aligment
等刊懈。
Tween(Animatable)
Animatable也是一個(gè)抽象類,大部分子類都是Tween
類型的撮奏,也有一些不是比如CurveTween
俏讹,涉及到Curve的類有些特殊,稍后會(huì)講到畜吊。
常見(jiàn)的Tween子類有ColorTween、SizeTween户矢、RectTween玲献、CurveTween、StepTween梯浪、IntTween
等捌年,這里以ColorTween為例講解一下轉(zhuǎn)換過(guò)程。
- animation可以用來(lái)調(diào)用drive挂洛,返回值是Animation類型的礼预,一般是由AnimationController來(lái)調(diào)用,傳入一個(gè)Animatable類型的參數(shù)虏劲。
- 假設(shè)這個(gè)Animatable類型的參數(shù)托酸,類型是ColorTween,它復(fù)寫(xiě)了父類Tween的
lerp
函數(shù)柒巫,當(dāng)父類lerp被調(diào)用時(shí)励堡,它將會(huì)被調(diào)用。 - ColorTween的真正實(shí)現(xiàn)堡掏,交給了具體值所在的類应结,這里是Color,傳入三個(gè)參數(shù)泉唁,動(dòng)畫(huà)初始值begin和終點(diǎn)值end鹅龄,此外還有一個(gè)
double t
, - t來(lái)自AnimationController的double值亭畜,根據(jù)轉(zhuǎn)化公式扮休,即可將動(dòng)畫(huà)控制double值轉(zhuǎn)化為目標(biāo)color值。
將tween和controller關(guān)聯(lián)起來(lái)有兩種方式贱案,一種是剛才提到的通過(guò)調(diào)用Animation的drive
方法肛炮,另一種是Animatable類中的animate
方法止吐,返回值都是Animation類型的,無(wú)論哪種方式都離不開(kāi)AnimationController(提供的原始double值)侨糟。如下圖
- controller.drive(tween)
- tween.anmation(controller)
多個(gè)tween還可以組合在一起碍扔,這樣可以控制的動(dòng)畫(huà)屬性就更多了,也有兩種方式秕重,通過(guò)chain
或者drive
組合不同。
Curve
除了通過(guò)tween對(duì)值類型進(jìn)行改變,我們還可以通過(guò)Curve來(lái)控制動(dòng)畫(huà)執(zhí)行的速度溶耘,常見(jiàn)的curveCurves.easeIn二拐、Curves.easeOut
等都是基于Cubic
的不同參數(shù)對(duì)應(yīng)的實(shí)例。系統(tǒng)內(nèi)置了幾十種不同的Cubic
實(shí)例凳兵,這也是curve最常用的控制百新,如果這些都不能滿足用戶,你也可以自己基于cubic創(chuàng)建庐扫。Cubic支持4個(gè)參數(shù)饭望,Cubic(a, b, c, d)
,這個(gè)幾個(gè)參數(shù)最終通過(guò)一系列公式形庭,轉(zhuǎn)化controller初始double t
值得改變順序铅辞,從而實(shí)現(xiàn)控制動(dòng)畫(huà)執(zhí)行的速度。
Cubic的本質(zhì)其實(shí)是一個(gè)三階貝塞爾曲線
這里有一個(gè)在線調(diào)試cubic的網(wǎng)站萨醒,支持實(shí)時(shí)觀看斟珊、對(duì)比效果。
需要注意的一點(diǎn)是cubic計(jì)算出來(lái)的值有可能為負(fù)富纸,小心動(dòng)畫(huà)值為負(fù)導(dǎo)致約束越界囤踩。
Curve的使用方式有三種:作為屬性
,通過(guò)Curvetween
胜嗓,通過(guò)CurveAnimation
高职,無(wú)論哪種都離不開(kāi) Controller提供的初始值。如下圖:
基本概念小結(jié)
Ticker逐幀給我們提供了回調(diào)辞州,AnimationController給我們默認(rèn)提供了從0到1的值改變怔锌,可以用來(lái)進(jìn)行動(dòng)畫(huà)控制,基于此初始值变过,我們可以使用Tween對(duì)初始值進(jìn)行改變埃元,使用Curve對(duì)動(dòng)畫(huà)速度進(jìn)行控制。動(dòng)畫(huà)的值被包裝成Animation類型媚狰,為我們提供了值和狀態(tài)的監(jiān)聽(tīng)岛杀。
隱式動(dòng)畫(huà)&顯式動(dòng)畫(huà)
還記得上面的從0到光速的動(dòng)畫(huà)嗎?那個(gè)只是簡(jiǎn)單的示范崭孤,其實(shí)有更簡(jiǎn)單的寫(xiě)法类嗤,甚至都不用自己創(chuàng)建AnimationController糊肠,講完這一節(jié)關(guān)于隱式、顯式動(dòng)畫(huà)你就明白啦遗锣。隱式動(dòng)畫(huà)和顯式動(dòng)畫(huà)的區(qū)分是:是否需要自己創(chuàng)建管理AnimationController货裹,還可以進(jìn)一步細(xì)分為內(nèi)置隱式動(dòng)畫(huà),自定義隱式動(dòng)畫(huà)精偿,內(nèi)置顯式動(dòng)畫(huà)弧圆,自定義顯式動(dòng)畫(huà)笔咽。隱式動(dòng)畫(huà)可以做的搔预,顯式動(dòng)畫(huà)都可以實(shí)現(xiàn),隱式動(dòng)畫(huà)只能控制duration
和curve
不需要?jiǎng)?chuàng)建controller
叶组,簡(jiǎn)單易用拯田,顯式動(dòng)畫(huà)則有更多的控制權(quán),在下面分別介紹后甩十,會(huì)詳細(xì)列出它們之間的對(duì)比勿锅。
隱式動(dòng)畫(huà)
常見(jiàn)的隱式動(dòng)畫(huà)都是以AnimatedFoo
命名的,F(xiàn)oo是沒(méi)有動(dòng)畫(huà)時(shí)Widget的名字枣氧,系統(tǒng)提供了很多隱式動(dòng)畫(huà),如下圖垮刹。當(dāng)下面的組件不滿足時(shí)达吞,也可以使用TweenAnimationBuilder
進(jìn)行自定義隱式動(dòng)畫(huà)
,相應(yīng)的系統(tǒng)提供的隱式動(dòng)畫(huà)被稱為內(nèi)置隱式動(dòng)畫(huà)
荒典。
AnimatedFoo
第一次加載到Widget樹(shù)中是沒(méi)有動(dòng)畫(huà)的酪劫,思考下為什么?因?yàn)殡[式動(dòng)畫(huà)是一次性的寺董,只有每次當(dāng)動(dòng)畫(huà)值改變時(shí)才有動(dòng)畫(huà)
覆糟。
以AnimatedAlign為例,其余隱式動(dòng)畫(huà)也一樣遮咖,我們只需要關(guān)注屬性的值變化滩字、curve、duration
御吞。
隱式動(dòng)畫(huà)大部分集成自ImplicitlyAnimatedWidget
類麦箍,但是也有一些特殊自接繼承自SingleChildRenderObjectWidget
,這里也是個(gè)設(shè)計(jì)相關(guān)的問(wèn)題陶珠,沒(méi)有官方答案挟裂,自行研究完源碼后歡迎一起探討。隱式動(dòng)畫(huà)并不是不需要?jiǎng)?chuàng)建AnimationController揍诽,之所以被稱之為隱式動(dòng)畫(huà)诀蓉,是因?yàn)槔跏瑒?chuàng)建controller這一步,隱式動(dòng)畫(huà)在內(nèi)部幫助我們創(chuàng)建了渠啤,如下圖狐肢,因此此類動(dòng)畫(huà)使用起來(lái)更加簡(jiǎn)便。
如果系統(tǒng)提供的內(nèi)置隱式動(dòng)畫(huà)不滿足需求埃篓,可以基于TweenAnimationBuilder
進(jìn)行自定義处坪,看下面示例
此示例涉及3個(gè)知識(shí)點(diǎn)
- Tween的值可以動(dòng)態(tài)改變,謹(jǐn)記
TweenAnimationBuilder永遠(yuǎn)是從當(dāng)前值運(yùn)動(dòng)到新的終值
- builder即是用戶自定義想要實(shí)現(xiàn)的內(nèi)容
- child參數(shù)放置不需要變的元素架专,比如自定義Widget中的icon同窘,它會(huì)在builder構(gòu)造中被當(dāng)成參數(shù)傳遞進(jìn)去,這塊是系統(tǒng)為了
優(yōu)化性能
所做的設(shè)計(jì)部脚。
顯式動(dòng)畫(huà)
顯示動(dòng)畫(huà)需要自己創(chuàng)建并管理想邦,系統(tǒng)也提供了一些實(shí)現(xiàn)好的顯示動(dòng)畫(huà),以FooTransition
Foo是沒(méi)有動(dòng)畫(huà)時(shí)Widget的名字
大部分顯示動(dòng)畫(huà)繼承自AnimatedWidget
委刘,和隱式動(dòng)畫(huà)類似丧没,也有一部分直接繼承自SingleChildRenderObjectWidget
,比如FadeTransition
锡移,我的理解是此類Widget動(dòng)畫(huà)的改變呕童,只需要直接改變渲染層即可,不涉及到Widget樹(shù)的改變淆珊,你的理解是什么吶夺饲?
以AnimatedAlign
為例,可以和上面介紹的AnimatedAlign
對(duì)比起來(lái)觀看施符,如下圖往声,可以發(fā)現(xiàn),隱式動(dòng)畫(huà)需要的參數(shù)是真正的值AlignmentGeometry alignment
戳吝,顯示動(dòng)畫(huà)的參數(shù)變成了Animation<T>
類型Animation<AlignmentGeometry> alignment
浩销,結(jié)合前面介紹的基本概念,這里可以將我們自定義的AnimationController
當(dāng)做參數(shù)傳進(jìn)去听哭,因?yàn)樗怖^承自Animation
的榛。
如果系統(tǒng)內(nèi)置的顯式動(dòng)畫(huà)不滿足需求咳秉,我們可以使用AnimationBuilder自定義顯式動(dòng)畫(huà)
,AnimationBuilder繼承自AnimatedWidget,因此我們也可以直接繼承自AnimatedWidget
句旱,自定義顯示動(dòng)畫(huà)關(guān)注點(diǎn)和自定義隱式動(dòng)畫(huà)類似妈踊,同樣只需要關(guān)注animation矾削、builder碗殷、child
,其中animation即為自己創(chuàng)建的controller祟霍,builder為自定義Widget杏头,child作用和上面一樣用來(lái)優(yōu)化性能盈包。
自定義顯式動(dòng)畫(huà)兩種方式效果一樣,建議是直接繼承自AnimatedWidget定義單獨(dú)的Widget醇王,這樣更獨(dú)立呢燥,也方便以后復(fù)用。當(dāng)然如果父節(jié)點(diǎn)比較簡(jiǎn)單時(shí)寓娩,首選AnimatedBuilder叛氨。下面有兩個(gè)小示例
隱式動(dòng)畫(huà)&顯式動(dòng)畫(huà)小結(jié)
對(duì)比 | 命名 | 控制器 | 值類型 | 父類 | 自定義 | 難易程度 |
---|---|---|---|---|---|---|
顯式動(dòng)畫(huà) | FooTransition | 顯式創(chuàng)建AnimationController,完整的控制權(quán) | Animation<T> value | 大多數(shù)繼承自AnimatedWidget | 使用AnimatedBuilder或繼承自AnimatedWidget | 中等 |
隱式動(dòng)畫(huà) | AnimatedFoo | 隱式創(chuàng)建Controller棘伴,只能控制duration寞埠、curve | T value | 大多數(shù)繼承自ImplicitlyAnimatedWidget | 使用TweenAnimationBuilder | 簡(jiǎn)單 |
選擇適合自己的動(dòng)畫(huà)
上面介紹了內(nèi)置隱式動(dòng)畫(huà)、自定義隱式動(dòng)畫(huà)焊夸,內(nèi)置顯式動(dòng)畫(huà)仁连、自定義顯式動(dòng)畫(huà),在Flutter動(dòng)畫(huà)體系中還有其他類型阱穗,那么我們?cè)撊绾芜x擇使用哪種吶饭冬?
- 隱式動(dòng)畫(huà)可以做的,顯式動(dòng)畫(huà)都可以實(shí)現(xiàn)揪阶,只是實(shí)現(xiàn)難易程度不一樣
- 隱式動(dòng)畫(huà)只能控制duration和curve昌抠,不需要?jiǎng)?chuàng)建controller,簡(jiǎn)單易用鲁僚,顯式動(dòng)畫(huà)有更多的控制權(quán)
此外這里有一張翻譯的圖供你參考扰魂,更詳細(xì)的介紹之前有翻譯過(guò)一篇文章如何選擇適合您的的Flutter Animation Widget,這里介紹的更詳細(xì)蕴茴。
動(dòng)畫(huà)類型
動(dòng)畫(huà)類型可以分為兩大類
- 補(bǔ)間動(dòng)畫(huà)(Tween animation) 以上介紹的都可以算是補(bǔ)間動(dòng)畫(huà)
- 基于物理動(dòng)畫(huà)(Physics-based animation animation)
基于物理的動(dòng)畫(huà)可以參考系統(tǒng)Api AnimationController.animateWith和SpringSimulation,這里還有一個(gè)官方示例Widget 的物理模擬動(dòng)畫(huà)效果
這塊實(shí)際項(xiàng)目中使用不多姐直,這里不再詳細(xì)介紹倦淀,如有特殊要求歡迎一起交流。
常見(jiàn)動(dòng)畫(huà)模式
- 列表和網(wǎng)格動(dòng)畫(huà)声畏,常見(jiàn)的ListView撞叽,GridView展示,加載插龄、刪除的動(dòng)畫(huà)
- 共享元素動(dòng)畫(huà)(Hero)愿棋,標(biāo)準(zhǔn)Hero動(dòng)畫(huà)和徑向Hero動(dòng)畫(huà)
- 交織動(dòng)畫(huà)
標(biāo)準(zhǔn)Hero動(dòng)畫(huà)使用起來(lái)比較簡(jiǎn)單,系統(tǒng)提供有hero
Widget均牢,只需要在轉(zhuǎn)場(chǎng)前后頁(yè)面保持同樣的tag
即可糠雨。
其原理是,系統(tǒng)在動(dòng)畫(huà)運(yùn)行的時(shí)候徘跪,在原視圖的基礎(chǔ)上覆蓋一層overlay
甘邀,當(dāng)然還有其中過(guò)渡動(dòng)畫(huà)琅攘。
徑向Hero動(dòng)畫(huà)稍微復(fù)雜一些,先看下效果展示松邪,共享元素代碼示例
這部分的詳細(xì)介紹看這里Hero動(dòng)畫(huà)坞琴,文章已經(jīng)太長(zhǎng)了,這里就不展開(kāi)講了逗抑。
交織動(dòng)畫(huà)主要考慮的是:
- 一個(gè)交織動(dòng)畫(huà)由一組序列動(dòng)畫(huà)或重疊動(dòng)畫(huà)所組成剧辐。
- 創(chuàng)建一個(gè)交織動(dòng)畫(huà),要用到多個(gè)動(dòng)畫(huà)對(duì)象
- 一個(gè) AnimationController 控制所有動(dòng)畫(huà)邮府。無(wú)論動(dòng)畫(huà)在真實(shí)時(shí)間中播放多長(zhǎng)時(shí)間荧关,控制器的值必須在 0.0 和 1.0 之間,包括 0.0 和 1.0挟纱。
- 每個(gè)動(dòng)畫(huà)對(duì)象在一個(gè)間隔時(shí)間內(nèi)指定一個(gè)動(dòng)畫(huà)羞酗。
- 為每一個(gè)要執(zhí)行動(dòng)畫(huà)的屬性創(chuàng)建一個(gè) Tween
這里也不展開(kāi)細(xì)講,詳細(xì)介紹可以看這里交織動(dòng)畫(huà)
下面是運(yùn)行效果及設(shè)計(jì)圖紊服,下面動(dòng)畫(huà)源碼交織動(dòng)畫(huà)示例
全文小結(jié)
文章第一部分先介紹了一些基本概念Ticker
檀轨、AnimationController
、Animation
欺嗤、Tween
参萄、Curve
這些是Flutter動(dòng)畫(huà)的核心,通過(guò)對(duì)其源碼分析煎饼,了解到彼此間的關(guān)聯(lián)關(guān)系讹挎。然后介紹了隱式動(dòng)畫(huà)和顯示動(dòng)畫(huà),以及如何進(jìn)行自定義吆玖,接著又介紹了如何選用適合自己的動(dòng)畫(huà)筒溃,這部分之前文章有介紹,這里一筆帶過(guò)了沾乘,建議詳細(xì)閱讀下之前的文章怜奖,最后介紹了Hero動(dòng)畫(huà)和交織動(dòng)畫(huà)。
看到這里翅阵,想必你對(duì)Flutter動(dòng)畫(huà)體系有了一定了解歪玲,文章中鏈接的文章,之前已經(jīng)單開(kāi)文章介紹過(guò)也推薦看一看掷匠。當(dāng)然了解了之后還需要寫(xiě)代碼練習(xí)滥崩,相信再看到Flutter動(dòng)畫(huà)代碼的時(shí)候,就不會(huì)那么陌生了讹语。