工程結(jié)構(gòu)

除了 flutter 本身的代碼、資源、依賴(lài)、配置文件之外,flutter 工程還包含了 Android 和 iOS 的工程目錄。
flutter 雖然是一個(gè)跨平臺(tái)開(kāi)發(fā)方案,但需要一個(gè)容器最終運(yùn)行到 Android 和 iOS 平臺(tái)上,所以 flutter 工程實(shí)際上就是一個(gè)同時(shí)內(nèi)嵌了 Android 和 iOS 原生子工程的父工程。我們?cè)?lib 目錄下進(jìn)行 flutter 代碼的開(kāi)發(fā),而在某些特殊場(chǎng)景下的原生功能,則在對(duì)應(yīng)的 Android 和 iOS 目錄下提供相應(yīng)的代碼實(shí)現(xiàn),供對(duì)應(yīng)的 flutter 代碼引用。
flutter 會(huì)將相應(yīng)的依賴(lài)和構(gòu)建產(chǎn)物注入這 2 個(gè)子工程,最終集成到各自的項(xiàng)目中。而我們開(kāi)發(fā)的 flutter 代碼,最終會(huì)以原生工程的形式運(yùn)行。
工程代碼
flutter 自帶應(yīng)用模版,一個(gè)計(jì)數(shù)器示例。
- 第一部分是應(yīng)用入口、應(yīng)用結(jié)構(gòu)以及頁(yè)面結(jié)構(gòu)。
- 第二部分是頁(yè)面布局、頁(yè)面邏輯以及狀態(tài)管理。
首先,第一部分的代碼,也就是應(yīng)用的整體結(jié)構(gòu):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(home: MyHomePage(title: 'Flutter Demo Home Page'));
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) => {...};
}
MyApp 類(lèi)繼承自 StatelessWidget 類(lèi),這也就意味著應(yīng)用本身也是一個(gè) Widget。在 flutter 中,Widget 是整個(gè)視圖描述的基礎(chǔ),在 Flutter 的世界里,包括應(yīng)用、視圖、視圖控制器、布局等在內(nèi)的概念,都建立在 Widget 之上,F(xiàn)lutter 的核心設(shè)計(jì)思想便是一切皆 Widget。
Widget 是組件視覺(jué)效果的封裝,是 UI 界面的載體,因此我們還需要為它提供一個(gè) build 方法,來(lái)告訴 flutter 框架如何構(gòu)建 UI 界面。
在 build 方法中,我們通常通過(guò)對(duì)基礎(chǔ) Widget 進(jìn)行相應(yīng)的 UI 配置,或是組合各類(lèi)基礎(chǔ) Widget 的方式進(jìn)行 UI 的定制化。
MaterialApp 類(lèi)是對(duì)構(gòu)建 material 設(shè)計(jì)風(fēng)格應(yīng)用的組件封裝框架,里面還有很多可配置的屬性,比如應(yīng)用主題、應(yīng)用名稱(chēng)、語(yǔ)言標(biāo)識(shí)符、組件路由等。但是,這些配置屬性并不是本次分享的重點(diǎn),如果你感興趣的話(huà),可以參考 Flutter 官方的API 文檔,來(lái)了解 MaterialApp 框架的其他配置能力。
MyHomePage 是應(yīng)用的首頁(yè),繼承自 StatefulWidget 類(lèi)。這,代表著它是一個(gè)有狀態(tài)的 Widget(Stateful Widget),而 _MyHomePageState 就是它的狀態(tài)。
如果你足夠細(xì)心的話(huà)就會(huì)發(fā)現(xiàn),雖然 MyHomePage 類(lèi)也是 Widget,但與 MyApp 類(lèi)不同的是,它并沒(méi)有一個(gè) build 方法去返回 Widget,而是多了一個(gè) createState 方法返回 _MyHomePageState 對(duì)象,而 build 方法則包含在這個(gè) _MyHomePageState 類(lèi)當(dāng)中。
那么,StatefulWidget 與 StatelessWidget 的接口設(shè)計(jì),為什么會(huì)有這樣的區(qū)別呢?
這是因?yàn)?Widget 需要依據(jù)數(shù)據(jù)才能完成構(gòu)建,而對(duì)于 StatefulWidget 來(lái)說(shuō),其依賴(lài)的數(shù)據(jù)在 Widget 生命周期中可能會(huì)頻繁地發(fā)生變化。由 State 創(chuàng)建 Widget,以數(shù)據(jù)驅(qū)動(dòng)視圖更新,而不是直接操作 UI 更新視覺(jué)屬性,代碼表達(dá)可以更精煉,邏輯也可以更清晰。
在了解了計(jì)數(shù)器示例程序的整體結(jié)構(gòu)以后,我們?cè)賮?lái)看看這個(gè)示例代碼的第二部分,也就是頁(yè)面布局及交互邏輯部分。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() => setState(() {_counter++;});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Widget.title)),
body: Text('You have pushed the button this many times:$_counter')),
floatingActionButton: FloatingActionButton(onPressed: _incrementCounter)
);
}
_MyHomePageState 中創(chuàng)建的 Widget Scaffold,是 Material 庫(kù)中提供的頁(yè)面布局結(jié)構(gòu),它包含 AppBar、Body,以及 FloatingActionButton。
AppBar 是頁(yè)面的導(dǎo)航欄,我們直接將 MyHomePage 中的 title 屬性作為標(biāo)題使用。
body 則是一個(gè) Text 組件,顯示了一個(gè)根據(jù) _counter 屬性可變的文本:‘You have pushed the button this many times:$_counter’。
floatingActionButton,則是頁(yè)面右下角的帶“+”的懸浮按鈕。我們將 _incrementCounter 作為其點(diǎn)擊處理函數(shù)。
_incrementCounter 的實(shí)現(xiàn)很簡(jiǎn)單,使用 setState 方法去自增狀態(tài)屬性 _counter。setState 方法是 Flutter 以數(shù)據(jù)驅(qū)動(dòng)視圖更新的關(guān)鍵函數(shù),它會(huì)通知 Flutter 框架:我這兒有狀態(tài)發(fā)生了改變,趕緊給我刷新界面吧。而 Flutter 框架收到通知后,會(huì)執(zhí)行 Widget 的 build 方法,根據(jù)新的狀態(tài)重新構(gòu)建界面。
這里需要注意的是:狀態(tài)的更改一定要配合使用 setState。通過(guò)這個(gè)方法的調(diào)用,F(xiàn)lutter 會(huì)在底層標(biāo)記 Widget 的狀態(tài),隨后觸發(fā)重建。于我們的示例而言,即使你修改了 _counter,如果不調(diào)用 setState,F(xiàn)lutter 框架也不會(huì)感知到狀態(tài)的變化,因此界面上也不會(huì)有任何改變(你可以動(dòng)手驗(yàn)證一下)。
整個(gè)計(jì)數(shù)器示例的代碼流程示意圖

