Flutter 動(dòng)畫(huà)淺析

什么是動(dòng)畫(huà)?

  • 人類具有“視覺(jué)暫留”的特性,人的眼睛看到畫(huà)面在0.34秒內(nèi)不會(huì)消失。利用這一原理,在一幅畫(huà)還沒(méi)有消失前播放下一幅畫(huà),就會(huì)給人造成一種流暢的視覺(jué)變化效果。因此,當(dāng)一段時(shí)間內(nèi),連續(xù)的對(duì)UI進(jìn)行改變;人的眼睛就會(huì)產(chǎn)生視覺(jué)暫留,最終看到的就是一個(gè)“連續(xù)”的動(dòng)畫(huà);對(duì)于人眼來(lái)說(shuō),一般幀率只要超過(guò)16FPS,就會(huì)比較流暢,超過(guò)32FPS就會(huì)較為細(xì)膩平滑,超過(guò)32FPS基本感受不到差別了。


    image

Flutter 動(dòng)畫(huà)簡(jiǎn)述

  • 由于 Flutter 動(dòng)畫(huà)中, 每一幀的變化都是需要對(duì)UI進(jìn)行改變,短時(shí)間內(nèi)連續(xù)的改變UI輸出會(huì)非常的消耗資源的,因此在UI系統(tǒng)中,動(dòng)畫(huà)的平均幀率是重要的性能指標(biāo);Flutter 動(dòng)畫(huà)在理想狀態(tài)下是可以實(shí)現(xiàn)60FPS的,這和原生應(yīng)用能達(dá)到的幀率是基本是持平;Flutter 中對(duì)動(dòng)畫(huà)進(jìn)行了抽象,主要通過(guò) Animation、Curve、Controller、Tween 配合來(lái)完成動(dòng)畫(huà)
    • Animation 對(duì)象是Flutter動(dòng)畫(huà)庫(kù)中的一個(gè)核心類,它生成指導(dǎo)動(dòng)畫(huà)的值。
    • Animation 對(duì)象知道動(dòng)畫(huà)的當(dāng)前狀態(tài)(例如,它是開(kāi)始、停止還是向前或向后移動(dòng)),但它不知道屏幕上顯示的內(nèi)容。
    • AnimationController 管理 Animation
    • CurvedAnimation 將過(guò)程抽象為一個(gè)非線性曲線.
    • Tween 在正在執(zhí)行動(dòng)畫(huà)的對(duì)象所使用的數(shù)據(jù)范圍之間生成值。例如,Tween 可能會(huì)生成從紅到藍(lán)之間的色值,或者從 0 到 255。
    • 使用 ListenersStatusListeners 監(jiān)聽(tīng)動(dòng)畫(huà)狀態(tài)改變。

Animation

Flutter中的動(dòng)畫(huà)系統(tǒng)基于 Animation 對(duì)象的,widget 可以在 build 函數(shù)中讀取 Animation對(duì)象的當(dāng)前值, 并且可以監(jiān)聽(tīng)動(dòng)畫(huà)的狀態(tài)改變。

  • Animation 對(duì)象本身和 UI 渲染沒(méi)有任何關(guān)系。Animation 是一個(gè)抽象類,它擁有其當(dāng)前值和狀態(tài)(完成或停止)。其中一個(gè)比較常用的 Animation 類是 Animation<double>Animation 對(duì)象是一個(gè)在一段時(shí)間內(nèi)依次生成一個(gè)區(qū)間之間值的類, 對(duì)象的輸出可以是線性的、曲線的、一個(gè)步進(jìn)函數(shù)或者任何其他可以設(shè)計(jì)的映射, 根據(jù) Animation 對(duì)象的控制方式,動(dòng)畫(huà)可以反向運(yùn)行,甚至可以在中間切換方向。Animation 還可以生成除 double 之外的其他類型值,如:Animation<Color>Animation<Size>Animation 對(duì)象有狀態(tài),在動(dòng)畫(huà)的每一幀中,我們可以通過(guò) Animation 對(duì)象的 value 屬性獲取動(dòng)畫(huà)的當(dāng)前狀態(tài)值。

  • 動(dòng)畫(huà)監(jiān)聽(tīng)

    • 使用 addListener()Animation 添加幀監(jiān)聽(tīng)器,在每一幀都會(huì)被調(diào)用。最常見(jiàn)的操作是改變狀態(tài)后調(diào)用 setState() 來(lái)觸發(fā)UI重建。
    • 使用 addStatusListener()Animation 添加 “動(dòng)畫(huà)狀態(tài)改變” 監(jiān)聽(tīng)器;動(dòng)畫(huà)開(kāi)始、結(jié)束、正向或反向(見(jiàn) AnimationStatus 定義)時(shí)會(huì)調(diào)用狀態(tài)改變的監(jiān)聽(tīng)器。

