深入理解Flutter動畫體系

Flutter的動畫體系是怎么運作的,各組件之間的關(guān)聯(lián)關(guān)系及原理什么,隱式動畫、顯式動畫怎么區(qū)分,本文將會進行詳細解答。
將會按照以下順序進行介紹:

  • 1.Flutter動畫基本概念
  • 2.隱式動畫&顯式動畫
  • 3.選擇適合自己的動畫
  • 4.動畫類型
  • 5.常見動畫模式

Flutter動畫基本概念

什么是動畫

1motion.gif

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

2motion.png

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

3光速.gif

這個小視頻演示了從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的能力。

5this.png

AnimationController

接著上圖講解,我們看到創(chuàng)建AnimationControllervsync參數(shù)傳入this參數(shù),這是因為,我們混入了mixin之后具備了相應(yīng)的能力,這塊的詳細解釋可以看之前文章深入理解Flutter動畫,傳入this之后做了什么事情吶?

7animationController.png

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

8_tick.png

主要有三件事:

  • 根據(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類型。

10AnimationController能力.png

動畫正常執(zhí)行forward,或者翻轉(zhuǎn)reverse通過_animateToInternal(upperBound)、_animateToInternal(lowerBound),注意其參數(shù),即是動畫改變范圍的最小值,最大值。其他操作repeat重復(fù)動畫則是反復(fù)的從最小值到最大值之間變換,動畫重置Reset是講value值重置為初始值。

回到之前1秒從0變光速的動畫的例子,我們就可以通過AnimationController來實現(xiàn)

10AnimationController例子.png
3光速.gif

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

9doubleanimation.png

Animation

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

11animation.png
abstract class Animation<T> extends Listenable implements ValueListenable<T>

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

12RotationTransition.png
13animation子類.png

Animation抽象類中另一個比較重要的方法就是drive了,通過綁定一個具有Animatable類型的對象來進行動畫值的類型轉(zhuǎn)換,這就是前面的疑問,怎么將AnimationController中的double值轉(zhuǎn)換為想要的類型,比如color、rect、position、aligment等。

Tween(Animatable)

Animatable也是一個抽象類,大部分子類都是Tween類型的,也有一些不是比如CurveTween,涉及到Curve的類有些特殊,稍后會講到。

15animatable.png
16tween.png

常見的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值。
17轉(zhuǎn)換值.png

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

  • controller.drive(tween)
  • tween.anmation(controller)
18tween關(guān)聯(lián)animation.png

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

19多個tween.png

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í)行的速度。

20curve.png
20三階貝塞爾曲線.png

Cubic的本質(zhì)其實是一個三階貝塞爾曲線

這里有一個在線調(diào)試cubic的網(wǎng)站,支持實時觀看、對比效果。

24cubic-bezier.png
Curves.png

需要注意的一點是cubic計算出來的值有可能為負,小心動畫值為負導(dǎo)致約束越界。

Curve的使用方式有三種:作為屬性,通過Curvetween,通過CurveAnimation,無論哪種都離不開 Controller提供的初始值。如下圖:

21curve屬性.png
19多個tween.png
22curveAnimation.png

基本概念小結(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),隱式動畫只能控制durationcurve不需要創(chuàng)建controller,簡單易用,顯式動畫則有更多的控制權(quán),在下面分別介紹后,會詳細列出它們之間的對比。

隱式動畫

常見的隱式動畫都是以AnimatedFoo命名的,F(xiàn)oo是沒有動畫時Widget的名字,系統(tǒng)提供了很多隱式動畫,如下圖。當(dāng)下面的組件不滿足時,也可以使用TweenAnimationBuilder進行自定義隱式動畫,相應(yīng)的系統(tǒng)提供的隱式動畫被稱為內(nèi)置隱式動畫。

27隱式動畫.png

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

28AnimatedAlign.png

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

29ImplicitlyAnimatedWidget.png

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

31自定義隱式動畫示例.png

此示例涉及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的名字

32顯式動畫.png

大部分顯示動畫繼承自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

33AnimatedAlign.png
34AlignTransition.png

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

34AnimatedBuilder.png

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

35builder自定義顯示W(wǎng)idget.png
34AnimatedBuilder.png

隱式動畫&顯式動畫小結(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)
25easy2hard.png

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

26whichanimation.png

動畫類型

動畫類型可以分為兩大類

  • 補間動畫(Tween animation) 以上介紹的都可以算是補間動畫
  • 基于物理動畫(Physics-based animation animation)
    基于物理的動畫可以參考系統(tǒng)Api AnimationController.animateWithSpringSimulation,這里還有一個官方示例Widget 的物理模擬動畫效果
    這塊實際項目中使用不多,這里不再詳細介紹,如有特殊要求歡迎一起交流。

常見動畫模式

  • 列表和網(wǎng)格動畫,常見的ListView,GridView展示,加載、刪除的動畫
  • 共享元素動畫(Hero),標準Hero動畫和徑向Hero動畫
  • 交織動畫

標準Hero動畫使用起來比較簡單,系統(tǒng)提供有heroWidget,只需要在轉(zhuǎn)場前后頁面保持同樣的tag即可。

其原理是,系統(tǒng)在動畫運行的時候,在原視圖的基礎(chǔ)上覆蓋一層overlay,當(dāng)然還有其中過渡動畫。

hero-transition.png

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

Hero動畫.gif
radial-hero-animation.png

這部分的詳細介紹看這里Hero動畫,文章已經(jīng)太長了,這里就不展開講了。

交織動畫主要考慮的是:

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

全文小結(jié)

文章第一部分先介紹了一些基本概念Ticker、AnimationController、Animation、Tween、Curve這些是Flutter動畫的核心,通過對其源碼分析,了解到彼此間的關(guān)聯(lián)關(guān)系。然后介紹了隱式動畫和顯示動畫,以及如何進行自定義,接著又介紹了如何選用適合自己的動畫,這部分之前文章有介紹,這里一筆帶過了,建議詳細閱讀下之前的文章,最后介紹了Hero動畫和交織動畫。

看到這里,想必你對Flutter動畫體系有了一定了解,文章中鏈接的文章,之前已經(jīng)單開文章介紹過也推薦看一看。當(dāng)然了解了之后還需要寫代碼練習(xí),相信再看到Flutter動畫代碼的時候,就不會那么陌生了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容