MyApp 為 Flutter 應(yīng)用的運(yùn)行實(shí)例,通過(guò)在 main 函數(shù)中調(diào)用 runApp 函數(shù)實(shí)現(xiàn)程序的入口。而應(yīng)用的首頁(yè)則為 MyHomePage,一個(gè)擁有 _MyHomePageState 狀態(tài)的 StatefulWidget。_MyHomePageState 通過(guò)調(diào)用 build 方法,以相應(yīng)的數(shù)據(jù)配置完成了包括導(dǎo)航欄、文本及按鈕的頁(yè)面視圖的創(chuàng)建。
而當(dāng)按鈕被點(diǎn)擊之后,其關(guān)聯(lián)的控件函數(shù) _incrementCounter 會(huì)觸發(fā)調(diào)用。在這個(gè)函數(shù)中,通過(guò)調(diào)用 setState 方法,更新 _counter 屬性的同時(shí),也會(huì)通知 Flutter 框架其狀態(tài)發(fā)生變化。隨后,F(xiàn)lutter 會(huì)重新調(diào)用 build 方法,以新的數(shù)據(jù)配置重新構(gòu)建 _MyHomePageState 的 UI,最終完成頁(yè)面的重新渲染。
Widget 只是視圖的“配置信息”,是數(shù)據(jù)的映射,是“只讀”的。對(duì)于 StatefulWidget 而言,當(dāng)數(shù)據(jù)改變的時(shí)候,我們需要重新創(chuàng)建 Widget 去更新界面,這也就意味著 Widget 的創(chuàng)建銷(xiāo)毀會(huì)非常頻繁。
為此,F(xiàn)lutter 對(duì)這個(gè)機(jī)制做了優(yōu)化,其框架內(nèi)部會(huì)通過(guò)一個(gè)中間層去收斂上層 UI 配置對(duì)底層真實(shí)渲染的改動(dòng),從而最大程度降低對(duì)真實(shí)渲染視圖的修改,提高渲染效率,而不是上層 UI 配置變了就需要銷(xiāo)毀整個(gè)渲染視圖樹(shù)重建。
這樣一來(lái),Widget 僅是一個(gè)輕量級(jí)的數(shù)據(jù)配置存儲(chǔ)結(jié)構(gòu),它的重新創(chuàng)建速度非??欤晕覀兛梢苑判牡刂匦聵?gòu)建任何需要更新的視圖,而無(wú)需分別修改各個(gè)子 Widget 的特定樣式。關(guān)于 Widget 具體的渲染過(guò)程細(xì)節(jié),我會(huì)在后續(xù)的第 9 篇文章“Widget,構(gòu)建 Flutter 界面的基石”中向你詳細(xì)介紹,在這里就不再展開(kāi)了。