本文主要介紹了動(dòng)畫(huà)的原理相關(guān)概念,對(duì)其他平臺(tái)的動(dòng)畫(huà)做了一個(gè)簡(jiǎn)要的梳理,并簡(jiǎn)要的介紹了Flutter動(dòng)畫(huà)的一些知識(shí)。
1. 動(dòng)畫(huà)介紹
動(dòng)畫(huà)對(duì)于App來(lái)說(shuō),非常的重要。很多App,正是因?yàn)橛辛藙?dòng)畫(huà),所以才會(huì)覺(jué)得炫酷。移動(dòng)端的動(dòng)畫(huà)庫(kù)有非常的多,例如iOS上的Pop、web端的animate.css、Android端的AndroidViewAnimations、跨平臺(tái)的Lottie等。正是因?yàn)橛辛诉@些封裝好的動(dòng)畫(huà)庫(kù),我們制作酷炫的效果方便了不少。當(dāng)然了,這些庫(kù)都是基于各平臺(tái)基礎(chǔ)的動(dòng)畫(huà)API實(shí)現(xiàn)的,筆者今天要聊的,也就是基礎(chǔ)的動(dòng)畫(huà)及背后的原理。
1.1 動(dòng)畫(huà)的本質(zhì)
動(dòng)畫(huà)顧名思義,就是動(dòng)起來(lái)的畫(huà)面。畫(huà)面為什么會(huì)動(dòng)起來(lái)了呢?在回答這個(gè)問(wèn)題之前,我們先引入一個(gè)概念。
人眼在觀察景物時(shí),光信號(hào)傳入大腦神經(jīng),需經(jīng)過(guò)一段短暫的時(shí)間,光的作用結(jié)束后,視覺(jué)形象并不立即消失,這種殘留的視覺(jué)稱“后像”,視覺(jué)的這一現(xiàn)象則被稱為“視覺(jué)暫留”。
視覺(jué)暫留被認(rèn)為是電影的最重要的一個(gè)理論基礎(chǔ)。我們看到的動(dòng)畫(huà),實(shí)際上是一連串的畫(huà)面組成,只不過(guò)是以很快的速度去播放,人眼在下一個(gè)畫(huà)面出來(lái)之前,還殘留著上一個(gè)畫(huà)面的視覺(jué),看起來(lái)就像是在沒(méi)有間隔的播放這一系列的圖片,也就是我們稱之為的動(dòng)畫(huà)。
1.2 相關(guān)概念
動(dòng)畫(huà)會(huì)有很多相關(guān)的概念,理解了這些概念,會(huì)對(duì)實(shí)際的使用更有幫助。
1.2.1 幀
剛才在介紹動(dòng)畫(huà)本質(zhì)的時(shí)候,用到了畫(huà)面這個(gè)詞匯,只是方便讀者去理解,這個(gè)畫(huà)面,在學(xué)術(shù)上叫做幀。
幀就是影像動(dòng)畫(huà)中最小單位的單幅影像畫(huà)面,一幀就是一副靜止的畫(huà)面。

幀里面又分為關(guān)鍵幀和過(guò)渡幀,這兩概念是理解一些動(dòng)畫(huà)的基礎(chǔ),例如Android中的補(bǔ)間動(dòng)畫(huà)。在一些場(chǎng)景中,我們可能不會(huì)給出一個(gè)動(dòng)畫(huà)的所有幀,所以將幀分成關(guān)鍵幀和過(guò)渡幀。關(guān)鍵幀可以理解為一個(gè)動(dòng)畫(huà)的起始狀態(tài),而過(guò)渡幀則是系統(tǒng)自動(dòng)完成插在關(guān)鍵幀之間的部分。

