上一篇文章我們了解了Flutter的動畫基礎,這一篇文章我們就來實現一個圖表的動畫效果。
首先,我們需要創建一個新項目myapp,然后把main.dart的內容替換成下面的代碼
import 'package:flutter/material.dart';import 'dart:math';void main() { runApp(new MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: new MyHomePage(), ); }}class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { // Random([int seed ]):創建一個隨機數生成器 final random = new Random(); int dataSet; void changeData() { setState(() { dataSet = random.nextInt(100); }); } @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new Text('數據集:$dataSet'), ), floatingActionButton: new FloatingActionButton( onPressed: changeData, child: new Icon(Icons.refresh), ), ); }}
啟動項目后,應用程序會顯示一個居中的文本標簽,顯示“數據集:null”和浮動按鈕來刷新數據。
我們的應用程序生成的樹結構如下圖所示,您可以看到,雖然控件概念相當廣泛,但每個具體的控件類型通常具有非常重要的責任。
通過定義用戶界面的不可變的控件樹,修改用戶界面的唯一方法是重建樹,當下一幀到期時告訴Flutter一個子樹所依賴的一些狀態已經改變了。這種狀態依賴的子樹的根必須是StatefulWidget,一個StatefulWidget不是可變的,但是它的子樹是由State對象構建的。Flutter在構建期間通過樹重建保留State對象并將其附加到新樹中的各自的控件,然后,它們確定該控件的子樹是如何構建的。在我們的應用程序中,MyHomePage是以_MyHomePageState為其狀態的StatefulWidget,每當用戶按下按鈕時,我們執行一些代碼來更改_MyHomePageState。我們已經用setState劃分了這個變化,以便Flutter可以進行內部管理,并調度控件樹進行重建。當發生這種情況時,_MyHomePageState將構建一個稍微不同的子樹,這個子樹以新的MyHomePage實例為根。
不可變的控件和狀態依賴的子樹是Flutter提供的主要工具,用于處理響應異步事件(比如按鈕、定時器刻度或輸入數據)的復雜用戶界面中的狀態管理的復雜性。
我們的應用程序將保持簡單的控件結構,但我們會做一些動畫定制圖形,第一步是用一個非常簡單的圖表替換每個數據集的文本顯示。由于數據集當前僅有一個在0~100之間數字,所以圖表將是一個帶有單個條形的條形圖,其高度由該數字確定,我們將使用初始值50來避免高度為null。
import 'package:flutter/material.dart';import 'dart:math';void main() { runApp(new MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: new MyHomePage(), ); }}class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { // Random([int seed ]):創建一個隨機數生成器 final random = new Random(); int dataSet = 50; void changeData() { setState(() { dataSet = random.nextInt(100); }); } @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new CustomPaint( size: new Size(200.0, 100.0), painter: new BarChartPainter(dataSet.toDouble()) ) ), floatingActionButton: new FloatingActionButton( onPressed: changeData, child: new Icon(Icons.refresh), ), ); }}// CustomPaint:是將繪畫委托給CustomPainter策略的控件class BarChartPainter extends CustomPainter { static const barWidth = 10.0; BarChartPainter(this.barHeight); final double barHeight; /* void paint( Canvas canvas, Size size ) 當對象需要繪制時調用,它給出Canvas的坐標空間,使得原點位于框的左上角, 框的面積是size參數的大小 */ @override void paint(Canvas canvas, Size size) { final paint = new Paint() ..color = Colors.blue[400] ..style = PaintingStyle.fill; // drawRect:使用給定的Paint繪制一個矩形,是否填充或描邊(或兩者)是由Paint.style控制 canvas.drawRect( // Rect.fromLTWH(double left, double top, double width, double height): // 從左上角和上邊緣構造一個矩形,并設置其寬度和高度 new Rect.fromLTWH( size.width-barWidth/2.0, size.height-barHeight, barWidth, barHeight ), paint ); } /* bool shouldRepaint( CustomPainter, oldDelegate ) 當定制繪畫委托類的新實例被提供給RenderCustomPaint對象時, 或任何時候使用自定義繪畫委托類的新實例創建新的CustomPaint對象 (這相當于同一件事,因為后者是以前者實施) */ @override bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;}
下一步是添加動畫,每當數據集發生變化時,我們希望該欄可以平滑而不是突然地改變高度。Flutter有一個AnimationController的概念,用于編排動畫,通過注冊一個監聽器,我們被告知當動畫值(0.0~1.0)改變時。每當發生這種情況,我們可以像以前一樣調用setState并更新_MyHomePageState。
import 'package:flutter/material.dart';import 'package:flutter/animation.dart';import 'dart:math';import 'dart:ui' show lerpDouble;void main() { runApp(new MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: new MyHomePage(), ); }}class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin { // Random([int seed ]):創建一個隨機數生成器 final random = new Random(); int dataSet = 50; AnimationController animation; double startHeight; double currentHeight; double endHeight; /* @protected @mustCallSuper void initState() 將此對象插入樹中時調用 該框架將為其創建的每個State對象精確地調用此方法一次 */ @override void initState() { super.initState(); /* AnimationController({ double value, Duration duration, String debugLabel, double lowerBound: 0.0, double upperBound: 1.0, TickerProvider vsync }) 創建動畫控制器 */ animation = new AnimationController( // 這個動畫應該持續的時間長短 duration: const Duration(milliseconds: 300), vsync: this ) /* void addListener( VoidCallback listener ) 每次動畫值更改時調用監聽器 可以使用removeListener刪除監聽器 */ ..addListener((){ setState((){ /* double lerpDouble( num a, num b, double t ) 在兩個數字之間進行線性內插 return a + (b - a) * t; */ currentHeight = lerpDouble( startHeight, endHeight, animation.value ); }); }); startHeight = 0.0; currentHeight = 0.0; endHeight = dataSet.toDouble(); // 開始向前運行這個動畫(朝向最后) animation.forward(); } /* @override void dispose() 當該對象永久從樹中刪除時調用 當該State對象永遠不會再次構建時,該框架調用此方法 框架調用dispose后,該State對象被視為已卸載,并且mounted屬性為false,此時調用setState是一個錯誤 生命周期的這個階段是終點:沒有辦法重新安裝dispose的State對象 */ @override void dispose() { animation.dispose(); super.dispose(); } void changeData() { setState(() { startHeight = currentHeight; dataSet = random.nextInt(100); endHeight = dataSet.toDouble(); animation.forward(from: 0.0); }); } @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new CustomPaint( size: new Size(200.0, 100.0), painter: new BarChartPainter(currentHeight) ) ), floatingActionButton: new FloatingActionButton( onPressed: changeData, child: new Icon(Icons.refresh), ), ); }}// CustomPaint:是將繪畫委托給CustomPainter策略的控件class BarChartPainter extends CustomPainter { static const barWidth = 10.0; BarChartPainter(this.barHeight); final double barHeight; /* void paint( Canvas canvas, Size size ) 當對象需要繪制時調用,它給出Canvas的坐標空間,使得原點位于框的左上角, 框的面積是size參數的大小 */ @override void paint(Canvas canvas, Size size) { final paint = new Paint() ..color = Colors.blue[400] ..style = PaintingStyle.fill; // drawRect:使用給定的Paint繪制一個矩形,是否填充或描邊(或兩者)是由Paint.style控制 canvas.drawRect( // Rect.fromLTWH(double left, double top, double width, double height): // 從左上角和上邊緣構造一個矩形,并設置其寬度和高度 new Rect.fromLTWH( size.width-barWidth/2.0, size.height-barHeight, barWidth, barHeight ), paint ); } /* bool shouldRepaint( CustomPainter, oldDelegate ) 當定制繪畫委托類的新實例被提供給RenderCustomPaint對象時, 或任何時候使用自定義繪畫委托類的新實例創建新的CustomPaint對象 (這相當于同一件事,因為后者是以前者實施) */ @override bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;}
上面代碼中的lerpDouble函數比較難理解,代入參數之后計算結果如下圖。
數據從一開始的0.0到達50.0時,花費了10個時間點。再到達52時,則花費了16個時間點。因此大約得出的結論時,在我們的應用程序中,數據變化越小,花費的時間點越多。
現在程序已經變得復雜性,我們的數據集仍然只是一個數字,設置動畫控制所需的代碼是一個小問題,因為當我們獲得更多的圖表數據時,它不會被分解。真正的問題是變量startHeight、currentHeight和endHeight,反映了對數據集和動畫值所做的更改,并在三個不同的地方更新。
我們需要一個概念來處理這個混亂的情況。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答