用Android Studio和VS Code建立的Flutter應用,原始碼模板是一個計數器示例,通過講解分析計數器示例的原始碼,可以讓讀者對Flutter應用程式結構有個最基本的瞭解,在後續的文章中,筆者將會基於此示例,一步一步新增新的功能來介紹Flutter應用的其他概念與技術。
示例原始碼
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
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'),
);
}
}
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(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}
複製程式碼
執行效果
原始碼分析
1、導包
import 'package:flutter/material.dart';
複製程式碼
- 此行程式碼作用是匯入了Material UI元件庫。Material是一種標準的移動端和web端的視覺設計語言,Flutter預設提供了一套豐富的Material風格的UI元件。
2、主函式(應用入口)
void main() => runApp(MyApp());
複製程式碼
- 與C/C++、Java類似,Flutter應用中
main
函式為應用程式的入口,main
函式中呼叫了runApp
方法,它的功能是啟動Flutter應用,它接受一個Widget
引數,在本示例中它是MyApp
類的一個例項,該引數代表Flutter應用。 main
函式使用了(=>
)符號,這是Dart中單行函式或方法的簡寫。
3、應用結構
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)。 MaterialApp
是Material庫中提供的Flutter APP框架,通過它可以設定應用的名稱、主題、語言和首頁及路由列表等。MaterialApp
也是一個Widget。home
為Flutter應用的首頁,它也是一個widget。
4、首頁
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有什麼區別呢?
(1)、Stateful widget可以擁有狀態,這些狀態在Widget生命週期中是可以變的,而Stateless widget是不可變的。
(2)、Stateful widget至少由兩個類組成:
①、一個
StatefulWidget
類;②、一個
State
類,StatefulWidget
類本身是不變的,但是State
類中持有的狀態在Widget生命週期中可能會發生變化。
_MyHomePageState
類是MyHomePage
類對應的狀態類。看到這裡,細心的讀者可能已經發現,和MyApp
類不同的是,MyHomePage
類中並沒有build
方法,取而代之的是,build
方法被挪到了_MyHomePageState
方法中,至於為什麼這麼做,先留個疑問,在分析完完整程式碼後再來解答,接下來,我們看看_MyHomePageState中都包含哪些東西:-
狀態
int _counter = 0; 複製程式碼
_count
為儲存螢幕右下角帶“+”號按鈕點選次數的狀態。 -
設定狀態的自增函式
void _incrementCounter() { setState(() { _counter++; }); } 複製程式碼
當按鈕點選時,會呼叫此函式,該函式的作用是先自增
_counter
,然後呼叫setState
方法。setState
方法的作用是通知Flutter框架,有狀態發生了改變,Flutter框架收到通知後,會執行build
方法來根據新的狀態重新構建介面,Flutter對此方法做了優化,使重新執行變的很快,所以讀者可以重新構建任何需要更新的東西,而無需分別去修改各個Widget。 -
構建UI介面
構建UI介面的邏輯在
build
方法中,當MyHomePage
第一次建立時,_MyHomePageState
類會被建立,當初始化完成後,Flutter框架會呼叫Widget的build
方法來構建Widget樹,最終將Widget樹渲染到裝置螢幕上。所以,我們看看_MyHomePageState
的build
方法中都幹了什麼事: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( 'You have pushed the button this many times:', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ), ); } 複製程式碼
-
Scaffold
是Material庫中提供的一個Widget,它提供了預設的導航欄、標題和包含主螢幕widget樹的body屬性。路由預設都是通過Scaffold
建立。 -
body
的Widget樹中包含了一個Center
Widget,Center
可以將其子Widget樹對齊到螢幕中心,Center
子Widget是一個Column
Widget,Column
的作用是將其所有子Widget沿螢幕垂直方向依次排列,此例中Column
包含兩個Text
子Widget,第一個Text
Widget顯示固定文字“You have pushed the button this many times:”,第二個Text
Widget顯示_counter
狀態的數值。 -
floatingActionButton是頁面右下角的帶“+”的懸浮按鈕,它的
onPressed
屬性接受一個回撥函式,代表它被點選後的處理器,本例中直接將_incrementCounter
作為其處理函式。
-
5、總結
現在,我們將整個流程串起來:當右下角的floatingActionButton按鈕被點選之後,會呼叫_incrementCounter
函式,在_incrementCounter
函式中,首先會自增_counter
狀態(計數器),然後setState
會通知Flutter框架狀態發生變化,接著,Flutter會呼叫build
方法以新的狀態重新構建UI,最終顯示在裝置螢幕上。
疑問解答
為什麼要將build方法放在State中,而不是放在StatefulWidget中?
現在,我們回答之前提出的問題,為什麼build
方法在State中,而不是StatefulWidget中?這主要是為了開發的靈活性。如果將build
方法在StatefulWidget中則會有兩個問題:
-
狀態訪問不便
試想一下,如果我們的Stateful widget有很多狀態,而每次狀態改變都要呼叫
build
方法。由於狀態是儲存在State中的,如果將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
中,可以給開發帶來很大的靈活性。