一,概述
Flutter
動畫庫的核心類是Animation
對象,它生成指導動畫的值,Animation
對象指導動畫的當前狀態(例如,是開始、停止還是向前或者向後移動),但它不知道屏幕上顯示的內容。動畫類型分爲兩類:
- 補簡動畫(Tween),定義了開始點和結束點、時間線以及定義轉換時間和速度的曲線。然後由框架計算如何從開始點過渡到結束點。Tween是一個無狀態(stateless)對象,需要begin和end值。Tween的唯一職責就是定義從輸入範圍到輸出範圍的映射。輸入範圍通常爲0.0到1.0,但這不是必須的。
- 基於物理動畫,運動被模擬與真實世界行爲相似,例如,當你擲球時,它何處落地,取決於拋球速度有多快、球有多重、距離地面有多遠。類似地,將連接在彈簧上的球落下(並彈起)與連接到繩子的球放下的方式也是不同。
在Flutter
中的動畫系統基於Animation
對象的。widget
可以在build
函數中讀取Animation
對象的當前值,並且可以監聽動畫的狀態改變。
二,Flutter動畫介紹
-
Animation
Animation 是 Flutter 動畫庫中的核心類,它會插入指導動畫生成的值。 Animation 對象知道一個動畫當前的狀態(例如開始、 停止、 播放、 回放), 但它不知道屏幕上繪製的是什麼, 因爲 Animation 對象只是提供一個值表示當前需要展示的動畫, UI 如何繪製出圖形完全取決於 UI 自身如何在渲染和 build() 方法裏處理這個值, 當然也可以不做處理。 Animation<double>
是一個比較常用的Animation類, 泛型也可以支持其它的類型,比如: Animation<Color>
或 Animation<Size>
。 Animation 對象就是會在一段時間內依次生成一個區間之間值的類, 它的輸出可以是線性的、曲線的、一個步進函數或者任何其他可以設計的映射 比如:CurvedAnimation。
-
AnimationController
AnimationController 是一個動畫控制器, 它控制動畫的播放狀態, 如例子裏面的: controller.forward()
就是控制動畫"向前"播放。 所以構建 AnimationController 對象之後動畫並沒有立刻開始執行。 在默認情況下, AnimationController 會在給定的時間內線性地生成從 0.0 到 1.0 之間的數字。 AnimationController 是一種特殊的 Animation 對象了, 它父類其實是一個 Animation<double>
, 當硬件準備好需要一個新的幀的時候它就會產生一個新的值。 由於 AnimationController 派生自 Animation <double>
,因此可以在需要 Animation 對象的任何地方使用它。 但是 AnimationController 還有其他的方法來控制動畫的播放, 例如前面提到的 .forward()
方法啓動動畫。
AnimationController 生成的數字(默認是從 0.0 到 1.0) 是和屏幕刷新有關, 前面也提到它會在硬件需要一個新幀的時候產生新值。 因爲屏幕一般都是 60 幀/秒, 所以它也通常一秒內生成 60 個數字。 每個數字生成之後, 每個 Animation 對象都會調用綁定的監聽器對象。
-
Tween
Tween 本身表示的就是一個 Animation 對象的取值範圍, 只需要設置開始和結束的邊界值(值也支持泛型)。 它唯一的工作就是定義輸入範圍到輸出範圍的映射, 輸入一般是 AnimationController 給出的值 0.0~1.0。 看下面的例子, 我們就能知道 animation 的 value 是怎麼樣通過 AnimationController 生成的值映射到 Tween 定義的取值範圍裏面的。
Tween.animation
通過傳入 aniamtionController 獲得一個_AnimatedEvaluation 類型的 animation 對象(基類爲 Animation), 並且將 aniamtionController 和 Tween 對象傳入了 _AnimatedEvaluation 對象。animation = new Tween(begin: 0.0, end: 300.0).animate(controller) ... Animation<T> animate(Animation<double> parent) { return _AnimatedEvaluation<T>(parent, this); }
-
animation.value
方法即是調用_evaluatable.evaluate(parent)
方法, 而 _evaluatable 和 parent 分別爲 Tween 對象和 AnimationController 對象。T get value => _evaluatable.evaluate(parent); .... class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> { _AnimatedEvaluation(this.parent, this._evaluatable); ....
- 這裏的 animation 其實就是前面的 AnimationController 對象, transform 方法裏面的
animation.value
則就是 AnimationController 線性生成的 0.0~1.0 直接的值。 在 lerp 方法裏面我們可以看到這個 0.0~1.0 的值被映射到了 begin 和 end 範圍內了。T evaluate(Animation<double> animation) => transform(animation.value); T transform(double t) { if (t == 0.0) return begin; if (t == 1.0) return end; return lerp(t); } T lerp(double t) { assert(begin != null); assert(end != null); return begin + (end - begin) * t; }
-
Flutter 的"時鐘"
那麼 Flutter 是怎麼樣讓這個動畫在規定時間不斷地繪製的呢?
- 首先看 Widget 引入的 SingleTickerProviderStateMixin 類。SingleTickerProviderStateMixin 是以 with 關鍵字引入的, 這是 dart 語言的 mixin 特性, 可以理解成"繼承", 所以 widget 相當於是繼承了SingleTickerProviderStateMixin。 所以在 AnimationController 對象的構造方法參數
vsync: this
, 我們看到了這個類的使用。 從 "vsync" 參數名意爲"垂直幀同步"可以看出, 這個是繪製動畫幀的"節奏器"。AnimationController({ double value, this.duration, this.debugLabel, this.lowerBound = 0.0, this.upperBound = 1.0, this.animationBehavior = AnimationBehavior.normal, @required TickerProvider vsync, }) : assert(lowerBound != null), assert(upperBound != null), assert(upperBound >= lowerBound), assert(vsync != null), _direction = _AnimationDirection.forward { _ticker = vsync.createTicker(_tick); _internalSetValue(value ?? lowerBound); }
- 在 AnimationController 的構造方法中, SingleTickerProviderStateMixin 的父類 TickerProvider 會創建一個 Ticker, 並將_tick(TickerCallback 類型)回調方法綁定到了 這個 Ticker, 這樣 AnimationController 就將回調方法 _tick 和 Ticker 綁定了。
@protected void scheduleTick({ bool rescheduling = false }) { assert(!scheduled); assert(shouldScheduleTick); _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling); }
-
而 Ticker 會在 start 函數內將_tick 被綁定到 SchedulerBinding 的幀回調方法內。 返回的_animationId 是 SchedulerBinding 給定的下一個動作回調的 ID, 可以根據_animationId 來取消 SchedulerBinding 上綁定的回調。
SchedulerBinding 則是在構造方法中將自己的 _handleBeginFrame 函數和 window 的 onBeginFrame 綁定了回調。 這個回調會在屏幕需要準備顯示幀之前回調。
再回到 AnimationController 看它是如何控制 Animation 的值的。
void _tick(Duration elapsed) { _lastElapsedDuration = elapsed; final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond; assert(elapsedInSeconds >= 0.0); _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound); if (_simulation.isDone(elapsedInSeconds)) { _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.completed : AnimationStatus.dismissed; stop(canceled: false); } notifyListeners(); _checkStatusChanged(); }
-
在 AnimationController 的回調當中, 會有一個 Simulation 根據動畫運行了的時間(elapsed) 來計算當前的的_value 值, 而且這個值還需要處於 Animation 設置的區間之內。 除了計算_value 值之外, 該方法還會更新 Animation Status 的狀態, 判斷是否動畫已經結束。 最後通過 notifyListeners 和_checkStatusChanged 方法通知給監聽器 value 和 AnimationStatus 的變化。 監聽 AnimationStatus 值的變化有一個專門的註冊方法 addStatusListener。
通過監聽 AnimationStatus, 在動畫開始或者結束的時候反轉動畫, 就達到了動畫循環播放的效果。
... animation.addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); controller.forward(); ...
-
回顧一下這個動畫繪製調用的順序就是, window 調用 SchedulerBinding 的_handleBeginFrame 方法, SchedulerBinding 調用 Ticker 的_tick 方法, Ticker 調用 AnimationController 的_tick 的方法, AnimationContoller 通知監聽器, 而監聽器調用 widget 的 setStatus 方法來調用 build 更新, 最後 build 使用了 Animation 對象當前的值來繪製動畫幀。
看到這裏會有一個疑惑, 爲什麼監聽器是註冊在 Animation 上的, 監聽通知反而由 AnimationController 發送?
還是看源碼吧。
Animation<T> animate(Animation<double> parent) { return _AnimatedEvaluation<T>(parent, this); } class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> { _AnimatedEvaluation(this.parent, this._evaluatable); } mixin AnimationWithParentMixin<T> { Animation<T> get parent; /// Listeners can be removed with [removeListener]. void addListener(VoidCallback listener) => parent.addListener(listener); }
- 首先 Animation 對象是由 Tween 的 animate 方法生成的, 它傳入了 AnimationController(Animation 的子類) 參數 作爲 parent 參數, 然後我們發現返回的
_AnimatedEvaluation<T>
泛型對象 使用 mixin "繼承" 了AnimationWithParentMixin<double>
, 最後我們看到 Animation 作爲 AnimationWithParentMixin 的"子類"實現的 addListener 方法其實是將監聽器註冊到 parent 對象上了, 也就是 AnimationController。
三,動畫示例
- 示例一
import 'package:flutter/material.dart'; import 'package:flutter/animation.dart'; void main() { //運行程序 runApp(LogoApp()); } class LogoApp extends StatefulWidget{ @override State<StatefulWidget> createState(){ return new _LogoAppState(); } } //logo Widget ImageLogo = new Image( image: new AssetImage('images/logo.jpg'), ); //with 是dart的關鍵字,混入的意思,將一個或者多個類的功能添加到自己的類無需繼承這些類 //避免多重繼承問題 //SingleTickerProviderStateMixin 初始化 animation 和 Controller的時候需要一個TickerProvider類型的參數Vsync //所依混入TickerProvider的子類 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{ //動畫的狀態,如動畫開啓,停止,前進,後退等 Animation<double> animation; //管理者animation對象 AnimationController controller; @override void initState() { // TODO: implement initState super.initState(); //創建AnimationController //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫( //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。 controller = new AnimationController( //時間是3000毫秒 duration: const Duration( milliseconds: 3000 ), //vsync 在此處忽略不必要的情況 vsync: this, ); //補間動畫 animation = new Tween( //開始的值是0 begin: 0.0, //結束的值是200 end : 200.0, ).animate(controller)//添加監聽器 ..addListener((){ //動畫值在發生變化時就會調用 setState(() { }); }); //只顯示動畫一次 controller.forward(); } @override Widget build(BuildContext context){ return new MaterialApp( theme: ThemeData( primarySwatch: Colors.red ), home: new Scaffold( appBar: new AppBar( title: Text("動畫demo"), ), body:new Center( child: new Container( //寬和高都是根據animation的值來變化 height: animation.value, width: animation.value, child: ImageLogo, ), ), ), ); } @override void dispose() { // TODO: implement dispose super.dispose(); //資源釋放 controller.dispose(); } }
上面實現了圖像在3000毫秒間從寬高是0變化到寬高是200,主要分爲六部
- 混入
SingleTickerProviderStateMixin
,爲了傳入vsync
對象 - 初始化
AnimationController
對象 - 初始化
Animation
對象,並關聯AnimationController
對象 - 調用
AnimationController
的forward
開啓動畫 widget
根據Animation
的value
值來設置寬高- 在
widget
的dispose()
方法中調用釋放資源
注意:上面創建Tween
用了Dart
語法的級聯符號animation = tween.animate(controller) ..addListener(() { setState(() { // the animation object’s value is the changed state }); });
等價於下面代碼:
animation = tween.animate(controller); animation.addListener(() { setState(() { // the animation object’s value is the changed state }); });
1.1.AnimatedWidget簡化
使用
AnimatedWidget
對動畫進行簡化,使用AnimatedWidget
創建一個可重用動畫的widget
,而不是用addListener()
和setState()
來給widget
添加動畫。AnimatedWidget
類允許從setState()
調用中的動畫代碼中分離出widget
代碼。AnimatedWidget
不需要維護一個State
對象了來保存動畫。import 'package:flutter/material.dart'; import 'package:flutter/animation.dart'; void main() { //運行程序 runApp(LogoApp()); } class LogoApp extends StatefulWidget{ @override State<StatefulWidget> createState(){ return new _LogoAppState(); } } //logo Widget ImageLogo = new Image( image: new AssetImage('images/logo.jpg'), ); //抽象出來 class AnimatedLogo extends AnimatedWidget{ AnimatedLogo({ Key key,Animation<double> animation }):super(key:key,listenable:animation); @override Widget build(BuildContext context){ final Animation<double> animation = listenable; return new MaterialApp( theme: ThemeData( primarySwatch: Colors.red ), home: new Scaffold( appBar: new AppBar( title: Text("動畫demo"), ), body:new Center( child: new Container( //寬和高都是根據animation的值來變化 height: animation.value, width: animation.value, child: ImageLogo, ), ), ), ); } } //with 是dart的關鍵字,混入的意思,將一個或者多個類的功能添加到自己的類無需繼承這些類 //避免多重繼承問題 //SingleTickerProviderStateMixin 初始化 animation 和 Controller的時候需要一個TickerProvider類型的參數Vsync //所依混入TickerProvider的子類 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{ //動畫的狀態,如動畫開啓,停止,前進,後退等 Animation<double> animation; //管理者animation對象 AnimationController controller; @override void initState() { // TODO: implement initState super.initState(); //創建AnimationController //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫( //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。 controller = new AnimationController( //時間是3000毫秒 duration: const Duration( milliseconds: 3000 ), //vsync 在此處忽略不必要的情況 vsync: this, ); //補間動畫 animation = new Tween( //開始的值是0 begin: 0.0, //結束的值是200 end : 200.0, ).animate(controller);//添加監聽器 //只顯示動畫一次 controller.forward(); } @override Widget build(BuildContext context){ return AnimatedLogo(animation: animation); } @override void dispose() { // TODO: implement dispose super.dispose(); //資源釋放 controller.dispose(); } }
可以發現
AnimatedWidget
中會自動調用addListener
和setState()
,_LogoAppState
將Animation
對象傳遞給基類並用animation.value
設置Image寬高。1.2.監視動畫
在平時開發,我們知道,很多時候都需要監聽動畫的狀態,好像完成、前進、倒退等。在
Flutter
中可以通過addStatusListener()
來得到這個通知,以下代碼添加了動畫狀態//補間動畫 animation = new Tween( //開始的值是0 begin: 0.0, //結束的值是200 end : 200.0, ).animate(controller) //添加動畫狀態 ..addStatusListener((state){ return print('$state'); });//添加監聽器
運行代碼會輸出下面結果:
I/flutter (16745): AnimationStatus.forward //動畫開始 Syncing files to device KNT AL10... I/zygote64(16745): Do partial code cache collection, code=30KB, data=25KB I/zygote64(16745): After code cache collection, code=30KB, data=25KB I/zygote64(16745): Increasing code cache capacity to 128KB I/flutter (16745): AnimationStatus.completed//動畫完成
下面那就運用
addStatusListener()
在開始或結束反轉動畫。那就產生循環效果://補間動畫 animation = new Tween( //開始的值是0 begin: 0.0, //結束的值是200 end : 200.0, ).animate(controller) //添加動畫狀態 ..addStatusListener((state){ //如果動畫完成了 if(state == AnimationStatus.completed){ //開始反向這動畫 controller.reverse(); } else if(state == AnimationStatus.dismissed){ //開始向前運行着動畫 controller.forward(); } });//添加監聽器
效果如下:
1.3.用AnimatedBuilder重構
上面的代碼存在一個問題:更改動畫需要更改顯示
Image
的widget
,更好的解決方案是將職責分離:- 顯示圖像
- 定義
Animation
對象 - 渲染過渡效果 這時候可以藉助
AnimatedBuilder
類完成此分離。AnimatedBuilder
是渲染樹中的一個獨立的類,與AnimatedWidget
類似,AnimatedBuilder
自動監聽來自Animation
對象的通知,並根據需要將該控件樹標記爲髒(dirty),因此不需要手動調用addListener()
//AnimatedBuilder class GrowTransition extends StatelessWidget{ final Widget child; final Animation<double> animation; GrowTransition({this.child,this.animation}); @override Widget build(BuildContext context){ return new MaterialApp( theme: ThemeData( primarySwatch: Colors.red ), home: new Scaffold( appBar: new AppBar( title: Text("動畫demo"), ), body:new Center( child: new AnimatedBuilder( animation: animation, builder: (BuildContext context,Widget child){ return new Container( //寬和高都是根據animation的值來變化 height: animation.value, width: animation.value, child: child, ); }, child: child, ), ), ), ); } class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{ //動畫的狀態,如動畫開啓,停止,前進,後退等 Animation animation; //管理者animation對象 AnimationController controller; @override void initState() { // TODO: implement initState super.initState(); //創建AnimationController //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫( //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。 controller = new AnimationController( //時間是3000毫秒 duration: const Duration( milliseconds: 3000 ), //vsync 在此處忽略不必要的情況 vsync: this, ); final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn); //補間動畫 animation = new Tween( //開始的值是0 begin: 0.0, //結束的值是200 end : 200.0, ).animate(curve) // //添加動畫狀態 ..addStatusListener((state){ //如果動畫完成了 if(state == AnimationStatus.completed){ //開始反向這動畫 controller.reverse(); } else if(state == AnimationStatus.dismissed){ //開始向前運行着動畫 controller.forward(); } });//添加監聽器 //只顯示動畫一次 controller.forward(); } @override Widget build(BuildContext context){ //return AnimatedLogo(animation: animation); return new GrowTransition(child:ImageLogo,animation: animation); } @override void dispose() { // TODO: implement dispose super.dispose(); //資源釋放 controller.dispose(); } }
上面代碼有一個迷惑的問題是,
child
看起來好像是指定了兩次,但實際發生的事情是,將外部引用的child
傳遞給AnimatedBuilder
,AnimatedBuilder
將其傳遞給匿名構造器,然後將該對象用作其子對象。最終的結果是AnimatedBuilder
插入到渲染樹中的兩個Widget
之間。最後,在initState()
方法創建一個AnimationController
和一個Tween
,然後通過animate()
綁定,在build
方法中,返回帶有一個Image
爲子對象的GrowTransition
對象和一個用於驅動過渡的動畫對象。如果只是想把可複用的動畫定義成一個widget
,那就用AnimatedWidget
。1.4.並行動畫
很多時候,一個動畫需要兩種或者兩種以上的動畫,在
Flutter
也是可以實現的,每一個Tween
管理動畫的一種效果,如:final AnimationController controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); final Animation<double> sizeAnimation = new Tween(begin: 0.0, end: 300.0).animate(controller); final Animation<double> opacityAnimation = new Tween(begin: 0.1, end: 1.0).animate(controller);
可以通過
sizeAnimation.Value
來獲取大小,通過opacityAnimation.value
來獲取不透明度,但AnimatedWidget
的構造函數只能接受一個動畫對象,解決這個問題,需要動畫的widget
創建了自己的Tween
對象,上代碼://AnimatedBuilder class GrowTransition extends StatelessWidget { final Widget child; final Animation<double> animation; GrowTransition({this.child, this.animation}); static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0); static final _sizeTween = new Tween<double>(begin: 0.0, end: 200.0); @override Widget build(BuildContext context) { return new MaterialApp( theme: ThemeData(primarySwatch: Colors.red), home: new Scaffold( appBar: new AppBar( title: Text("動畫demo"), ), body: new Center( child: new AnimatedBuilder( animation: animation, builder: (BuildContext context, Widget child) { return new Opacity( opacity: _opacityTween.evaluate(animation), child: new Container( //寬和高都是根據animation的值來變化 height: _sizeTween.evaluate(animation), width: _sizeTween.evaluate(animation), child: child, ), ); }, child: child, ), ), ), ); } } class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin { //動畫的狀態,如動畫開啓,停止,前進,後退等 Animation<double> animation; //管理者animation對象 AnimationController controller; @override void initState() { // TODO: implement initState super.initState(); //創建AnimationController //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫( //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。 controller = new AnimationController( //時間是3000毫秒 duration: const Duration(milliseconds: 3000), //vsync 在此處忽略不必要的情況 vsync: this, ); //新增 animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn) ..addStatusListener((state) { //如果動畫完成了 if (state == AnimationStatus.completed) { //開始反向這動畫 controller.reverse(); } else if (state == AnimationStatus.dismissed) { //開始向前運行着動畫 controller.forward(); } }); //添加監聽器 //只顯示動畫一次 controller.forward(); } @override Widget build(BuildContext context) { return new GrowTransition(child:ImageLogo,animation: animation); } @override void dispose() { // TODO: implement dispose super.dispose(); //資源釋放 controller.dispose(); } }
可以看到在
GrowTransition
定義兩個Tween
動畫,並且加了不透明Opacity
widget,最後在initState
方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
,最後的動畫效果:注意:可以通過改變
Curves.easeIn
值來實現非線性運動效果。 - 混入
-
2.自定義動畫
示例2:
2.1.自定義小球
class _bollView extends CustomPainter{ //顏色 Color color; //數量 int count; //集合放動畫 List<Animation<double>> ListAnimators; _bollView({this.color,this.count,this.ListAnimators}); @override void paint(Canvas canvas,Size size){ //繪製流程 double boll_radius = (size.width - 15) / 8; Paint paint = new Paint(); paint.color = color; paint.style = PaintingStyle.fill; //因爲這個wiaget是80 球和球之間相隔5 for(int i = 0; i < count;i++){ double value = ListAnimators[i].value; //確定圓心 半徑 畫筆 //第一個球 r //第二個球 5 + 3r //第三個球 15 + 5r //第四個球 30 + 7r //半徑也是隨着動畫值改變 canvas.drawCircle(new Offset((i+1) * boll_radius + i * boll_radius + i * 5,size.height / 2), boll_radius * (value > 1 ? (2 - value) : value), paint); } } //刷新是否重繪 @override bool shouldRepaint(CustomPainter oldDelegate){ return oldDelegate != this; } }
2.2.配置小球屬性
class MyBalls extends StatefulWidget{ Size size; Color color; int count; int seconds; //默認四個小球 紅色 MyBalls({this.size,this.seconds : 400,this.color :Colors.redAccent,this.count : 4}); @override State<StatefulWidget> createState(){ return MyBallsState(); } }
2.3.創建動畫
//繼承TickerProviderStateMixin,提供Ticker對象 class MyBallsState extends State<MyBalls> with TickerProviderStateMixin { //動畫集合 List<Animation<double>>animatios = []; //控制器集合 List<AnimationController> animationControllers = []; //顏色 Animation<Color> colors; @override void initState(){ super.initState(); for(int i = 0;i < widget.count;i++){ //創建動畫控制器 AnimationController animationController = new AnimationController( vsync: this, duration: Duration( milliseconds: widget.count * widget.seconds )); //添加到控制器集合 animationControllers.add(animationController); //顏色隨機 colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController); //創建動畫 每個動畫都要綁定控制器 Animation<double> animation = new Tween(begin: 0.1,end:1.9).animate(animationController); animatios.add(animation); } animatios[0].addListener((){ //刷新 setState(() { }); }); //延遲執行 var delay = (widget.seconds ~/ (2 * animatios.length - 2)); for(int i = 0;i < animatios.length;i++){ Future.delayed(Duration(milliseconds: delay * i),(){ animationControllers[i] ..repeat().orCancel; }); } } @override Widget build(BuildContext context){ return new CustomPaint( //自定義畫筆 painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios), size: widget.size, ); } //釋放資源 @override void dispose(){ super.dispose(); animatios[0].removeListener((){ setState(() { }); }); animationControllers[0].dispose(); } }
2.4.調用
class Ball extends StatelessWidget{ @override Widget build(BuildContext context){ return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Animation demo'), ), body: Center( child: MyBalls(size: new Size(80.0,20.0)), ), ), ); } }
四,總結
本篇文章從簡單的例子出發, 並且結合了源碼, 分析了 Flutter 動畫實現的原理。Flutter 以硬件設備刷新爲驅動, 驅使 widget 依據給定的值生成新動畫幀, 從而實現了動畫效果。
鏈接:
1. https://juejin.im/post/5cdbbc01f265da037b6134d9
2.https://juejin.im/post/5c617e34f265da2d90581613