在Flutter框架中,Widget代表最基礎(chǔ)的部分,相當于是應(yīng)用程序的基石.引用一句話“萬物皆是Widget”,就能看出Widget的重要性不言而喻,所以本篇著重講一下,我在學(xué)習(xí)Flutter的過程中,對Widget的一些理解和認知.有些基本知識直接摘自Flutter中文網(wǎng)
1.Widget的劃分
import 'package:flutter/material.dart'; //安卓類型的風(fēng)格庫
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
眼熟吧? HelloWorld,程序員標準入門,上面的例子中,將一個widget傳遞給runApp函數(shù)就能構(gòu)成一個最簡單的Flutter程序.
上面的例子中,該runApp函數(shù)接收給定的widget并使其成為widget樹的根,這就類似于iOS中,指定window的rootViewController.(我是這樣理解的).
常用的Widget:
1.Text:文本格式的widget
2.Row,Column:類似web開發(fā)中的盒模型FlexBox,讓你可以在水平或者垂直方向上靈活布局.
3.Stack:取代線性布局,Stack允許子widget堆疊,你可以使用Positioned來定位他們相對于Stack的上左下右四條邊的位置.
4.Container:它可以讓你創(chuàng)建一個矩形元素.它可以被裝飾為一個BoxDecoration,如background、一個邊框或者一個陰影.它同樣具有margins、padding和應(yīng)用于其大小的約束constraints.
1.1.StatelessWidget和StatefulWidget
statelessWidget:表示無狀態(tài)的widget,內(nèi)部沒有保存狀態(tài),UI界面一經(jīng)創(chuàng)建不會發(fā)生改變.
statefulWidget:有狀態(tài)的widget,內(nèi)部有保存狀態(tài),當狀態(tài)發(fā)生改變,調(diào)用setState()方法,會觸發(fā)更新UI界面的操作.另外對于自定義繼承自StatefulWidget的子類,必須要重寫createState()方法.
StatelessWidget示例
import 'package:flutter/material.dart';
void main() => runApp(MyStatelessWidget(text: "Welcome"));
class MyStatelessWidget extends StatelessWidget {
final String text;
MyStatelessWidget({Key key, this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
text,
textDirection: TextDirection.ltr,
),
);
}
}
在上面的示例中,使用了MyStatelessWidget類的構(gòu)造函數(shù)傳遞標記為final的text。這個類繼承了StatelessWidget,它包含不可變數(shù)據(jù)。
statelessWidget的build方法通常只會在以下三種情況調(diào)用:
- 將widget插入樹中時
- 當widget的父級更改其配置時
- 當它依賴的InheritedWidget發(fā)生變化時
StatefulWidget示例
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
------------------------------------------------------------------------
class _HomePageState extends State<HomePage> {
int count=0;
@override
Widget build(BuildContext context) {
return Container(
child:Column(
children: <Widget>[
Chip(
label: Text("${this.count}")
),
RaisedButton(
child: Text('增加'),
onPressed: (){
setState(() {
this.count++;
});
},
)
],
)
);
}
}
上面的示例中,虛線上面的部分,聲明了一個StatefulWidget,它需要一個createState()方法。此方法創(chuàng)建管理widget狀態(tài)的狀態(tài)對象_HomePageState 。
2.如何判斷使用statelessWidget還是StatefulWidget的條件?
在Flutter中,widget是有狀態(tài)的還是無狀態(tài)的 , 取決于是否他們依賴于狀態(tài)的變化。
1.如果用戶交互或數(shù)據(jù)改變導(dǎo)致widget改變,那么它就是有狀態(tài)的。
2.如果一個widget是最終的或不可變的,那么它就是無狀態(tài)。
首先需要判斷widget的狀態(tài),簡單點,沒有數(shù)據(jù)驅(qū)動,信息不可變的,都是statelessWidget,F(xiàn)lutter本身也告訴你,優(yōu)先使用StatelessWidget。
還有一個重要的條件,F(xiàn)lutter并沒有實現(xiàn)數(shù)據(jù)雙向綁定,當你使用StatefulWidget時,你在 State.setState((){}) 中寫什么代碼都不重要,它僅用來標記這個State對象需要重新Build,重新build后根據(jù)已變更的數(shù)據(jù)來創(chuàng)建新的Widget,但是這個build帶來的消耗是巨大的,它會觸發(fā)父類底下每個子widget的build方法。
當某個父widget下,只有部分數(shù)據(jù)發(fā)生改變時,它還是會全局重新刷新,所以這對于個別場景是不適用的。對于網(wǎng)上的一些學(xué)習(xí)資料中提到的方法,我認為是行之有效的。
1.盡量將需要刷新的statefulWidget放在最小的子節(jié)點
2.根布局視圖不要使用statefulWidget
3.將會調(diào)用到setState((){}) 的代碼盡可能的和要更新的視圖封裝在一個盡可能小的模塊里。
4.如果一個Widget需要reBuild,那么它的子節(jié)點、兄弟節(jié)點、兄弟節(jié)點的子節(jié)點應(yīng)該盡可能少
3.Widget的生命周期
在iOS里的ViewController中,每個視圖控制器都有自己的生命周期,包含loadView,viewDidLoad等方法一樣,F(xiàn)lutter中的widget也有屬于自己生命周期。
| 方法 | 狀態(tài) |
|---|---|
| initState | 插入渲染樹時調(diào)用,只調(diào)用一次 |
| didChangeDependencies | state依賴的對象發(fā)生變化時調(diào)用 |
| build | 構(gòu)建widget時調(diào)用 |
| setState | 觸發(fā)視圖重建 |
| didUpdateWidget | 某個組件狀態(tài)發(fā)生改變時調(diào)用,可能會調(diào)用多次 |
| deactivate | 移除渲染樹時調(diào)用 |
| dispose | 組件即將銷毀時調(diào)用 |
上面的表格中,羅列了widget生命周期的各個階段。
initState:類似于iOS中ViewController的ViewDidLoad方法,
可以在此做一些初始化的設(shè)置,比如為某些狀態(tài)變量設(shè)置默認值。
didChangeDependencies: 用來專門處理 State 對象依賴關(guān)系變化,會在 initState() 調(diào)用結(jié)束后被調(diào)用。
build:構(gòu)建視圖,創(chuàng)建并返回一個widget。
setState:當狀態(tài)數(shù)據(jù)發(fā)生變化時,通過調(diào)用這個方法通知 Flutter 更新重構(gòu) Widget。
didUpdateWidget:當 Widget 的配置發(fā)生變化時,比如,父 Widget 觸發(fā)重建(即父 Widget 的狀態(tài)發(fā)生變化時),熱重載時,系統(tǒng)會調(diào)用這個函數(shù)。
deactivate:當組件的可見狀態(tài)發(fā)生變化時,deactivate 函數(shù)會被調(diào)用,這時 State 會被暫時從視圖樹中移除。當頁面切換時,由于 State 對象在視圖樹中的位置發(fā)生了變化,需要先暫時移除后再重新添加,重新觸發(fā)組件構(gòu)建,因為這個函數(shù)也會被調(diào)用。
dispose:從視圖樹中銷毀時調(diào)用。
4.Flutter中視圖的層級關(guān)系

