用Android Studio和VS Code建立的Flutter應用模板是一個簡單的計數器示例,本節先仔細講解一下這個計數器Demo的源碼,讓讀者對Flutter應用程序結構有個基本瞭解,在隨後小節中,將會基於此示例,一步一步添加一些新的功能來介紹Flutter應用的其它概念與技術。對於接下來的示例,但願讀者能夠跟着筆者實際動手來寫一下,這樣不只能夠加深印象,並且也會對介紹的概念與技術有一個真切的體會。若是你還不是很熟悉Dart或者沒有移動開發經驗,不用擔憂,只要你熟悉面向對象和基本編程概念(如變量、循環和條件控制),則能夠完成本示例。javascript
經過Android Studio和VS Code根據前面「編輯器配置與使用」一章中介紹的建立Flutter工程的方法建立一個新的Flutter工程,命名爲"first_flutter_app"。建立好後,就會獲得一個計數器應用的Demo。java
注意,默認Demo示例可能隨着編輯器Flutter插件版本變化而變化,本例中會介紹計數器示例的所有代碼,因此不會對本示例產生影響。git
咱們先運行此示例,效果圖以下:github
該計數器示例中,每點擊一次右下角帶「➕」號的懸浮按鈕,屏幕中央的數字就會加1。web
在這個示例中,主要Dart代碼是在 lib/main.dart 文件中,下面咱們看看該示例的源碼:ajax
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '【全棧編程】-onajax.com', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '【全棧編程】-onajax.com'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( '【全棧編程】-onajax.com提示:你已經點擊了--', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: '【全棧編程】-onajax.com', child: new Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
import 'package:flutter/material.dart';
此行代碼做用是導入了Material UI組件庫。Material是一種標準的移動端和web端的視覺設計語言, Flutter默認提供了一套豐富的Material風格的UI組件。編程
void main() => runApp(new MyApp());
與C/C++、Java相似,Flutter 應用中main函數爲應用程序的入口,main函數中調用了,runApp 方法,它的功能是啓動Flutter應用,它接受一個Widget參數,在本示例中它是MyApp類的一個實例,該參數表明Flutter應用。
main函數使用了(=>)符號,這是Dart中單行函數或方法的簡寫。app
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( //應用名稱 title: 'Flutter Demo', theme: new ThemeData( //藍色主題 primarySwatch: Colors.blue, ), //應用首頁路由 home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } }
MyApp類表明Flutter應用,它繼承了 StatelessWidget類,這也就意味着應用自己也是一個widget。
在Flutter中,大多數東西都是widget,包括對齊(alignment)、填充(padding)和佈局(layout)。框架
Flutter在構建頁面時,會調用組件的build方法,widget的主要工做是提供一個build()方法來描述如何構建UI界面(一般是經過組合、拼裝其它基礎widget)。less
MaterialApp 是Material庫中提供的Flutter APP框架,經過它能夠設置應用的名稱、主題、語言、首頁及路由列表等。MaterialApp也是一個widget。
Scaffold 是Material庫中提供的頁面腳手架,它包含導航欄和Body以及FloatingActionButton(若是須要的話)。 本書後面示例中,路由默認都是經過Scaffold建立。
home 爲Flutter應用的首頁,它也是一個widget。
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { ... }
MyHomePage 是應用的首頁,它繼承自StatefulWidget類,表示它是一個有狀態的widget(Stateful widget)。如今,咱們能夠簡單認爲Stateful widget 和Stateless widget有兩點不一樣:
Stateful widget能夠擁有狀態,這些狀態在widget生命週期中是能夠變的,而Stateless widget是不可變的。
Stateful widget至少由兩個類組成:
一個StatefulWidget類。
一個 State類; StatefulWidget類自己是不變的,可是 State類中持有的狀態在widget生命週期中可能會發生變化。
_MyHomePageState類是MyHomePage類對應的狀態類。看到這裏,細心的讀者可能已經發現,和MyApp 類不一樣, MyHomePage類中並無build方法,取而代之的是,build方法被挪到了_MyHomePageState方法中,至於爲何這麼作,先留個疑問,在分析完完整代碼後再來解答。
接下來,咱們看看_MyHomePageState中都包含哪些東西:
1.狀態。
int _counter = 0;
_counter 爲保存屏幕右下角帶「➕」號按鈕點擊次數的狀態。
2.設置狀態的自增函數。
void _incrementCounter() { setState(() { _counter++; }); }
當按鈕點擊時,會調用此函數,該函數的做用是先自增_counter,而後調用setState 方法。setState方法的做用是通知Flutter框架,有狀態發生了改變,Flutter框架收到通知後,會執行build方法來根據新的狀態從新構建界面, Flutter 對此方法作了優化,使從新執行變的很快,因此你能夠從新構建任何須要更新的東西,而無需分別去修改各個widget。
3.構建UI界面
構建UI界面的邏輯在build方法中,當MyHomePage第一次建立時,_MyHomePageState類會被建立,當初始化完成後,Flutter框架會調用Widget的build方法來構建widget樹,最終將widget樹渲染到設備屏幕上。因此,咱們看看_MyHomePageState的build方法中都幹了什麼事:
@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( '【全棧編程】-onajax.com提示:你已經點擊了--', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: '【全棧編程】-onajax.com', child: new Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); }
Scaffold 是 Material庫中提供的一個widget, 它提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性。widget樹能夠很複雜。
body的widget樹中包含了一個Center widget,Center 能夠將其子widget樹對齊到屏幕中心, Center 子widget是一個Column widget,Column的做用是將其全部子widget沿屏幕垂直方向依次排列, 此例中Column包含兩個 Text子widget,第一個Text widget顯示固定文本 「【全棧編程】-onajax.com提示:你已經點擊了--」,第二個Text widget顯示_counter狀態的數值。
floatingActionButton是頁面右下角的帶「➕」的懸浮按鈕,它的onPressed屬性接受一個回調函數,表明它被點擊後的處理器,本例中直接將_incrementCounter做爲其處理函數。
如今,咱們將整個流程串起來:當右下角的floatingActionButton按鈕被點擊以後,會調用_incrementCounter,在_incrementCounter中,首先會自增_counter計數器(狀態),而後setState會通知Flutter框架狀態發生變化,接着,Flutter會調用build方法以新的狀態從新構建UI,最終顯示在設備屏幕上。
如今,咱們回答以前提出的問題,爲何build()方法在State(而不是StatefulWidget)中 ?這主要是爲了開發的靈活性。若是將build()方法在StatefulWidget中則會有兩個問題:
狀態訪問不便
試想一下,若是咱們的Stateful widget 有不少狀態,而每次狀態改變都要調用build方法,因爲狀態是保存在State中的,若是將build方法放在StatefulWidget中,那麼構建時讀取狀態將會很不方便,試想一下,若是真的將build方法放在StatefulWidget中的話,因爲構建用戶界面過程須要依賴State,因此build方法將必須加一個State參數,大概是下面這樣:
Widget build(BuildContext context, State state){ //state.counter ... }
這樣的話就只能將State的全部狀態聲明爲公開的狀態,這樣才能在State類外部訪問狀態,但將狀態設置爲公開後,狀態將再也不具備私密性,這樣依賴,對狀態的修改將會變的不可控。將build()方法放在State中的話,構建過程則能夠直接訪問狀態,這樣會很方便。
繼承StatefulWidget不便
例如,Flutter中有一個動畫widget的基類AnimatedWidget,它繼承自StatefulWidget類。AnimatedWidget中引入了一個抽象方法build(BuildContext context),繼承自AnimatedWidget的動畫widget都要實現這個build方法。如今設想一下,若是StatefulWidget 類中已經有了一個build方法,正如上面所述,此時build方法須要接收一個state對象,這就意味着AnimatedWidget必須將本身的State對象(記爲_animatedWidgetState)提供給其子類,由於子類須要在其build方法中調用父類的build方法,代碼可能以下:
class MyAnimationWidget extends AnimatedWidget{ @override Widget build(BuildContext context, State state){ //因爲子類要用到AnimatedWidget的狀態對象_animatedWidgetState, //因此AnimatedWidget必須經過某種方式將其狀態對象_animatedWidgetState //暴露給其子類 super.build(context, _animatedWidgetState) } }
這樣很顯然是不合理的,由於
AnimatedWidget的狀態對象是AnimatedWidget內部實現細節,不該該暴露給外部。
若是要將父類狀態暴露給子類,那麼必須得有一種傳遞機制,而作這一套傳遞機制是無心義的,由於父子類之間狀態的傳遞和子類自己邏輯是無關的。
綜上所述,能夠發現,對於StatefulWidget,將build方法放在State中,能夠給開發帶來很大的靈活性。