CurvedAnimation

  • CurvedAnimation 將動(dòng)畫(huà)過(guò)程定義為一個(gè)非線性曲線
  • Flutter 中通過(guò) Curve(曲線)來(lái)描述動(dòng)畫(huà)過(guò)程,我們把勻速動(dòng)畫(huà)稱為線性的(Curves.linear),而非勻速動(dòng)畫(huà)稱為非線性的
final CurvedAnimation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeIn);
  • Curves 類定義了許多常用的曲線,我們也可以選擇自定義
class ShakeCurve extends Curve {
  @override
  double transform(double t) {
    return math.sin(t * math.PI * 2);
  }
}
  • CurvedAnimationAnimationController 都是 Animation<double> 類型,CurvedAnimation 可以通過(guò)包裝 AnimationControllerCurve 生成一個(gè)新的動(dòng)畫(huà)對(duì)象 ,通過(guò)這種方式來(lái)將動(dòng)畫(huà)和動(dòng)畫(huà)執(zhí)行的曲線進(jìn)行關(guān)聯(lián);

AnimationController

  • AnimationController 是一個(gè)特殊的 Animation 對(duì)象,在屏幕刷新的每一幀,就會(huì)生成一個(gè)新的值。它包含動(dòng)畫(huà)的啟動(dòng) forward()、stop()、reverse() 等方法;默認(rèn)情況下,AnimationController 在給定的時(shí)間段內(nèi)會(huì)線性的生成0.0~1.0 的數(shù)字,另外在創(chuàng)建 AnimationController 時(shí),需要傳遞一個(gè) vsync 參數(shù),存在 vsync 時(shí)會(huì)防止動(dòng)畫(huà)的UI不在當(dāng)前屏幕時(shí)消耗不必要的資源 。 例如,下面代碼創(chuàng)建一個(gè)Animation對(duì)象,但不會(huì)啟動(dòng)它運(yùn)行
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 2000), vsync: this);

注:vsync 對(duì)象會(huì)綁定動(dòng)畫(huà)的定時(shí)器到一個(gè)可視的 widget,當(dāng) widget 不顯示時(shí),動(dòng)畫(huà)定時(shí)器將會(huì)暫停,當(dāng) widget 再次顯示時(shí),動(dòng)畫(huà)定時(shí)器重新恢復(fù)執(zhí)行,這樣就可以避免動(dòng)畫(huà)相關(guān)UI不在當(dāng)前屏幕時(shí)消耗資源。 如果要使用自定義的State 對(duì)象作為 vsync 時(shí),請(qǐng)包含 TickerProviderStateMixin;

注意: 在某些情況下,值(position,指動(dòng)畫(huà)的當(dāng)前值)可能會(huì)超出AnimationController 的 0.0-1.0 的范圍。例如,fling() 函數(shù)允許您提供速度(velocity)、力量(force)、position(通過(guò)Force對(duì)象)。位置(position)可以是任何東西,因此可以在0.0到1.0范圍之外。 CurvedAnimation 生成的值也可以超出0.0到1.0的范圍。根據(jù)選擇的曲線,CurvedAnimation 的輸出可以具有比輸入更大的范圍。例如,Curves.elasticIn 等彈性曲線會(huì)生成大于或小于默認(rèn)范圍的值。

  • AnimationController 生成數(shù)字的區(qū)間可以通過(guò) lowerBoundupperBound 來(lái)指定
