Flutter的動畫體系是怎么運作的,各組件之間的關(guān)聯(lián)關(guān)系及原理什么,隱式動畫、顯式動畫怎么區(qū)分,本文將會進行詳細解答。
將會按照以下順序進行介紹:
- 1.Flutter動畫基本概念
- 2.隱式動畫&顯式動畫
- 3.選擇適合自己的動畫
- 4.動畫類型
- 5.常見動畫模式
Flutter動畫基本概念
什么是動畫

講解動畫之前,需要先介紹一下幀的概念,上面我們看到的小視頻,其實是由一張張連續(xù)的靜止圖片所構(gòu)成,每一幅圖片就是一幀。

傳統(tǒng)電影每秒播放24幀,現(xiàn)在的手機每秒刷新60到120幀,我們在手機上看到的其實也是每秒刷新的圖像。

這個小視頻演示了從0變到光速的動畫,利用的也是視覺差,假如手機幀率是每秒60幀,那么你看到的動畫,其實是由渲染出來的60次圖像所構(gòu)成。那么,在Flutter系統(tǒng)中動畫是怎么形成的吶?
Ticker
首先介紹Ticker,它是Flutter中動畫運行的基礎(chǔ)。Ticker是一個每幀都會執(zhí)行某個函數(shù)的對象,借助于此,我們可以在每幀的回調(diào)中連續(xù)改變UI視圖的形態(tài),這樣視覺上看到的就是連續(xù)運行的動畫了。
// 創(chuàng)建ticker
var ticker = Ticker((elapsed) => print(‘hello'));
創(chuàng)建完之后,需要手動開啟ticker.start(),使用完之后還需要手動釋放資源ticker.dispose(),此外還有muted(bool value)、stop()等操作,管理起來比較麻煩,容易疏忽。好消息是99%d的場景你并不會直接使用Ticker,但是在動畫中Ticker又是必不可少的,為了方便使用,系統(tǒng)提供了SingleTickerProviderStateMixin來方便開發(fā)者,它繼承自TickerProvider,實現(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時vsync參數(shù)傳入this參數(shù),這是因為,我們混入了mixin之后具備了相應(yīng)的能力,這塊的詳細解釋可以看之前文章深入理解Flutter動畫,傳入this之后做了什么事情吶?

我們可以看到,this具備了createTicker能力后,通過傳入一個回調(diào)函數(shù),就可以創(chuàng)建ticker了,再看回調(diào)函數(shù)_tick做了哪些事情吶?

主要有三件事:
- 根據(jù)創(chuàng)建動畫傳入的最大最小值(默認是0到1)每幀生成改變所對應(yīng)的值。
- 通知值得監(jiān)聽者
- 改變動畫狀態(tài)
這樣創(chuàng)建好的AnimationController就具備了每幀刷新double值的能力,這里強調(diào)的是double值,你或許會好奇,我們的動畫可能會改變color、rect、position、aligment,這和double值有什么關(guān)系吶?這之間的關(guān)系稍后會詳細講述,這里先把controller講述完。
AnimationController和它的名字一致是一個控制器,主要作用是控制動畫、驅(qū)動動畫,其核心是通過控制value變量的值來驅(qū)動動畫,這點可以很容易通過其Api看出來,幾乎所有參數(shù)都是double類型。

動畫正常執(zhí)行forward,或者翻轉(zhuǎn)reverse通過_animateToInternal(upperBound)、_animateToInternal(lowerBound),注意其參數(shù),即是動畫改變范圍的最小值,最大值。其他操作repeat重復(fù)動畫則是反復(fù)的從最小值到最大值之間變換,動畫重置Reset是講value值重置為初始值。
回到之前1秒從0變光速的動畫的例子,我們就可以通過AnimationController來實現(xiàn)


關(guān)于AnimationController基本就講完了,其核心就是每幀改變一個double類型的值,還有改變動畫狀態(tài),通知值得監(jiān)聽者,后面兩個動作就涉及到了AnimationController的父類Animation。通過下圖可以看到其繼承自Animation類,Animation泛型類型傳入的也是double類型的值。

Animation
最簡單的動畫或許只需要改變值就可以改變UI效果了,但是如果你想要監(jiān)聽動畫是否運行完成,當(dāng)前運行到哪種程度了,那么只有一個值是不夠的,這也是Animation<T>設(shè)計的目的,

abstract class Animation<T> extends Listenable implements ValueListenable<T>
Animation就是結(jié)合了值、狀態(tài)、并且能夠提供回調(diào)的抽象類。因此看到需要傳Animation<T>類型的參數(shù),需要使用其子類對象作為參數(shù),它的子類常用的主要有兩個AnimationController和CurvedAnimation。


Animation抽象類中另一個比較重要的方法就是drive了,通過綁定一個具有Animatable類型的對象來進行動畫值的類型轉(zhuǎn)換,這就是前面的疑問,怎么將AnimationController中的double值轉(zhuǎn)換為想要的類型,比如color、rect、position、aligment等。
Tween(Animatable)
Animatable也是一個抽象類,大部分子類都是Tween類型的,也有一些不是比如CurveTween,涉及到Curve的類有些特殊,稍后會講到。


常見的Tween子類有ColorTween、SizeTween、RectTween、CurveTween、StepTween、IntTween等,這里以ColorTween為例講解一下轉(zhuǎn)換過程。
- animation可以用來調(diào)用drive,返回值是Animation類型的,一般是由AnimationController來調(diào)用,傳入一個Animatable類型的參數(shù)。
- 假設(shè)這個Animatable類型的參數(shù),類型是ColorTween,它復(fù)寫了父類Tween的
lerp函數(shù),當(dāng)父類lerp被調(diào)用時,它將會被調(diào)用。 - ColorTween的真正實現(xiàn),交給了具體值所在的類,這里是Color,傳入三個參數(shù),動畫初始值begin和終點值end,此外還有一個
double t, - t來自AnimationController的double值,根據(jù)轉(zhuǎn)化公式,即可將動畫控制double值轉(zhuǎn)化為目標color值。

將tween和controller關(guān)聯(lián)起來有兩種方式,一種是剛才提到的通過調(diào)用Animation的drive方法,另一種是Animatable類中的animate方法,返回值都是Animation類型的,無論哪種方式都離不開AnimationController(提供的原始double值)。如下圖
- controller.drive(tween)
- tween.anmation(controller)

多個tween還可以組合在一起,這樣可以控制的動畫屬性就更多了,也有兩種方式,通過chain或者drive組合。

Curve
除了通過tween對值類型進行改變,我們還可以通過Curve來控制動畫執(zhí)行的速度,常見的curveCurves.easeIn、Curves.easeOut等都是基于Cubic的不同參數(shù)對應(yīng)的實例。系統(tǒng)內(nèi)置了幾十種不同的Cubic實例,這也是curve最常用的控制,如果這些都不能滿足用戶,你也可以自己基于cubic創(chuàng)建。Cubic支持4個參數(shù),Cubic(a, b, c, d),這個幾個參數(shù)最終通過一系列公式,轉(zhuǎn)化controller初始double t值得改變順序,從而實現(xiàn)控制動畫執(zhí)行的速度。


Cubic的本質(zhì)其實是一個三階貝塞爾曲線
這里有一個在線調(diào)試cubic的網(wǎng)站,支持實時觀看、對比效果。


需要注意的一點是cubic計算出來的值有可能為負,小心動畫值為負導(dǎo)致約束越界。
Curve的使用方式有三種:作為屬性,通過Curvetween,通過CurveAnimation,無論哪種都離不開 Controller提供的初始值。如下圖:



基本概念小結(jié)
Ticker逐幀給我們提供了回調(diào),AnimationController給我們默認提供了從0到1的值改變,可以用來進行動畫控制,基于此初始值,我們可以使用Tween對初始值進行改變,使用Curve對動畫速度進行控制。動畫的值被包裝成Animation類型,為我們提供了值和狀態(tài)的監(jiān)聽。
隱式動畫&顯式動畫
還記得上面的從0到光速的動畫嗎?那個只是簡單的示范,其實有更簡單的寫法,甚至都不用自己創(chuàng)建AnimationController,講完這一節(jié)關(guān)于隱式、顯式動畫你就明白啦。隱式動畫和顯式動畫的區(qū)分是:是否需要自己創(chuàng)建管理AnimationController,還可以進一步細分為內(nèi)置隱式動畫,自定義隱式動畫,內(nèi)置顯式動畫,自定義顯式動畫。隱式動畫可以做的,顯式動畫都可以實現(xiàn),隱式動畫只能控制duration和curve不需要創(chuàng)建controller,簡單易用,顯式動畫則有更多的控制權(quán),在下面分別介紹后,會詳細列出它們之間的對比。
隱式動畫
常見的隱式動畫都是以AnimatedFoo命名的,F(xiàn)oo是沒有動畫時Widget的名字,系統(tǒng)提供了很多隱式動畫,如下圖。當(dāng)下面的組件不滿足時,也可以使用TweenAnimationBuilder進行自定義隱式動畫,相應(yīng)的系統(tǒng)提供的隱式動畫被稱為內(nèi)置隱式動畫。

AnimatedFoo第一次加載到Widget樹中是沒有動畫的,思考下為什么?因為隱式動畫是一次性的,只有每次當(dāng)動畫值改變時才有動畫。

以AnimatedAlign為例,其余隱式動畫也一樣,我們只需要關(guān)注屬性的值變化、curve、duration。
隱式動畫大部分集成自ImplicitlyAnimatedWidget類,但是也有一些特殊自接繼承自SingleChildRenderObjectWidget,這里也是個設(shè)計相關(guān)的問題,沒有官方答案,自行研究完源碼后歡迎一起探討。隱式動畫并不是不需要創(chuàng)建AnimationController,之所以被稱之為隱式動畫,是因為,創(chuàng)建controller這一步,隱式動畫在內(nèi)部幫助我們創(chuàng)建了,如下圖,因此此類動畫使用起來更加簡便。

如果系統(tǒng)提供的內(nèi)置隱式動畫不滿足需求,可以基于TweenAnimationBuilder進行自定義,看下面示例

此示例涉及3個知識點
- Tween的值可以動態(tài)改變,謹記
TweenAnimationBuilder永遠是從當(dāng)前值運動到新的終值 - builder即是用戶自定義想要實現(xiàn)的內(nèi)容
- child參數(shù)放置不需要變的元素,比如自定義Widget中的icon,它會在builder構(gòu)造中被當(dāng)成參數(shù)傳遞進去,這塊是系統(tǒng)為了
優(yōu)化性能所做的設(shè)計。
顯式動畫
顯示動畫需要自己創(chuàng)建并管理,系統(tǒng)也提供了一些實現(xiàn)好的顯示動畫,以FooTransitionFoo是沒有動畫時Widget的名字

大部分顯示動畫繼承自AnimatedWidget,和隱式動畫類似,也有一部分直接繼承自SingleChildRenderObjectWidget,比如FadeTransition,我的理解是此類Widget動畫的改變,只需要直接改變渲染層即可,不涉及到Widget樹的改變,你的理解是什么吶?
以AnimatedAlign為例,可以和上面介紹的AnimatedAlign對比起來觀看,如下圖,可以發(fā)現(xiàn),隱式動畫需要的參數(shù)是真正的值AlignmentGeometry alignment,顯示動畫的參數(shù)變成了Animation<T>類型Animation<AlignmentGeometry> alignment,結(jié)合前面介紹的基本概念,這里可以將我們自定義的AnimationController當(dāng)做參數(shù)傳進去,因為它也繼承自Animation。


如果系統(tǒng)內(nèi)置的顯式動畫不滿足需求,我們可以使用AnimationBuilder自定義顯式動畫,AnimationBuilder繼承自AnimatedWidget,因此我們也可以直接繼承自AnimatedWidget,自定義顯示動畫關(guān)注點和自定義隱式動畫類似,同樣只需要關(guān)注animation、builder、child,其中animation即為自己創(chuàng)建的controller,builder為自定義Widget,child作用和上面一樣用來優(yōu)化性能。

自定義顯式動畫兩種方式效果一樣,建議是直接繼承自AnimatedWidget定義單獨的Widget,這樣更獨立,也方便以后復(fù)用。當(dāng)然如果父節(jié)點比較簡單時,首選AnimatedBuilder。下面有兩個小示例


隱式動畫&顯式動畫小結(jié)
| 對比 | 命名 | 控制器 | 值類型 | 父類 | 自定義 | 難易程度 |
|---|---|---|---|---|---|---|
| 顯式動畫 | FooTransition | 顯式創(chuàng)建AnimationController,完整的控制權(quán) | Animation<T> value | 大多數(shù)繼承自AnimatedWidget | 使用AnimatedBuilder或繼承自AnimatedWidget | 中等 |
| 隱式動畫 | AnimatedFoo | 隱式創(chuàng)建Controller,只能控制duration、curve | T value | 大多數(shù)繼承自ImplicitlyAnimatedWidget | 使用TweenAnimationBuilder | 簡單 |
選擇適合自己的動畫
上面介紹了內(nèi)置隱式動畫、自定義隱式動畫,內(nèi)置顯式動畫、自定義顯式動畫,在Flutter動畫體系中還有其他類型,那么我們該如何選擇使用哪種吶?
- 隱式動畫可以做的,顯式動畫都可以實現(xiàn),只是實現(xiàn)難易程度不一樣
- 隱式動畫只能控制duration和curve,不需要創(chuàng)建controller,簡單易用,顯式動畫有更多的控制權(quán)

此外這里有一張翻譯的圖供你參考,更詳細的介紹之前有翻譯過一篇文章如何選擇適合您的的Flutter Animation Widget,這里介紹的更詳細。

動畫類型
動畫類型可以分為兩大類
- 補間動畫(Tween animation) 以上介紹的都可以算是補間動畫
- 基于物理動畫(Physics-based animation animation)
基于物理的動畫可以參考系統(tǒng)Api AnimationController.animateWith和SpringSimulation,這里還有一個官方示例Widget 的物理模擬動畫效果
這塊實際項目中使用不多,這里不再詳細介紹,如有特殊要求歡迎一起交流。
常見動畫模式
- 列表和網(wǎng)格動畫,常見的ListView,GridView展示,加載、刪除的動畫
- 共享元素動畫(Hero),標準Hero動畫和徑向Hero動畫
- 交織動畫
標準Hero動畫使用起來比較簡單,系統(tǒng)提供有heroWidget,只需要在轉(zhuǎn)場前后頁面保持同樣的tag即可。
其原理是,系統(tǒng)在動畫運行的時候,在原視圖的基礎(chǔ)上覆蓋一層overlay,當(dāng)然還有其中過渡動畫。

徑向Hero動畫稍微復(fù)雜一些,先看下效果展示,共享元素代碼示例


這部分的詳細介紹看這里Hero動畫,文章已經(jīng)太長了,這里就不展開講了。
交織動畫主要考慮的是:
- 一個交織動畫由一組序列動畫或重疊動畫所組成。
- 創(chuàng)建一個交織動畫,要用到多個動畫對象
- 一個 AnimationController 控制所有動畫。無論動畫在真實時間中播放多長時間,控制器的值必須在 0.0 和 1.0 之間,包括 0.0 和 1.0。
- 每個動畫對象在一個間隔時間內(nèi)指定一個動畫。
- 為每一個要執(zhí)行動畫的屬性創(chuàng)建一個 Tween
這里也不展開細講,詳細介紹可以看這里交織動畫
下面是運行效果及設(shè)計圖,下面動畫源碼交織動畫示例

全文小結(jié)
文章第一部分先介紹了一些基本概念Ticker、AnimationController、Animation、Tween、Curve這些是Flutter動畫的核心,通過對其源碼分析,了解到彼此間的關(guān)聯(lián)關(guān)系。然后介紹了隱式動畫和顯示動畫,以及如何進行自定義,接著又介紹了如何選用適合自己的動畫,這部分之前文章有介紹,這里一筆帶過了,建議詳細閱讀下之前的文章,最后介紹了Hero動畫和交織動畫。
看到這里,想必你對Flutter動畫體系有了一定了解,文章中鏈接的文章,之前已經(jīng)單開文章介紹過也推薦看一看。當(dāng)然了解了之后還需要寫代碼練習(xí),相信再看到Flutter動畫代碼的時候,就不會那么陌生了。