如上圖所示,F(xiàn)lutter的視圖層級主要包含三個層級:widget,Element和RenderObject。下面我們就按照這三個層級,依次講述其中的知識點。
4.1Widget
首先是widget,按我的理解,它在這三者里應(yīng)該屬于組織者的角色,通過下面widget的源碼,我們做簡單分析。
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
/// 創(chuàng)建Widget對應(yīng)的Element對象,Element對象存儲了Widget的配置信息
@protected
Element createElement();
/// 判斷是否可以更新Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
widget這個class中,主要有以下兩個方法:
createElement:該方法主要是通過widget創(chuàng)建一個對應(yīng)的Element對象。
canUpdate:該方法主要是判斷widget是否可更新,通過比較新舊widget的runtimeType和key。說到這里就不不提新舊widget之間的重要判斷依據(jù)key。
Widget的標識符:Key
Key是所有Widget都擁有的重要屬性,但它并不是默認的,構(gòu)建某些復(fù)雜界面時,我們需要設(shè)置widget的key來提升性能。通過Key來比較新舊widget Tree。
4.2 Element
在這三者里,屬于協(xié)調(diào)者的角色。Element對象會同時持有widget對象和RenderObject對象,相當于是widget和RenderObject之間的橋梁。
它承載了視圖構(gòu)建的上下文數(shù)據(jù),Element與Widget是一對多的關(guān)系。由于Element是可變的,所以通過Element將Widget樹的變化做了抽象,可以將真正需要修改的部分同步到RenderObject樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。
我們在代碼中最??匆姷?strong>BuildContext,其實就是抽象的Element對象。
4.3 RenderObject
RenderObject中包含了所有用來渲染實例Widget的邏輯。它負責(zé)layout、painting和hit-testing。它的生成十分耗費性能,所以我們應(yīng)該盡可能的緩存它。我們與它打交道最多的時候就是在調(diào)試布局的時候。
| RenderObject | 抽象的Widget |
|---|---|
| layout | Column和Row |
| painting | Text和Image |
| hit-testing | GestureDetector |
通過上面的敘述,可以總結(jié)出三者的基本關(guān)系:
視圖由一個個獨立的Element節(jié)點構(gòu)成。組件最終的Layout、渲染都是通過RenderObejct來完成的,從創(chuàng)建到繪制渲染的大體流程是:根據(jù)Widget生成Element,然后創(chuàng)建相應(yīng)的RenderObejct并關(guān)聯(lián)到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。
Flutter這塊總是寫寫停停的,層級關(guān)系這部分還有許多更深的知識點,以后還是要繼續(xù)總結(jié)學(xué)習(xí),回顧舊知識,學(xué)習(xí)新知識。