老孟導讀:動畫系統是任何一個UI框架的核心功能,也是開發者學習一個UI框架的重中之重,同時也是比較難掌握的一部分,下面咱們就一層一層的揭開 Flutter 動畫的面紗。git
任何程序的動畫原理都是同樣的,即:視覺暫留,視覺暫留又叫視覺暫停,人眼在觀察景物時,光信號傳入大腦神經,需通過一段短暫的時間,光的做用結束後,視覺形象並不當即消失,這種殘留的視覺稱「後像」,視覺的這一現象則被稱爲「視覺暫留」。微信
人眼能保留0.1-0.4秒左右的圖像,因此在 1 秒內看到連續的25張圖像,人就會感到畫面流暢,而 1 秒內看到連續的多少張圖像稱爲 幀率,即 FPS,理論上 達到 24 FPS 畫面比較流暢,而Flutter,理論上能夠達到 60 FPS。框架
介紹完了動畫系統的基本原理,實現一個藍色盒子大小從 100 變爲 200動畫效果:ide
class AnimationBaseDemo extends StatefulWidget { @override _AnimationBaseDemoState createState() => _AnimationBaseDemoState(); } class _AnimationBaseDemoState extends State<AnimationBaseDemo> { double _size = 100; @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { setState(() { _size = 200; }); }, child: Container( height: _size, width: _size, color: Colors.blue, alignment: Alignment.center, child: Text('點我變大',style: TextStyle(color: Colors.white,fontSize: 18),), ), ), ); } }
雖然變大了,但並無動畫效果,而是直接變大的,想要使其一點點放大須要引入 AnimationController,它是動畫控制器,控制動畫的啓動、中止,還能夠獲取動畫的運行狀態,AnimationController 一般在 initState 方法中初始化:學習
class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{ double _size = 100; AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)); } ... }
這裏有兩個參數須要設置:動畫
vsync
參數,存在vsync
時會防止屏幕外動畫消耗沒必要要的資源,單個 AnimationController 的時候使用 SingleTickerProviderStateMixin,多個 AnimationController 使用 TickerProviderStateMixin。修改以下:ui
class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{ double _size = 100; AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: _size, width: _size, color: Colors.blue, alignment: Alignment.center, child: Text('點我變大',style: TextStyle(color: Colors.white,fontSize: 18),), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
點擊藍色盒子的時候再也不直接更改大小,而是執行動畫_controller.forward()
。this
另外在State dispose 生命週期中釋放 AnimationController。設計
此時點擊藍色盒子發現並不會變大,StatefulWidget 組件改變外觀須要調用 setState
,所以給 AnimationController 添加監聽:code
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _size = 100+100*_controller.value; }); });
每一幀都會回調addListener ,在此回調中設置藍色盒子大小,藍色的大小是由 100 變到 200,而 AnimationController 的值默認是 0 到 1,因此藍色大小等於 _size = 100+100*_controller.value,運行效果:
這就是 Flutter 中最簡單動畫的實現方式,其中最重要的就是 AnimationController,_controller.value 是當前動畫的值,默認從 0 到 1。也能夠經過參數形式設置最大值和最小值:
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500),lowerBound: 100,upperBound: 200) ..addListener(() { setState(() { _size = _controller.value; }); })
此時 _controller.value 的值就是從 100變化到 200。
除了使用 addListener 監聽每一幀,還能夠監聽動畫狀態的變化:
_controller = AnimationController( vsync: this, duration: Duration(milliseconds: 500), lowerBound: 100, upperBound: 200) ..addStatusListener((status) { print('status:$status'); })
動畫的狀態分爲四種:
再來看下動畫的控制方法:
forward 和 reverse 方法中都有 from 參數,這個參數的意義是同樣的,表示動畫今後值開始執行,而再也不是從lowerBound 到 upperBound。好比上面的例子中 from 參數設置 150,那麼執行動畫時,藍色盒子瞬間變爲 150,而後再慢慢變大到200。
讓藍色盒子大小從 100 到 200,而後再變到 100,再到 200,如此反覆:
_controller = AnimationController( vsync: this, duration: Duration(milliseconds: 500), lowerBound: 100, upperBound: 200) ..addStatusListener((AnimationStatus status) { if(status == AnimationStatus.completed){ _controller.reverse(); }else if(status == AnimationStatus.dismissed){ _controller.forward(); } }) ..addListener(() { setState(() { _size = _controller.value; }); });
只需監聽動畫狀態變化,在動畫結束後再正向/反向再次執行動畫。
雖然上面講了不少,但只有一個重點 AnimationController。
AnimationController 設置的最小/大值類型是 double,若是動畫的變化是顏色要如何處理?
AnimationController 在執行動畫期間返回的值是 0 到 1,顏色從藍色變爲紅色方法以下:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _color = Color.lerp(_startColor, _endColor, _controller.value); }); });
重點是 Color.lerp 方法,此方法是在兩種顏色之間線性插值。
完整代碼以下:
class TweenDemo extends StatefulWidget { @override _TweenDemoState createState() => _TweenDemoState(); } class _TweenDemoState extends State<TweenDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Color _startColor = Colors.blue; Color _endColor = Colors.red; Color _color = Colors.blue; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() { _color = Color.lerp(_startColor, _endColor, _controller.value); }); }); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: 100, width: 100, color: _color, alignment: Alignment.center, child: Text( '點我變色', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
Flutter 中把這種從 0 -> 1 轉換爲 藍色 -> 紅色 行爲稱之爲 Tween(映射)。
使用 Tween 完成動畫 藍色 -> 紅色:
class _TweenDemoState extends State<TweenDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<Color> _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500)) ..addListener(() { setState(() {}); }); _animation = ColorTween(begin: Colors.blue, end: Colors.red).animate(_controller); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: 100, width: 100, color: _animation.value, alignment: Alignment.center, child: Text( '點我變色', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
效果和上面是同樣的。
Tween 僅僅是映射,動畫的控制依然由 AnimationController 控制,所以須要 Tween.animate(_controller) 將控制器傳遞給Tween。
系統提供了大量的 Tween:
基本上經常使用的屬性都包含了其對應的 Tween,看一下 ColorTween 的源代碼實現:
本質上也是使用 Color.lerp 實現的。
動畫中還有一個重要的概念就是 Curve,即動畫執行曲線。Curve 的做用和 Android 中的 Interpolator(差值器)是同樣的,負責控制動畫變化的速率,通俗地講就是使動畫的效果可以以勻速、加速、減速、拋物線等各類速率變化。
藍色盒子大小 100 變大到 200,動畫曲線設置爲 bounceIn(彈簧效果) :
class CurveDemo extends StatefulWidget { @override _CurveDemoState createState() => _CurveDemoState(); } class _CurveDemoState extends State<CurveDemo> with SingleTickerProviderStateMixin { AnimationController _controller; Animation _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.bounceIn)) .animate(_controller); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _controller.forward(); }, child: Container( height: _animation.value, width: _animation.value, color: Colors.blue, alignment: Alignment.center, child: Text( '點我變大', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } }
動畫加上Curve 後,AnimationController 的最小/大值必須是 [0,1]之間,例以下面的寫法就是錯誤的:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000),lowerBound: 100.0,upperBound: 200.0) ..addListener(() { setState(() {}); }); _animation = CurveTween(curve: Curves.bounceIn).animate(_controller);
拋出以下異常:
正確寫法:
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.bounceIn)) .animate(_controller);
系統已經提供了38種經常使用到動畫曲線:
linear
decelerate
bounceIn
bounceOut
elasticIn
其他動畫效果能夠官方文檔查看。
一般狀況下,這些曲線可以知足 99.99% 的需求,不少時候設計也就是告訴你動畫 先快後慢 或者 先慢後快,因此選個相似的就能夠了,但有一些 特別 的設計非要一個系統沒有的動畫曲線,要怎麼辦?
其實自定義一個動畫曲線難點在 數學 上,怎麼把數學公式用代碼實現纔是難點。
下面是一個 樓梯效果 的動畫曲線:
自定義動畫曲線須要繼承 Curve 重寫 transformInternal 方法便可:
class _StairsCurve extends Curve { @override double transformInternal(double t) { return t; } }
直接返回 t 其實就是線性動畫,即 Curves.linear,實現樓梯效果動畫代碼以下:
class _StairsCurve extends Curve { //階梯的數量 final int num; double _perStairY; double _perStairX; _StairsCurve(this.num) { _perStairY = 1.0 / (num - 1); _perStairX = 1.0 / num; } @override double transformInternal(double t) { return _perStairY * (t / _perStairX).floor(); } }
修改開始處的案例,使用此曲線:
_animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: _StairsCurve(5))) .animate(_controller);
動畫系統的核心是 AnimationController,並且是不可或缺的,動畫中必須有 AnimationController,而 Tween 和 Curve 則是對 AnimationController 的補充, Tween 實現了將 AnimationController [0,1]的值映射爲其餘類型的值,好比顏色、樣式等,Curve 是 AnimationController 動畫執行曲線,默認是線性運行。
將 AnimationController 、 Tween 、Curve 進行關聯的方式:
AnimationController _controller; Animation _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _animation = Tween(begin: 100.0, end: 200.0) .animate(_controller); }
或者:
_animation = _controller.drive(Tween(begin: 100.0, end: 200.0));
加入 Curve :
_animation = Tween(begin: 100.0, end: 200.0) .chain(CurveTween(curve: Curves.linear)) .animate(_controller);
或者:
_animation = _controller .drive(CurveTween(curve: Curves.linear)) .drive(Tween(begin: 100.0, end: 200.0));
只須要 Curve :
_animation = CurveTween(curve: Curves.linear) .animate(_controller);
或者
_animation = _controller.drive(CurveTween(curve: Curves.linear));
一個 AnimationController 能夠對應多個 Animation(Tween 或者 Curve),StatefulWidget 組件能夠包含多個 AnimationController ,但 SingleTickerProviderStateMixin 須要修改成 TickerProviderStateMixin,改變顏色和大小,由兩個 AnimationController 控制:
class MultiControllerDemo extends StatefulWidget { @override _MultiControllerDemoState createState() => _MultiControllerDemoState(); } class _MultiControllerDemoState extends State<MultiControllerDemo> with TickerProviderStateMixin { AnimationController _sizeController; AnimationController _colorController; Animation<double> _sizeAnimation; Animation<Color> _colorAnimation; @override void initState() { super.initState(); _sizeController = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)) ..addListener(() { setState(() {}); }); _sizeAnimation = _sizeController .drive(CurveTween(curve: Curves.linear)) .drive(Tween(begin: 100.0, end: 200.0)); _colorController = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)) ..addListener(() { setState(() {}); }); _colorAnimation = _colorController .drive(CurveTween(curve: Curves.bounceIn)) .drive(ColorTween(begin: Colors.blue, end: Colors.red)); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { _sizeController.forward(); _colorController.forward(); }, child: Container( height: _sizeAnimation.value, width: _sizeAnimation.value, color: _colorAnimation.value, alignment: Alignment.center, child: Text( '點我變化', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ); } @override void dispose() { super.dispose(); _sizeController.dispose(); _colorController.dispose(); } }
AnimationController 、Tween 、Curve 是整個動畫的基礎,Flutter 系統封裝了大量了動畫組件,但這些組件也是基於此封裝的,由於深刻了解這三部分比學習使用動畫組件更重要,再次對這3個進行總結:
完成一個動畫效果的過程以下:
若是你發現閱讀完此篇文章仍是感受不會寫動畫,不要灰心,這是正常的,第一次想了解這些抽象的概念仍是比較困難的,若是你有其餘平臺的相關經驗,那會好不少,對於動畫,想要掌握我的認爲只有一個方法就是 多寫。
後面會介紹動畫組件基礎使用、實現原理、高級動畫以及自定義動畫,把每個動畫組件的用法都親自手寫一遍(而不是複製黏貼),回過頭來在看這篇文章,會有不同的感受。
老孟Flutter博客地址(330個控件用法):http://laomengit.com
歡迎加入Flutter交流羣(微信:laomengit)、關注公衆號【老孟Flutter】:
![]() |
![]() |