我們知道Android中的補(bǔ)間動(dòng)畫(huà),基礎(chǔ)的有四種類型,平移、縮放、旋轉(zhuǎn)、透明度。而我們?cè)O(shè)置動(dòng)畫(huà)的時(shí)候,通常只是設(shè)置起始的狀態(tài),也就是關(guān)鍵幀,中間過(guò)程其實(shí)我們并不需要去考慮,如果關(guān)注動(dòng)畫(huà)速率的話,頂多加一個(gè)差值器去控制,但是中間生成的幀我們并沒(méi)有提供。
系統(tǒng)為什么能夠補(bǔ)齊過(guò)渡幀呢?我們看下這四種基本的動(dòng)畫(huà)類型,給定起始狀態(tài),中間狀態(tài)我們其實(shí)是可以通過(guò)計(jì)算推演出來(lái)的,這也是系統(tǒng)為什么能夠補(bǔ)齊的原因。
是不是只有這四種才可以通過(guò)系統(tǒng)填補(bǔ)過(guò)渡幀呢?顯然不是的,例如一個(gè)跳躍前進(jìn)的動(dòng)畫(huà),添加一些限制條件,就可以推演出中間的狀態(tài)。系統(tǒng)提供的只是比較常見(jiàn)的四種,并不是說(shuō)只有這四種,而是絕大部分動(dòng)畫(huà)都可以通過(guò)這四種組合實(shí)現(xiàn)。當(dāng)然了,肯定也是有實(shí)現(xiàn)不了的,這個(gè)時(shí)候有一個(gè)辦法就是通過(guò)canvas畫(huà)出來(lái)。
另外再插一嘴,Android系統(tǒng)提供的四種動(dòng)畫(huà)操作,也是變換矩陣是四維的原因,具體的就不多說(shuō)了,之前文章也有介紹過(guò)。
最后一嘴,此處講解幀的概念,拿了很多Android相關(guān)的知識(shí)去講解,只是希望讀者能夠通過(guò)一些已知的概念,去理解一些未知的。動(dòng)畫(huà)的原理都一樣,具體到某個(gè)平臺(tái),可能頂多就是實(shí)現(xiàn)或者叫法不一樣罷了。
1.2.2 幀數(shù)與FPS
小時(shí)候很多人都玩過(guò)書(shū)角動(dòng)畫(huà)。在書(shū)或者本子的一角,每一頁(yè)都畫(huà)上一個(gè)畫(huà)面,然后撥書(shū)角,不同速度撥,動(dòng)畫(huà)的感受不一樣,撥的越快,動(dòng)畫(huà)越流暢。這是為什么呢?這就牽扯到幀數(shù)與FPS了。
幀數(shù),幀的數(shù)量。FPS(Frame per Second),即每秒顯示幀數(shù)。
這兩個(gè)概念,主要是FPS有什么作用呢?這是因?yàn)槿搜凵順?gòu)造的原因。人眼殘留鏡像的時(shí)間是有限的,如果過(guò)了這個(gè)時(shí)間,下一幀還沒(méi)有變化,就會(huì)感覺(jué)不流暢。但也不是幀數(shù)越大越好,畢竟人眼也是有極限的。

