讓動畫實現更簡單,Flutter 動畫簡易教程!
本文作者:Didier Boelens
原文連結: …
翻譯: hcc
Flutter中的動畫功能強大且易於使用。接下來透過一個具體的例項,您將學到關於 Flutter 動畫的一切。
難度:中級
今天,我們無法想象移動應用程式裡面沒有任何動畫,當您從一頁跳轉到另一頁時,或者點選一個按鈕(如 InkWell)... 都會有一個動畫。動畫無處不在。
Flutter 使動畫非常易於實現。
簡而言之,這篇文章就是討論這個話題的,儘管之前只有專家才能談論,為了讓這篇文章顯得更有吸引力,我將挑戰一下,仿照 Vitaly Rubtsov 在 Dribble 上傳的一個 "Guillotine Menu (斬頭選單)"的一個動畫效果,用 Flutter 一步步的實現這個效果。
本文的第一部分將介紹一下主要的理論知識和概念,第二部分將要實現上面的那個動畫效果。
動畫中的三大核心
為了能夠實現動畫效果,必須提供下面的三個元素:
- Ticker
- Animation
- AnimationController
下面對這幾個元素進行一下簡單的介紹,更詳細的在後面說明。
Ticker
簡單來說,Ticker 這個類會在常規的一個時間區間裡(大約每秒 60 次),傳送一個訊號,把這想象成你得手錶,每秒都會滴答滴答的轉。
當 Ticker 啟動之後,自從第一個 tick 到來開始,每個到的 tick 都會回撥 Ticker 的 callback 方法。
重要提示
儘管所有的 ticker 可能是在不同的時間裡啟動的,但是它們總是以同步的方式執行,這對於一些同步動畫是很有用的。
Animation
Animation 其實沒有什麼特別的,只不過是一個可以隨著動畫的生命週期改變的一個值(有特定的型別),值隨著動畫時間的變化而變化的方式可以是線性的(例如1、2、3、4、5...),也可以更為複雜(參考後面的“Curves 曲線”)。
AnimationController
AnimationController 是一個可以控制一個或多個動畫(開始,結束,重複)的控制器。換句話說,它讓上面說的 Animation 值在一個指定的時間內,根據一個速度從一個最小值變化到最大。
AnimationController 類介紹
此類可控制動畫。為了更加精確,我寧願說“ 控制一個場景”,因為稍後我們將看到,幾個不同的動畫可以由同一個控制器來控制……
因此,使用這個AnimationController類,我們可以:
- 開始一個子動畫,正向或者反向播放
- 停止一個子動畫
- 為子動畫設定一個具體的值
- 定義動畫值的邊界
以下虛擬碼可以展示這個類裡面的不同的初始化引數
AnimationController controller = new AnimationController( value: // the current value of the animation, usually 0.0 (= default) lowerBound: // the lowest value of the animation, usually 0.0 (= default) upperBound: // the highest value of the animation, usually 1.0 (= default) duration: // the total duration of the whole animation (scene) vsync: // the ticker provider debugLabel: // a label to be used to identify the controller // during debug session); 複製程式碼
在大多數情況下,初始化 AnimationController 時不會設計到 value,lowerBound,upperBound和debugLabel。
如何將 AnimationController 繫結到 Ticker 上
為了讓動畫正常工作,必須將 AnimationController 繫結到 Ticker 上。
通常情況下,你可以生成一個 Ticker 繫結到一個 StatefulWidget 例項上。
class _MyStateWidget extends State<MyStateWidget> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState(){ super.initState(); _controller = new AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); } @override void dispose(){ _controller.dispose(); super.dispose(); } ... } 複製程式碼
-
第 2 行 這行程式碼告訴 Flutter ,你想要一個單 Ticker,這個 Ticker 連結到了 MyStateWidget 例項上。
-
8-10行
控制器的初始化。場景(子動畫)的總持續時間設定為1000毫秒,並繫結到了 Ticker(vsync:this)。
隱式引數為:lowerBound = 0.0 和 upperBound = 1.0
- 16行
非常重要,當 MyStateWidget 這個頁面的例項銷燬時,您需要釋放 controller。
TickerProviderStateMixin 還是 SingleTickerProviderStateMixin?
如果你有幾個Animation Controller情況下,你想有不同的 Ticker, 只需要將 SingleTickerProviderStateMixin 替換為 TickerProviderStateMixin。
好的,我已經將控制器繫結到了 Ticker 上,但是它是工作的?
正是由於 ticker,每秒鐘將會產生大約 60 個 tick,AnimationController 將根據 tick 在給定的時間裡,線性的產生在最小值和最大值之間的值。
在這1000毫秒內產生的值的示例如下:
我們看到值在1000毫秒內從0.0(lowerBound)到1.0(upperBound)變化。生成了51個不同的值。
讓我們擴充套件程式碼以檢視如何使用它。
class _MyStateWidget extends State<MyStateWidget> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState(){ super.initState(); _controller = new AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _controller.addListener((){ setState((){}); }); _controller.forward(); } @override void dispose(){ _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context){ final int percent = (_controller.value * 100.0).round(); return new Scaffold( body: new Container( child: new Center( child: new Text('$percent%'), ), ), ); } } 複製程式碼
-
12 行 此行告訴控制器,每次其值更改時,我們都需要重建Widget(透過setState())
-
第15行
Widget初始化完成後,我們告訴控制器開始計數(forward() -> 從lowerBound到upperBound)
- 26行
我們檢索控制器的值(_controller.value),並且在此示例中,此值的範圍是0.0到1.0(也就是 0% 到 100%),我們得到此百分比的整數表示式,將其顯示在頁面的中心。
動畫的概念
如我們所見, controller 可以以線性的方式返回彼此不同的小數值。
有的時候我們可能還有其他的需求如:
- 使用其他型別的值,例如Offset,int …
- 使用範圍不是從0.0到1.0
- 考慮線性變化以外的其他變化型別以產生一些效果
使用其他值型別
為了能夠使用其他值型別,Animation 類使用模板。
換句話說,您可以定義:
Animation<int> integerVariation; Animation<double> decimalVariation; Animation<Offset> offsetVariation; 複製程式碼
使用不同的數值範圍
有時,我們希望使用一個不同的範圍,而不是0.0和1.0。
為了定義這樣的範圍,我們將使用 Tween 類。
為了說明這一點,讓我們考慮一個情況,您希望角度從0到π/ 2 變化的情況。
Animation<double> angleAnimation = new Tween(begin: 0.0, end: pi/2); 複製程式碼
變化型別
如前所述,將預設值從 lowerBound 變化到 upperBound 的預設方式是線性的,controller 就是這麼控制的。
如果要使角度從0到π/ 2 弧度線性變化,請將 Animation 繫結到AnimationController:
Animation<double> angleAnimation = new Tween(begin: 0.0, end: pi/2).animate(_controller); 複製程式碼
當您開始動畫(透過_controller.forward())時,angleAnimation.value 將使用 _controller.value 來獲取 範圍[0.0; π/ 2] 中的值。
下圖顯示了這種線性變化(π/ 2 = 1.57)
使用Flutter預定義的曲線變化
Flutter 提供了一組預定義的 Curved 變化,如下:
要使用這些曲線效果:
Animation<double> angleAnimation = new Tween(begin: 0.0, end: pi/2).animate( new CurvedAnimation( parent: _controller, curve: Curves.ease, reverseCurve: Curves.easeOut )); 複製程式碼
這將產生值[0; π/ 2] 之間的值:
- 當正向播放動畫,數值從 0 到 π/2 ,會使用 Curves.ease 效果
- 當反向播放動畫,數值從 π/2 到 0,會使用 Curves.easeOut 效果
控制動畫
該AnimationController 類可以讓你透過 API 來控制動畫。(以下是最常用的API):
- _controller.forward({兩個區間的值})
要求控制器開始生成 lowerBound- > upperBound中的值
from 的可選引數可用於強制控制器從lowerBound之外的另一個值開始“ 計數 ”
- _controller.reverse({兩個區間的值})
要求控制器開始生成 upperBound- > lowerBound中的值
from的可選引數可用於強制控制器從“ upperBound ”之外的另一個值開始“ 計數 ”
- _controller.stop({bool cancelled:true})
停止執行動畫
- _controller.reset()
將動畫重置為從 LowerBound 開始
- _controller.animateTo(double target, { Duration duration, Curve curve: Curves.linear })
將動畫的當前值改變到目標值。
- _controller.repeat({double min,double max,Duration period})
開始以正向執行動畫,並在動畫完成後重新啟動動畫。如果定義了 min 或者 max ,將限制動畫的重複執行次數。
安全起見
由於動畫可能會意外停止(例如關閉螢幕),因此在使用以下API之一時,新增“ .orCancel ” 更為安全:
__controller.forward().orCancel; 複製程式碼
這個小技巧,可以保證,在 _controller 釋放之前,如果 Ticker 取消了,將不會導致異常。
場景的概念
官方文件中不存在“ 場景 ”一詞,但就我個人而言,我發現它更接近現實。我來解釋一下。
如我所說,一個 AnimationController 管理一個Animation。但是,我們可能將“ 動畫 ” 一詞理解為一系列需要依次播放或重疊播放的子動畫。將子動畫組合在一起,這就是我所說的“ 場景 ”。
考慮以下情況,其中動畫的整個持續時間為10秒,我們希望達到的效果是:
- 在開始的2秒內,有一個球從螢幕的左側移動到螢幕的中間
- 然後,同一個球需要3秒鐘才能從螢幕中心移動到螢幕頂部中心
- 最終,球需要5秒鐘才能消失。 正如您最可能已經想到的那樣,我們必須考慮3種不同的動畫:
////// Definition of the _controller with a whole duration of 10 seconds///AnimationController _controller = new AnimationController( duration: const Duration(seconds: 10), vsync: this);////// First animation that moves the ball from the left to the center///Animation<Offset> moveLeftToCenter = new Tween( begin: new Offset(0.0, screenHeight /2), end: new Offset(screenWidth /2, screenHeight /2) ).animate(_controller);////// Second animation that moves the ball from the center to the top///Animation<Offset> moveCenterToTop = new Tween( begin: new Offset(screenWidth /2, screenHeight /2), end: new Offset(screenWidth /2, 0.0) ).animate(_controller);////// Third animation that will be used to change the opacity of the ball to make it disappear///Animation<double> disappear = new Tween( begin: 1.0, end: 0.0).animate(_controller); 複製程式碼
現在的問題是,我們如何連結(或編排)子動畫?
Interval
組合動畫可以透過 Interval 這個類來實現。但是,那什麼是 Interval?
可能和我們腦子裡首先想到的不一樣, Interval 和時間沒有關係,而是一組值的範圍。
如果考慮使用 _controller,則必須記住,它會使值從 lowerBound 到 upperBound 變化。
通常,這兩個值基本定義為 lowerBound = 0.0 和 upperBound = 1.0,這使動畫計算更容易,因為[0.0-> 1.0]只是從0%到100%的變化。因此,如果一個場景的總持續時間為10秒,則最有可能在5秒後,相應的_controller.value將非常接近0.5(= 50%)。
如果將3個不同的動畫放在一個時間軸上,則可以獲得如下示意圖:
如果現在考慮值的間隔,則對於3個動畫中的每個動畫,我們將得到:
- moveLeftToCenter
持續時間:2秒,從0秒開始,以2秒結束=>範圍= [0; 2] =>百分比:從整個場景的0%到20%=> [0.0; 0.20]
- moveCenterToTop
持續時間:3秒,開始於2秒,結束於5秒=>範圍= [2; 5] =>百分比:從整個場景的20%到50%=> [0.20; 0.50]
- disappear
持續時間:5秒,開始於5秒,結束於10秒=>範圍= [5; 10] =>百分比:從整個場景的50%到100%=> [0.50; 1.0]
現在我們有了這些百分比,我們得到每個動畫的定義,如下:
////// Definition of the _controller with a whole duration of 10 seconds///AnimationController _controller = new AnimationController( duration: const Duration(seconds: 10), vsync: this);////// First animation that moves the ball from the left to the center///Animation<Offset> moveLeftToCenter = new Tween( begin: new Offset(0.0, screenHeight /2), end: new Offset(screenWidth /2, screenHeight /2) ).animate( new CurvedAnimation( parent: _controller, curve: new Interval( 0.0, 0.20, curve: Curves.linear, ), ), );////// Second animation that moves the ball from the center to the top///Animation<Offset> moveCenterToTop = new Tween( begin: new Offset(screenWidth /2, screenHeight /2), end: new Offset(screenWidth /2, 0.0) ).animate( new CurvedAnimation( parent: _controller, curve: new Interval( 0.20, 0.50, curve: Curves.linear, ), ), );////// Third animation that will be used to change the opacity of the ball to make it disappear///Animation<double> disappear = new Tween(begin: 1.0, end: 0.0) .animate( new CurvedAnimation( parent: _controller, curve: new Interval( 0.50, 1.0, curve: Curves.linear, ), ), ); 複製程式碼
這就是定義場景(或一系列動畫)所需的全部設定。當然,沒有什麼可以阻止您重疊子動畫…
響應動畫狀態
有時,獲取動畫(或場景)的狀態很方便。
動畫可能具有4種不同的狀態:
- dismissed:動畫在開始後停止(或尚未開始)
- forward:動畫從頭到尾執行
- reverse:動畫反向播放
- completed:動畫在播放後停止
要獲得此狀態,我們需要透過以下方式監聽動畫狀態的變化:
myAnimation.addStatusListener((AnimationStatus status){ switch(status){ case AnimationStatus.dismissed: ... break; case AnimationStatus.forward: ... break; case AnimationStatus.reverse: ... break; case AnimationStatus.completed: ... break; } }); 複製程式碼
狀態應用的典型示例就是狀態的切換。例如,動畫完成後,我們要反轉它,如:
myAnimation.addStatusListener((AnimationStatus status){ switch(status){ /// /// When the animation is at the beginning, we force the animation to play /// case AnimationStatus.dismissed: _controller.forward(); break; /// /// When the animation is at the end, we force the animation to reverse /// case AnimationStatus.completed: _controller.reverse(); break; } }); 複製程式碼
理論已經足夠了,現在我們開始實戰
我在文章開頭提到了一個動畫,現在我準備開始實現它,名字就叫“guillotine(斷頭臺)”
動畫分析及程式初始化
未來能夠實現“斬頭臺”效果,我們需要考慮一下幾個方面:
- 頁面內容本身
- 當我們點選選單圖示時,選單欄會旋轉
- 旋轉時,選單會覆蓋頁面內容並填充整個視口
- 一旦選單是完全可見,我們再次點選圖示,選單旋轉出來,以便回到原來的位置和尺寸
從這些觀察中,我們可以立即得出結論,我們沒有使用帶有AppBar的普通Scaffold(因為後者是固定的)。
我們需要使用 2 層 Stack:
- 頁面內容(下層)
- 選單(上層)
程式的基本框架基本出來了:
class MyPage extends StatefulWidget { @override _MyPageState createState() => new _MyPageState(); }class _MyPageState extends State<MyPage>{ @override Widget build(BuildContext context){ return SafeArea( top: false, bottom: false, child: new Container( child: new Stack( alignment: Alignment.topLeft, children: <Widget>[ new Page(), new GuillotineMenu(), ], ), ), ); } }class Page extends StatelessWidget { @override Widget build(BuildContext context){ return new Container( padding: const EdgeInsets.only(top: 90.0), color: Color(0xff222222), ); } }class GuillotineMenu extends StatefulWidget { @override _GuillotineMenuState createState() => new _GuillotineMenuState(); }class _GuillotineMenuState extends State<GuillotineMenu> { @overrride Widget build(BuildContext context){ return new Container( color: Color(0xff333333), ); } } 複製程式碼
這些程式碼的執行結果為黑屏,僅顯示覆蓋整個視口的GuillotineMenu。
選單效果分析
如果你看了上面的示例,可以看到選單完全開啟時,它完全覆蓋了視口。開啟後,只有可見的AppBa。
而如果最初旋轉 GuillotineMenu 並在按下選單按鈕時將其旋轉π/ 2,將會怎樣呢,如下圖所示這樣嗎?
然後,我們可以按以下方式重寫_GuillotineMenuState類:(這裡不在解釋如何佈局,這不是重點)
class _GuillotineMenuState extends State<GuillotineMenu> { double rotationAngle = 0.0; @override Widget build(BuildContext context){ MediaQueryData mediaQueryData = MediaQuery.of(context); double screenWidth = mediaQueryData.size.width; double screenHeight = mediaQueryData.size.height; return new Transform.rotate( angle: rotationAngle, origin: new Offset(24.0, 56.0), alignment: Alignment.topLeft, child: Material( color: Colors.transparent, child: Container( width: screenWidth, height: screenHeight, color: Color(0xFF333333), child: new Stack( children: <Widget>[ _buildMenuTitle(), _buildMenuIcon(), _buildMenuContent(), ], ), ), ), ); } /// /// Menu Title /// Widget _buildMenuTitle(){ return new Positioned( top: 32.0, left: 40.0, width: screenWidth, height: 24.0, child: new Transform.rotate( alignment: Alignment.topLeft, origin: Offset.zero, angle: pi / 2.0, child: new Center( child: new Container( width: double.infinity, height: double.infinity, child: new Opacity( opacity: 1.0, child: new Text('ACTIVITY', textAlign: TextAlign.center, style: new TextStyle( color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold, letterSpacing: 2.0, )), ), ), )), ); } /// /// Menu Icon /// Widget _buildMenuIcon(){ return new Positioned( top: 32.0, left: 4.0, child: new IconButton( icon: const Icon( Icons.menu, color: Colors.white, ), onPressed: (){}, ), ); } /// /// Menu content /// Widget _buildMenuContent(){ final List<Map> _menus = <Map>[ { "icon": Icons.person, "title": "profile", "color": Colors.white, }, { "icon": Icons.view_agenda, "title": "feed", "color": Colors.white, }, { "icon": Icons.swap_calls, "title": "activity", "color": Colors.cyan, }, { "icon": Icons.settings, "title": "settings", "color": Colors.white, }, ]; return new Padding( padding: const EdgeInsets.only(left: 64.0, top: 96.0), child: new Container( width: double.infinity, height: double.infinity, child: new Column( mainAxisAlignment: MainAxisAlignment.start, children: _menus.map((menuItem) { return new ListTile( leading: new Icon( menuItem["icon"], color: menuItem["color"], ), title: new Text( menuItem["title"], style: new TextStyle( color: menuItem["color"], fontSize: 24.0), ), ); }).toList(), ), ), ); } } 複製程式碼
- 10-13行
這些線定義了斷頭臺選單圍繞旋轉中心(選單圖示的位置)的旋轉
現在,此程式碼的結果將顯示一個未旋轉的選單螢幕(因為rotationAngle = 0.0),該螢幕顯示了垂直的標題。
接下來使 menu 顯示動畫
如果更新 rotationAngle 的值(在-π/ 2和0之間),您將看到選單旋轉了相應的角度。
如前所述,我們需要
- 一個SingleTickerProviderStateMixin,因為我們只有1個場景
- 一個AnimationController
- 一個動畫 有一個角度變化
程式碼如下所示:
class _GuillotineMenuState extends State<GuillotineMenu> with SingleTickerProviderStateMixin { AnimationController animationControllerMenu; Animation<double> animationMenu; /// /// Menu Icon, onPress() handling /// _handleMenuOpenClose(){ animationControllerMenu.forward(); } @override void initState(){ super.initState(); /// /// Initialization of the animation controller /// animationControllerMenu = new AnimationController( duration: const Duration(milliseconds: 1000), vsync: this )..addListener((){ setState((){}); }); /// /// Initialization of the menu appearance animation /// _rotationAnimation = new Tween( begin: -pi/2.0, end: 0.0 ).animate(animationControllerMenu); } @override void dispose(){ animationControllerMenu.dispose(); super.dispose(); } @override Widget build(BuildContext context){ MediaQueryData mediaQueryData = MediaQuery.of(context); double screenWidth = mediaQueryData.size.width; double screenHeight = mediaQueryData.size.height; double angle = animationMenu.value; return new Transform.rotate( angle: angle, origin: new Offset(24.0, 56.0), alignment: Alignment.topLeft, child: Material( color: Colors.transparent, child: Container( width: screenWidth, height: screenHeight, color: Color(0xFF333333), child: new Stack( children: <Widget>[ _buildMenuTitle(), _buildMenuIcon(), _buildMenuContent(), ], ), ), ), ); } ... /// /// Menu Icon /// Widget _buildMenuIcon(){ return new Positioned( top: 32.0, left: 4.0, child: new IconButton( icon: const Icon( Icons.menu, color: Colors.white, ), onPressed: _handleMenuOpenClose, ), ); } ... } 複製程式碼
現在,當我們按下選單按鈕時,選單會開啟,但再次按下按鈕時選單不會關閉。這是 AnimationStatus 要完成的事情。
讓我們新增一個監聽器,並基於 AnimationStatus 決定是向前還是向後執行動畫。
////// Menu animation status///enum _GuillotineAnimationStatus { closed, open, animating }class _GuillotineMenuState extends State<GuillotineMenu> with SingleTickerProviderStateMixin { AnimationController animationControllerMenu; Animation<double> animationMenu; _GuillotineAnimationStatus menuAnimationStatus = _GuillotineAnimationStatus.closed; _handleMenuOpenClose(){ if (menuAnimationStatus == _GuillotineAnimationStatus.closed){ animationControllerMenu.forward().orCancel; } else if (menuAnimationStatus == _GuillotineAnimationStatus.open) { animationControllerMenu.reverse().orCancel; } } @override void initState(){ super.initState(); /// /// Initialization of the animation controller /// animationControllerMenu = new AnimationController( duration: const Duration(milliseconds: 1000), vsync: this )..addListener((){ setState((){}); })..addStatusListener((AnimationStatus status) { if (status == AnimationStatus.completed) { /// /// When the animation is at the end, the menu is open /// menuAnimationStatus = _GuillotineAnimationStatus.open; } else if (status == AnimationStatus.dismissed) { /// /// When the animation is at the beginning, the menu is closed /// menuAnimationStatus = _GuillotineAnimationStatus.closed; } else { /// /// Otherwise the animation is running /// menuAnimationStatus = _GuillotineAnimationStatus.animating; } }); ... } ... } 複製程式碼
現在選單可以按預期方式開啟或關閉,但是前面的演示向我們展示了一個開啟/關閉的動畫,該懂哈不是線性的,看起來有一個反覆的回彈效果。接下來讓我們新增此效果。
為此,我將選擇以下2種效果:
- 選單開啟時用 bounceOut
- 選單關閉時用 bouncIn
class _GuillotineMenuState extends State<GuillotineMenu> with SingleTickerProviderStateMixin { ... @override void initState(){ ... /// /// Initialization of the menu appearance animation /// animationMenu = new Tween( begin: -pi / 2.0, end: 0.0 ).animate(new CurvedAnimation( parent: animationControllerMenu, curve: Curves.bounceOut, reverseCurve: Curves.bounceIn, )); } ... } 複製程式碼
在此實現中仍有一些細節沒有實現:開啟選單時標題消失,而關閉選單時顯示標題。這是一個面朝上/朝外的效果,也要作為動畫處理。讓我們新增它。
class _GuillotineMenuState extends State<GuillotineMenu> with SingleTickerProviderStateMixin { AnimationController animationControllerMenu; Animation<double> animationMenu; Animation<double> animationTitleFadeInOut; _GuillotineAnimationStatus menuAnimationStatus; ... @override void initState(){ ... /// /// Initialization of the menu title fade out/in animation /// animationTitleFadeInOut = new Tween( begin: 1.0, end: 0.0 ).animate(new CurvedAnimation( parent: animationControllerMenu, curve: new Interval( 0.0, 0.5, curve: Curves.ease, ), )); } ... /// /// Menu Title /// Widget _buildMenuTitle(){ return new Positioned( top: 32.0, left: 40.0, width: screenWidth, height: 24.0, child: new Transform.rotate( alignment: Alignment.topLeft, origin: Offset.zero, angle: pi / 2.0, child: new Center( child: new Container( width: double.infinity, height: double.infinity, child: new Opacity( opacity: animationTitleFadeInOut.value, child: new Text('ACTIVITY', textAlign: TextAlign.center, style: new TextStyle( color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold, letterSpacing: 2.0, )), ), ), )), ); } ... } 複製程式碼
最終的效果基本如下:
本文的完整原始碼可在 GitHub上找到。
結論
如您所見,構建動畫非常簡單,甚至複雜的動畫也是如此。
我希望這篇較長的文章能夠成功的解釋 Flutter 中的動畫。
當然,你想多多學習flutter的話,可以關注我,私信我
flutter
送你學習影片
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2670818/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Flutter 動畫簡易教程Flutter動畫
- [翻譯] Flutter 中的動畫 - 簡易指南 - 教程Flutter動畫
- 使用canvas實現簡單動畫Canvas動畫
- 讓動畫變得更簡單之FLIP技術動畫
- Flutter Animation(1)動畫的簡單使用Flutter動畫
- Unity Shader- UV動畫原理及簡易實現Unity動畫
- 最簡單的Flutter路由教程——跳轉、動畫與傳參Flutter路由動畫
- Tvori推出2.0版本,讓VR動畫製作更簡單VR動畫
- 前端動畫效果實現的簡單比較前端動畫
- Flutter實現動畫Flutter動畫
- flutter 簡單實現瀏覽器H5粒子動畫Flutter瀏覽器H5動畫
- JavaScript 簡單動畫效果JavaScript動畫
- canvas簡單的畫布動畫 - KaiqisanCanvas動畫AI
- Flutter動畫:用Flutter來實現一個拍手動畫Flutter動畫
- 簡單的動畫 (numpy & PySimpleGUI)動畫GUI
- JavaScript簡單的動畫效果JavaScript動畫
- Flutter 實現背景 Parallax 動畫Flutter動畫
- CSS3建立簡單的網頁動畫–實現彈跳球動CSSS3網頁動畫
- canvas簡單動畫案例(圓圈閃爍漸變動畫)Canvas動畫
- 簡單CSS實現閃爍動畫(+1白話講解)CSS動畫
- 分享一個簡單的畫刷動畫效果:?️BrushEffect動畫
- Flutter動畫實現粒子漂浮效果Flutter動畫
- Flutter動畫實現原理淺析Flutter動畫
- Flutter實戰之動畫實現篇Flutter動畫
- 視覺化學習:如何生成簡單動畫讓圖形動起來視覺化動畫
- flutter簡易教程Flutter
- 記錄---前端實現畫中畫超簡單,讓網頁飛出瀏覽器前端網頁瀏覽器
- Flutter動畫 5 - Flutter內建動畫元件Flutter動畫元件
- Canvas 實現畫中畫動畫效果–網易娛樂年度盤點H5動畫解密Canvas動畫H5解密
- Canvas 實現畫中畫動畫效果--網易娛樂年度盤點H5動畫解密Canvas動畫H5解密
- 基於 HTML5 實現的簡單雲動畫和景物描述HTML動畫
- Flutter 類抽屜效果動畫實現。Flutter動畫
- 《Flutter 動畫系列》組合動畫Flutter動畫
- Flutter 動畫Flutter動畫
- 【Flutter 實戰】動畫核心Flutter動畫
- 用PPT製作一個簡易的演示動畫動畫
- Flutter抖動動畫、顫抖動畫、Flutter文字抖動效果Flutter動畫
- Flutter滾動動畫Flutter動畫