Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫(huà)效果(一)
上一篇文章我們了解了Flutter的動(dòng)畫(huà)基礎(chǔ),這一篇文章我們就來(lái)實(shí)現(xiàn)一個(gè)圖表的動(dòng)畫(huà)效果。
首先,我們需要?jiǎng)?chuàng)建一個(gè)新項(xiàng)目myapp,然后把main.dart的內(nèi)容替換成下面的代碼
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 ]):創(chuàng)建一個(gè)隨機(jī)數(shù)生成器
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('數(shù)據(jù)集:$dataSet'),
),
floatingActionButton: new FloatingActionButton(
onPressed: changeData,
child: new Icon(Icons.refresh),
),
);
}
}
啟動(dòng)項(xiàng)目后,應(yīng)用程序會(huì)顯示一個(gè)居中的文本標(biāo)簽,顯示“數(shù)據(jù)集:null”和浮動(dòng)按鈕來(lái)刷新數(shù)據(jù)。
我們的應(yīng)用程序生成的樹(shù)結(jié)構(gòu)如下圖所示,您可以看到,雖然控件概念相當(dāng)廣泛,但每個(gè)具體的控件類(lèi)型通常具有非常重要的責(zé)任。

通過(guò)定義用戶(hù)界面的不可變的控件樹(shù),修改用戶(hù)界面的唯一方法是重建樹(shù),當(dāng)下一幀到期時(shí)告訴Flutter一個(gè)子樹(shù)所依賴(lài)的一些狀態(tài)已經(jīng)改變了。這種狀態(tài)依賴(lài)的子樹(shù)的根必須是StatefulWidget,一個(gè)StatefulWidget不是可變的,但是它的子樹(shù)是由State對(duì)象構(gòu)建的。Flutter在構(gòu)建期間通過(guò)樹(shù)重建保留State對(duì)象并將其附加到新樹(shù)中的各自的控件,然后,它們確定該控件的子樹(shù)是如何構(gòu)建的。在我們的應(yīng)用程序中,MyHomePage是以_MyHomePageState為其狀態(tài)的StatefulWidget,每當(dāng)用戶(hù)按下按鈕時(shí),我們執(zhí)行一些代碼來(lái)更改_MyHomePageState。我們已經(jīng)用setState劃分了這個(gè)變化,以便Flutter可以進(jìn)行內(nèi)部管理,并調(diào)度控件樹(shù)進(jìn)行重建。當(dāng)發(fā)生這種情況時(shí),_MyHomePageState將構(gòu)建一個(gè)稍微不同的子樹(shù),這個(gè)子樹(shù)以新的MyHomePage實(shí)例為根。
不可變的控件和狀態(tài)依賴(lài)的子樹(shù)是Flutter提供的主要工具,用于處理響應(yīng)異步事件(比如按鈕、定時(shí)器刻度或輸入數(shù)據(jù))的復(fù)雜用戶(hù)界面中的狀態(tài)管理的復(fù)雜性。
我們的應(yīng)用程序?qū)⒈3趾?jiǎn)單的控件結(jié)構(gòu),但我們會(huì)做一些動(dòng)畫(huà)定制圖形,第一步是用一個(gè)非常簡(jiǎn)單的圖表替換每個(gè)數(shù)據(jù)集的文本顯示。由于數(shù)據(jù)集當(dāng)前僅有一個(gè)在0~100之間數(shù)字,所以圖表將是一個(gè)帶有單個(gè)條形的條形圖,其高度由該數(shù)字確定,我們將使用初始值50來(lái)避免高度為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 ]):創(chuàng)建一個(gè)隨機(jī)數(shù)生成器
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:是將繪畫(huà)委托給CustomPainter策略的控件
class BarChartPainter extends CustomPainter {
static const barWidth = 10.0;
BarChartPainter(this.barHeight);
final double barHeight;
/*
void paint(
Canvas canvas,
Size size
)
當(dāng)對(duì)象需要繪制時(shí)調(diào)用,它給出Canvas的坐標(biāo)空間,使得原點(diǎn)位于框的左上角,
框的面積是size參數(shù)的大小
*/
@override
void paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = Colors.blue[400]
..style = PaintingStyle.fill;
// drawRect:使用給定的Paint繪制一個(gè)矩形,是否填充或描邊(或兩者)是由Paint.style控制
canvas.drawRect(
// Rect.fromLTWH(double left, double top, double width, double height):
// 從左上角和上邊緣構(gòu)造一個(gè)矩形,并設(shè)置其寬度和高度
new Rect.fromLTWH(
size.width-barWidth/2.0,
size.height-barHeight,
barWidth,
barHeight
),
paint
);
}
/*
bool shouldRepaint(
CustomPainter,
oldDelegate
)
當(dāng)定制繪畫(huà)委托類(lèi)的新實(shí)例被提供給RenderCustomPaint對(duì)象時(shí),
或任何時(shí)候使用自定義繪畫(huà)委托類(lèi)的新實(shí)例創(chuàng)建新的CustomPaint對(duì)象
(這相當(dāng)于同一件事,因?yàn)楹笳呤且郧罢邔?shí)施)
*/
@override
bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}
下一步是添加動(dòng)畫(huà),每當(dāng)數(shù)據(jù)集發(fā)生變化時(shí),我們希望該欄可以平滑而不是突然地改變高度。Flutter有一個(gè)AnimationController的概念,用于編排動(dòng)畫(huà),通過(guò)注冊(cè)一個(gè)監(jiān)聽(tīng)器,我們被告知當(dāng)動(dòng)畫(huà)值(0.0~1.0)改變時(shí)。每當(dāng)發(fā)生這種情況,我們可以像以前一樣調(diào)用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 ]):創(chuàng)建一個(gè)隨機(jī)數(shù)生成器
final random = new Random();
int dataSet = 50;
AnimationController animation;
double startHeight;
double currentHeight;
double endHeight;
/*
@protected
@mustCallSuper
void initState()
將此對(duì)象插入樹(shù)中時(shí)調(diào)用
該框架將為其創(chuàng)建的每個(gè)State對(duì)象精確地調(diào)用此方法一次
*/
@override
void initState() {
super.initState();
/*
AnimationController({
double value,
Duration duration,
String debugLabel,
double lowerBound: 0.0,
double upperBound: 1.0,
TickerProvider vsync
})
創(chuàng)建動(dòng)畫(huà)控制器
*/
animation = new AnimationController(
// 這個(gè)動(dòng)畫(huà)應(yīng)該持續(xù)的時(shí)間長(zhǎng)短
duration: const Duration(milliseconds: 300),
vsync: this
)
/*
void addListener(
VoidCallback listener
)
每次動(dòng)畫(huà)值更改時(shí)調(diào)用監(jiān)聽(tīng)器
可以使用removeListener刪除監(jiān)聽(tīng)器
*/
..addListener((){
setState((){
/*
double lerpDouble(
num a,
num b,
double t
)
在兩個(gè)數(shù)字之間進(jìn)行線(xiàn)性?xún)?nèi)插
return a + (b - a) * t;
*/
currentHeight = lerpDouble(
startHeight,
endHeight,
animation.value
);
});
});
startHeight = 0.0;
currentHeight = 0.0;
endHeight = dataSet.toDouble();
// 開(kāi)始向前運(yùn)行這個(gè)動(dòng)畫(huà)(朝向最后)
animation.forward();
}
/*
@override
void dispose()
當(dāng)該對(duì)象永久從樹(shù)中刪除時(shí)調(diào)用
當(dāng)該State對(duì)象永遠(yuǎn)不會(huì)再次構(gòu)建時(shí),該框架調(diào)用此方法
框架調(diào)用dispose后,該State對(duì)象被視為已卸載,并且mounted屬性為false,此時(shí)調(diào)用setState是一個(gè)錯(cuò)誤
生命周期的這個(gè)階段是終點(diǎn):沒(méi)有辦法重新安裝dispose的State對(duì)象
*/
@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:是將繪畫(huà)委托給CustomPainter策略的控件
class BarChartPainter extends CustomPainter {
static const barWidth = 10.0;
BarChartPainter(this.barHeight);
final double barHeight;
/*
void paint(
Canvas canvas,
Size size
)
當(dāng)對(duì)象需要繪制時(shí)調(diào)用,它給出Canvas的坐標(biāo)空間,使得原點(diǎn)位于框的左上角,
框的面積是size參數(shù)的大小
*/
@override
void paint(Canvas canvas, Size size) {
final paint = new Paint()
..color = Colors.blue[400]
..style = PaintingStyle.fill;
// drawRect:使用給定的Paint繪制一個(gè)矩形,是否填充或描邊(或兩者)是由Paint.style控制
canvas.drawRect(
// Rect.fromLTWH(double left, double top, double width, double height):
// 從左上角和上邊緣構(gòu)造一個(gè)矩形,并設(shè)置其寬度和高度
new Rect.fromLTWH(
size.width-barWidth/2.0,
size.height-barHeight,
barWidth,
barHeight
),
paint
);
}
/*
bool shouldRepaint(
CustomPainter,
oldDelegate
)
當(dāng)定制繪畫(huà)委托類(lèi)的新實(shí)例被提供給RenderCustomPaint對(duì)象時(shí),
或任何時(shí)候使用自定義繪畫(huà)委托類(lèi)的新實(shí)例創(chuàng)建新的CustomPaint對(duì)象
(這相當(dāng)于同一件事,因?yàn)楹笳呤且郧罢邔?shí)施)
*/
@override
bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}
上面代碼中的lerpDouble函數(shù)比較難理解,代入?yún)?shù)之后計(jì)算結(jié)果如下圖。