1.2.3 插值器
如果動(dòng)畫(huà)播放一直都是這種勻速的進(jìn)行,那表現(xiàn)形式就太單一了。那如何實(shí)現(xiàn)非線性的動(dòng)畫(huà)效果呢,這個(gè)時(shí)候就需要用到插值器了。
插值器其實(shí)并不復(fù)雜,就是一個(gè)數(shù)學(xué)函數(shù),設(shè)置屬性值從初始值過(guò)渡到結(jié)束值的變化規(guī)律。每個(gè)平臺(tái)都有自己定義好的一系列插值器,可以供開(kāi)發(fā)者選擇使用,也提供自定義的接口,本質(zhì)上是一個(gè)貝塞爾函數(shù)。
一個(gè)勻速插值器如下:
屬性值百分比 = 時(shí)間百分比
1.3 如何實(shí)現(xiàn)
動(dòng)畫(huà)的基本原理和一些基本概念都介紹了一下,現(xiàn)在來(lái)聊一下動(dòng)畫(huà)的實(shí)現(xiàn)。
先拋開(kāi)系統(tǒng)層級(jí)的各種渲染優(yōu)化,也僅僅是以補(bǔ)間動(dòng)畫(huà)為例,假設(shè)以現(xiàn)有的移動(dòng)平臺(tái)基礎(chǔ)上,去實(shí)現(xiàn)一套簡(jiǎn)單的動(dòng)畫(huà)框架,該如何去實(shí)現(xiàn)呢?
以Android的為例,要實(shí)現(xiàn)平移、縮放、旋轉(zhuǎn)、透明度這四種基礎(chǔ)的補(bǔ)間動(dòng)畫(huà),可以看到,這些都是基于某個(gè)屬性的動(dòng)畫(huà),平移是基于point、縮放是基于scale、旋轉(zhuǎn)是基于angle、透明度是基于alpha。
結(jié)合插值器,提煉出一個(gè)通用的動(dòng)畫(huà)類,這個(gè)類的作用是根據(jù)插值器,得到視圖某個(gè)時(shí)間點(diǎn)的屬性變化的狀態(tài)。
既然各個(gè)時(shí)間點(diǎn)的狀態(tài)已經(jīng)有了,剩下來(lái)的就是讓各個(gè)狀態(tài)渲染出來(lái)。底層的機(jī)制在此處不去討論,這個(gè)地方就需要一個(gè)定時(shí)器,定時(shí)器的作用是每隔一段時(shí)間就把素材渲染到屏幕上。
至此,一個(gè)簡(jiǎn)易的動(dòng)畫(huà)框架就出來(lái)了。如果對(duì)各平臺(tái)比較了解的話,就知道我說(shuō)的是視圖動(dòng)畫(huà),真正的動(dòng)畫(huà)引擎不是這么簡(jiǎn)單,涉及到的技術(shù)也比較復(fù)雜,但是大體的思想不會(huì)有錯(cuò),不管是哪種動(dòng)畫(huà),都是跟時(shí)間相關(guān)的幀序列,只是實(shí)現(xiàn)方式不同。
這也是筆者為什么花這么多篇幅去介紹動(dòng)畫(huà)相關(guān)的概念,知道一些底層原理后,不管什么平臺(tái),怎么去實(shí)現(xiàn),底層的思想肯定都差不多,只是實(shí)現(xiàn)上的不同。
2. 其他平臺(tái)的動(dòng)畫(huà)
Flutter動(dòng)畫(huà),與Android、iOS等平臺(tái)對(duì)比,其實(shí)本身并沒(méi)有什么特別之處。基本的原理是一樣的,只是提供的種類以及實(shí)現(xiàn)的方式不同罷了。
2.1 Android動(dòng)畫(huà)
Android的動(dòng)畫(huà),大的分類有兩種:
- 視圖動(dòng)畫(huà)(View Animation)
- 屬性動(dòng)畫(huà)(Property Animation)
視圖動(dòng)畫(huà)又可以分為兩類:
- 補(bǔ)間動(dòng)畫(huà)(Tween Animation)
- 逐幀動(dòng)畫(huà)(Frame Animation)
這之間的差別是什么呢?它們只有實(shí)現(xiàn)上的差別
- 補(bǔ)間動(dòng)畫(huà)是根據(jù)初始狀態(tài),系統(tǒng)自動(dòng)補(bǔ)充中間狀態(tài);
- 逐幀動(dòng)畫(huà)則是需要我們提供每一幀;
- 視圖動(dòng)畫(huà)只是作用于視圖上,而不會(huì)改變控件的屬性;
- 屬性動(dòng)畫(huà)則是會(huì)實(shí)實(shí)在在的更改控件的屬性。
可以看出Android的動(dòng)畫(huà)分類還是比較明晰的。
2.2 iOS動(dòng)畫(huà)
iOS很久沒(méi)弄了,在這里也簡(jiǎn)單說(shuō)下,不對(duì)的話還請(qǐng)各位指正。
- 隱式動(dòng)畫(huà)
- 顯式動(dòng)畫(huà)
顯式動(dòng)畫(huà)又可以分為兩類:
- 基礎(chǔ)動(dòng)畫(huà)
- 關(guān)鍵幀動(dòng)畫(huà)
這些動(dòng)畫(huà)類別之間的差別是什么呢?
- 隱式動(dòng)畫(huà),顧名思義是不指定動(dòng)畫(huà)類型,更改某個(gè)屬性,Core Animation來(lái)決定如何且何時(shí)去做動(dòng)畫(huà);
- 基礎(chǔ)動(dòng)畫(huà),根據(jù)起始值來(lái)做動(dòng)畫(huà);
- 關(guān)鍵幀動(dòng)畫(huà),則是定義一系列關(guān)鍵幀,系統(tǒng)自動(dòng)補(bǔ)齊中間的過(guò)渡幀。
通過(guò)動(dòng)畫(huà)這一塊兒,可以看出iOS的分類其實(shí)是比較的模糊的,有一些歷史包袱。
2.3 css動(dòng)畫(huà)
css動(dòng)畫(huà)大體上有兩種:
- Transition
- Animation
web中的動(dòng)畫(huà)分類簡(jiǎn)單的多了
- Transition動(dòng)畫(huà),給定起始值,可以結(jié)合插值器做動(dòng)畫(huà);
- Animation動(dòng)畫(huà),則是定義一系列關(guān)鍵幀,系統(tǒng)補(bǔ)齊中間的過(guò)渡幀。
2.4 小節(jié)
通過(guò)上面?zhèn)€平臺(tái)動(dòng)畫(huà)粗略的介紹,動(dòng)畫(huà)在不同平臺(tái)雖然被叫著不同的名稱,本質(zhì)上其實(shí)都差不多的,變來(lái)變?nèi)ザ际沁@幾種方式,要么根據(jù)屬性要么根據(jù)關(guān)鍵幀,要么更改繪制層,要么更改控件本身屬性。一些游戲引擎,雖然我沒(méi)有看,但是我覺(jué)得原理也大致相似。
3. Flutter動(dòng)畫(huà)
上面鋪墊了這么多,終于到Flutter動(dòng)畫(huà)了。Flutter是一門比較新的技術(shù),歷史包袱理應(yīng)說(shuō)是最小的。
3.1 Flutter動(dòng)畫(huà)分類
Flutter動(dòng)畫(huà)分為兩類:
- 補(bǔ)間動(dòng)畫(huà)(Tween Animation)
- 基于物理的動(dòng)畫(huà)(Physics-based animation)
補(bǔ)間動(dòng)畫(huà)很好理解,基于物理的動(dòng)畫(huà)是這個(gè)什么鬼。
基于物理的動(dòng)畫(huà)是一種遵循物理學(xué)定律的動(dòng)畫(huà)形式
舉個(gè)例子,比方說(shuō)你滑動(dòng)一張圖片,這個(gè)過(guò)程不是勻速的,而是起始速度快,然后慢慢的降速,就像一本書(shū)在地上往前推一樣。它有什么特點(diǎn)呢?
- 遵循物理學(xué)定律;
- 能夠依據(jù)加速度和速度去計(jì)算和更新每一幀的動(dòng)畫(huà)數(shù)值;
- 當(dāng)受力平衡時(shí),動(dòng)畫(huà)為處于恒定運(yùn)動(dòng)或靜止?fàn)顟B(tài)。
哈哈,最后一點(diǎn)是不是似曾相識(shí),這樣做的好處是什么呢?隨著人們生活水平的極大提升,移動(dòng)端硬件這些年也是趕英超美,人們不再滿足于簡(jiǎn)單的動(dòng)畫(huà),于是就有部分有(xian)識(shí)(de)之(dan)士(teng),實(shí)現(xiàn)了基于物理學(xué)定律的動(dòng)畫(huà)。
這種動(dòng)畫(huà)iOS或者Android有沒(méi)有呢,是有的,但不是作為最基礎(chǔ)的動(dòng)畫(huà)API被提供。為什么其他平臺(tái)沒(méi)有將這個(gè)納入最基本的動(dòng)畫(huà)中去呢?
- 歷史原因。iOS以及Android端多年前就誕生了,那個(gè)時(shí)候,硬件資源都是極其有限的,當(dāng)時(shí)的環(huán)境不足以支撐這種動(dòng)畫(huà)效果。但也不是說(shuō)沒(méi)有,一些游戲引擎里面也是有的,但是作為操作系統(tǒng),把這些集成進(jìn)去,還是不太現(xiàn)實(shí)的。
- 認(rèn)知過(guò)程。電腦以及移動(dòng)端這些年的發(fā)展,最開(kāi)始只是滿足于查看最簡(jiǎn)單的文本,然后各種圖片視頻。隨著互聯(lián)網(wǎng)的越來(lái)越普及,人們的需求越來(lái)越多了。于是,在一些游戲里面才會(huì)見(jiàn)到的基于物理學(xué)定律的動(dòng)畫(huà),進(jìn)入了尋常百姓家。反觀一下,現(xiàn)在也是有非常多的“委屈”事物。例如人類幾千年都是通過(guò)人眼看現(xiàn)實(shí)的事物,現(xiàn)在卻被限制在一個(gè)小屏幕上,這其實(shí)是不合理的。所以AR、VR,還有一些動(dòng)畫(huà)片科幻片中的遠(yuǎn)程感知等技術(shù),才會(huì)層出不窮,當(dāng)然這個(gè)扯的有些遠(yuǎn)了。
基于物理的動(dòng)畫(huà)這么好,那有什么好處呢?更自然,更加符合人們的認(rèn)知。
3.2 分類的原因
前面講的各平臺(tái)的動(dòng)畫(huà),從本質(zhì)上看,基于某個(gè)屬性也好,幀動(dòng)畫(huà)也好,都是從一種狀態(tài)到另一種狀態(tài),而中間過(guò)程是可以推演出來(lái)的,所以Flutter提供補(bǔ)間動(dòng)畫(huà)。
基于物理的動(dòng)畫(huà),我猜測(cè)可能是為了實(shí)現(xiàn)其他平臺(tái)上的一些效果,例如彈簧、阻尼效果等等。所以Flutter就提供了這種動(dòng)畫(huà)API,畢竟沒(méi)什么包袱。
3.3 動(dòng)畫(huà)模式
Flutter提煉了三種動(dòng)畫(huà)模式,與其說(shuō)提煉出來(lái)的,倒不如說(shuō)統(tǒng)一不能更為合適。
- list、grid中的動(dòng)畫(huà)(Animated list or grid)。場(chǎng)景是item的添加或者刪除操作;
- 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(Shared element transition)。場(chǎng)景是當(dāng)前頁(yè)面打開(kāi)另一頁(yè)面的過(guò)渡動(dòng)畫(huà);
- 交錯(cuò)動(dòng)畫(huà)(Staggered animation)。場(chǎng)景是需要部分或者完全交錯(cuò)的動(dòng)畫(huà)。
3.4 復(fù)雜度
Flutter的實(shí)現(xiàn)原理以及這個(gè)階段,注定了做動(dòng)畫(huà)是非常麻煩的一件事情??缙脚_(tái)的技術(shù)做動(dòng)畫(huà)都麻煩,這個(gè)似乎是通識(shí),為了跨平臺(tái)而同化的一些東西,到異化部分,就變得蛋疼了,動(dòng)畫(huà)正是這種存在。
Flutter做動(dòng)畫(huà)復(fù)雜提現(xiàn)在哪些地方呢?
- 實(shí)現(xiàn)的動(dòng)畫(huà)較少,這個(gè)是初期,沒(méi)啥好說(shuō)的;
- 動(dòng)畫(huà)實(shí)現(xiàn)的方式復(fù)雜,這個(gè)是Flutter的設(shè)計(jì)思想所決定的。
4. 小節(jié)
關(guān)于動(dòng)畫(huà)的具體的實(shí)現(xiàn)、一些底層的代碼邏輯以及如何使用,將會(huì)在下一篇文章中做介紹。這篇文章更多的是偏于一些普適性的介紹,關(guān)于Flutter動(dòng)畫(huà)相關(guān)的介紹反而很少。希望讀者能夠了解一些動(dòng)畫(huà)的原理,以及各個(gè)平臺(tái)動(dòng)畫(huà)的大致實(shí)現(xiàn)方式,這樣可以更好的理解Flutter動(dòng)畫(huà)的設(shè)計(jì)思想。文中若有錯(cuò)誤的地方,還懇請(qǐng)指出,在此不勝感激。
5. 后話
筆者建了一個(gè)Flutter學(xué)習(xí)相關(guān)的項(xiàng)目,Github地址,里面包含了筆者寫(xiě)的關(guān)于Flutter學(xué)習(xí)相關(guān)的一些文章,會(huì)定期更新,也會(huì)上傳一些學(xué)習(xí)Demo,歡迎大家關(guān)注。