final AnimationController controller = new AnimationController( 
 duration: const Duration(milliseconds: 2000), 
 lowerBound: 10.0,
 upperBound: 20.0,
 vsync: this
);
  • AnimationController 派生自 Animation<double>,因此可以在需要Animation 對(duì)象的任何地方使用。 但是,AnimationController 具有控制動(dòng)畫(huà)的其他方法。例如,.forward() 方法可以啟動(dòng)動(dòng)畫(huà)。數(shù)字的產(chǎn)生與屏幕刷新有關(guān),因此每秒鐘通常會(huì)產(chǎn)生60個(gè)數(shù)字,在生成每個(gè)數(shù)字后,每個(gè) Animation 對(duì)象調(diào)用添加的 Listener 對(duì)象。
  • 當(dāng)動(dòng)畫(huà)開(kāi)始執(zhí)行后會(huì)開(kāi)始生成動(dòng)畫(huà)幀,屏幕每刷新一次就是一個(gè)動(dòng)畫(huà)幀,每一個(gè)動(dòng)畫(huà)幀,會(huì)隨著動(dòng)畫(huà)的曲線來(lái)生成當(dāng)前的 Animation.value,然后根據(jù)當(dāng)前的 Animation.value 去構(gòu)建UI,當(dāng)所有動(dòng)畫(huà)幀依次觸發(fā)時(shí),Animation.value 會(huì)依次改變,構(gòu)建的UI也會(huì)跟隨著依次變化,最終我們可以看到一個(gè)完成的動(dòng)畫(huà)。 另外在動(dòng)畫(huà)的每一幀,Animation 對(duì)象會(huì)調(diào)用其幀監(jiān)聽(tīng)器,等動(dòng)畫(huà)狀態(tài)發(fā)生改變時(shí)會(huì)調(diào)用狀態(tài)改變監(jiān)聽(tīng)器。

Tween

  • 默認(rèn)情況下,AnimationController 對(duì)象的范圍為 0.0~1.0。如果您需要不同的范圍或不同的數(shù)據(jù)類型,則可以使用 Tween 來(lái)配置動(dòng)畫(huà)以生成不同的范圍或數(shù)據(jù)類型的值;Tween是一個(gè)無(wú)狀態(tài)(stateless)對(duì)象,需要 beginend 值。Tween 的唯一職責(zé)就是定義從輸入范圍到輸出范圍的映射。輸入范圍通常為 0.0~1.0,但這不是必須的;
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);
  • Tween 繼承自 Animatable<T>,而非 Animation<T>AnimatableAnimation 相似,不是必須輸出 double 值。例如,ColorTween 指定兩種顏色之間的過(guò)渡
final Tween colorTween =
    new ColorTween(begin: Colors.transparent, end: Colors.black54);
  • Tween 對(duì)象不存儲(chǔ)任何狀態(tài),它提供了 evaluate(Animation<double> animation) 方法將映射函數(shù)應(yīng)用于動(dòng)畫(huà)當(dāng)前值。Animation 對(duì)象的當(dāng)前值可以通過(guò) value() 方法取到。evaluate 函數(shù)還執(zhí)行一些其它處理,例如分別確保在動(dòng)畫(huà)值為 0.0~1.0 時(shí)返回開(kāi)始和結(jié)束狀態(tài)
  • 使用 Tween 對(duì)象,需要調(diào)用其 animate() 方法,傳入一個(gè)控制器對(duì)象。
// 需要注意的是 `animate()` 返回的是一個(gè) `Animation`,而不是一個(gè) `Animatable`
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);

Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);
  • 以下示例構(gòu)建了一個(gè)控制器、一條曲線和一個(gè) Tween
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeOut);

Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容