數(shù)據(jù)從一開(kāi)始的0.0到達(dá)50.0時(shí),花費(fèi)了10個(gè)時(shí)間點(diǎn)。再到達(dá)52時(shí),則花費(fèi)了16個(gè)時(shí)間點(diǎn)。因此大約得出的結(jié)論時(shí),在我們的應(yīng)用程序中,數(shù)據(jù)變化越小,花費(fèi)的時(shí)間點(diǎn)越多。

現(xiàn)在程序已經(jīng)變得復(fù)雜性,我們的數(shù)據(jù)集仍然只是一個(gè)數(shù)字,設(shè)置動(dòng)畫(huà)控制所需的代碼是一個(gè)小問(wèn)題,因?yàn)楫?dāng)我們獲得更多的圖表數(shù)據(jù)時(shí),它不會(huì)被分解。真正的問(wèn)題是變量startHeight、currentHeight和endHeight,反映了對(duì)數(shù)據(jù)集和動(dòng)畫(huà)值所做的更改,并在三個(gè)不同的地方更新。
我們需要一個(gè)概念來(lái)處理這個(gè)混亂的情況。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Flutter中如何加載并預(yù)覽本地的html文件的方法
- 你必須掌握在Flutter中添加資源文件的方法
- flutter Container容器實(shí)現(xiàn)圓角邊框
- Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法
- Flutter 超實(shí)用簡(jiǎn)單菜單彈出框 PopupMenuButton功能
- Flutter中http請(qǐng)求抓包的完美解決方案
- 詳解Flutter WebView與JS互相調(diào)用簡(jiǎn)易指南
- Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫(kù)存儲(chǔ)(sqflite)詳解
- flutter直接上傳文件到阿里云oss
相關(guān)文章
Android RecyclerView的簡(jiǎn)單使用
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView簡(jiǎn)單使用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
flutter開(kāi)發(fā)技巧自定頁(yè)面指示器PageIndicator詳解
這篇文章主要為大家介紹了flutter開(kāi)發(fā)技巧自定頁(yè)面指示器PageIndicator詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
直接可用的Android studio學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了直接可用的Android studio學(xué)生信息管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
Android使用Intent顯示實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Android使用Intent顯示實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08
Android UI控件之Gallery實(shí)現(xiàn)拖動(dòng)式圖片瀏覽效果
這篇文章主要為大家詳細(xì)介紹了Android UI控件之Gallery實(shí)現(xiàn)拖動(dòng)式圖片瀏覽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android studio創(chuàng)建第一個(gè)app
這篇文章主要為大家詳細(xì)介紹了如何使用Android studio創(chuàng)建你的第一個(gè)項(xiàng)目Hello World,感興趣的小伙伴們可以參考一下2016-05-05
Android Service中使用Toast無(wú)法正常顯示問(wèn)題的解決方法
這篇文章主要介紹了Android Service中使用Toast無(wú)法正常顯示問(wèn)題的解決方法,分析了Service中Toast無(wú)法正常顯示的原因與相關(guān)的解決方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
android開(kāi)發(fā)教程之子線(xiàn)程中更新界面
本文主要介紹Android的Handler的使用方法。Handler可以發(fā)送Messsage和Runnable對(duì)象到與其相關(guān)聯(lián)的線(xiàn)程的消息隊(duì)列2014-01-01
Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫(xiě)功能
這篇文章主要介紹了Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫(xiě)功能,感興趣的小伙伴們可以參考一下2015-12-12

