Flutter中Widget爱只,State和BuildContext的概念是每個Flutter開發(fā)人員需要完全理解的最重要概念之一叫惊。這里先講解一下Widget以及Widget寞肖。三者之間的關系會在最后一篇總結一下。
Widget詳解
Widget類在Flutter中是非常重要的辩涝,繼承自Widget類的有PreferredSizeWidget贸伐、ProxyWidget、RenderObjectWidget怔揩、StatefulWidget捉邢、StatelessWidget。我們?nèi)粘J褂玫慕^大部分widget都是繼承自Widget類商膊,查看Widget類源碼伏伐,內(nèi)部實現(xiàn)非常簡單,構造函數(shù)如下:
const Widget({ this.key });
final Key key;
在flutter中構建APP是由widget樹構建起來的晕拆,所以這個key的作用是用來控制在widget樹中替換widget的時候使用的藐翎。其中Key類是Widget、Element以及SemanticsNode的唯一標識符实幕,繼承自Key的還有LocalKey以及GlobalKey吝镣。詳細可以去framework.dart文件查看相關源碼及說明。
Widget分類
在Flutter中昆庇,我們平時自定義的widget末贾,一般都是繼承自StatefulWidget或StatelessWidget(并不是只有這兩種),這兩種widget也是目前最常用的兩種凰锡。如果一個控件自身狀態(tài)不會去改變,創(chuàng)建了就直接顯示圈暗,不會有色值掂为、大小或者其他屬性的變化,這種widget一般都是繼承自StatelessWidget员串,常見的有Container勇哗、ScrollView等。如果一個控件需要動態(tài)的去改變或者相應一些狀態(tài)寸齐,例如點擊態(tài)欲诺、色值抄谐、內(nèi)容區(qū)域等,那么一般都是繼承自StatefulWidget扰法,常見的有CheckBox蛹含、AppBar、TabBar等塞颁。兩者的差別在于是否有狀態(tài)浦箱。
-
StatelessWidget
一個StatelessWidget可以用多個不同的BuildContext構建,它是由build函數(shù)構建的祠锣。BuildContext抽象類酷窥,它表示一個控件在整個控件樹中的位置句柄,每個控件都有自己的BuildContext實例伴网。某些靜態(tài)函數(shù)(例如showDialog蓬推、Theme.of等)也有BuildContext實例,以便它們可以代表調(diào)用控件或?qū)iT針對給定上下文獲取數(shù)據(jù)澡腾。BuildContext對象被傳遞給WidgetBuilder函數(shù)沸伏,它為創(chuàng)建控件的函數(shù)簽名,例如StatelessWidget.build或State.build蛋铆。無狀態(tài)窗口小部件只能在加載/構建窗口小部件時繪制一次馋评,這意味著無法基于任何事件或用戶操作重繪窗口小部件。
圖片2.png
對于StatelessWidget刺啦,build方法會在如下三種情況下調(diào)用:
- widget第一次被插入到樹中留特;
- widget的父節(jié)點更改了配置(configuration);
- widget依賴的InheritedWidget(InheritedWidget是一個特殊的Widget玛瘸,您可以將其作為另一個子樹的父級放在Widgets樹中蜕青。該子樹的所有小部件都必須能夠與該InheritedWidget公開的數(shù)據(jù)進行交互,后面會詳細總結)改變了糊渊。
import 'package:flutter/material.dart';
import 'package:flutter_apptest/SplashPage.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
MyApp ({
Key key,
this.parameter,
}): super(key:key);
final parameter;
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: SplashPage(),//啟動MainPage
);
}
}
-
StatefulWidget
其他一些小部件將處理一些在Widget生命周期內(nèi)會發(fā)生變化的內(nèi)部數(shù)據(jù)右核。因此,該數(shù)據(jù)變得動態(tài)渺绒。
此Widget保存的數(shù)據(jù)集可能會在此Widget的生命周期內(nèi)發(fā)生變化贺喝,稱為State。
這些窗口小部件稱為有狀態(tài)窗口小部件(Stateful Widget)宗兼。
這樣的Widget的示例可以是用戶可以選擇的復選框列表或者根據(jù)條件禁用的Button躏鱼。
StatefulWidget創(chuàng)建需要一個State狀態(tài),通過createState函數(shù)返回殷绍。而一個StatefulWidget會為每個BuildContext創(chuàng)建一個State對象染苛。
圖片3.png
在了解這些狀態(tài)的時候,更多的是了解State狀態(tài)
import 'package:flutter/material.dart';
class MinePage extends StatefulWidget{
//此部分在Widget的生命周期內(nèi)不會發(fā)生變化主到,但可能接受可由其相應的State實例使用的參數(shù)茶行。請注意躯概,在Widget的第一部分定義的任何變量通常在其生命周期內(nèi)不會更改。
MyStatefulWidget({
Key key,
this.color,
}): super(key: key);
final Color color;
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new Page();
}
}
class Page extends State<MinePage>{
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return layout(context);
}
Widget layout(BuildContext context) {
// 改變狀態(tài)加以重新繪制
setState(() {
});
return new Scaffold(
appBar: buildAppBar(context),
body: new Text('我的頁面'),
);
}
buildAppBar(BuildContext context) {
return new AppBar(title: const Text('我的'),);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
}
我們在創(chuàng)建State的時候可以看到和StatefulWidget相似的build方法畔师,也就是說我們也可以獲得一個BuildContext娶靡,在使用StatefulWidget.createState創(chuàng)建它們之前以及在調(diào)用initState之前,框架將State對象與BuildContext關聯(lián)起來茉唉,該關聯(lián)是永久的:State對象永遠不會改變它的BuildContext(但是BuildContext本身可以在控件樹中移動)固蛾。后面講解一下這個BuildContext對象在整個程序中什么角色
State
State的作用有兩點:
- 在widget構建的時候可以被同步讀取度陆;
- 在widget的生命周期中可能會被改變艾凯。
State的生命周期有四種狀態(tài):
- created:當State對象被創(chuàng)建時候,State.initState方法會被調(diào)用懂傀;
- initialized:當State對象被創(chuàng)建趾诗,但還沒有準備構建時,State.didChangeDependencies在這個時候會被調(diào)用蹬蚁;
- ready:State對象已經(jīng)準備好了構建恃泪,State.dispose沒有被調(diào)用的時候;
- defunct:State.dispose被調(diào)用后犀斋,State對象不能夠被構建贝乎。
完整生命周期如下:
- 創(chuàng)建一個State對象時,會調(diào)用StatefulWidget.createState叽粹;
- 和一個BuildContext相關聯(lián)览效,可以認為被加載了(mounted);
- 調(diào)用initState虫几;
- 調(diào)用didChangeDependencies锤灿;
- 經(jīng)過上述步驟,State對象被完全的初始化了辆脸,調(diào)用build但校;
- 如果有需要,會調(diào)用didUpdateWidget啡氢;
- 如果處在開發(fā)模式状囱,熱加載會調(diào)用reassemble;
- 如果它的子樹(subtree)包含需要被移除的State對象倘是,會調(diào)用deactivate亭枷;
- 調(diào)用dispose,State對象以后都不會被構建;
- 當調(diào)用了dispose,State對象處于未加載(unmounted)辨绊,已經(jīng)被dispose的State 對象沒有辦法被重新加載(remount)奶栖。
當控件的配置被更改時會調(diào)用State.didUpdateWidget方法匹表,此時框架會重新繪制控件门坷。你也可以使用State.setState方法在狀態(tài)發(fā)生變化時通知框架宣鄙,告訴框架該對象的內(nèi)部狀態(tài)已經(jīng)改變,框架接到通知后也會重新繪制控件默蚌。
State常用方法
initState()
initState()方法是創(chuàng)建State對象后要調(diào)用的第一個方法(在構造函數(shù)之后)冻晤。
需要執(zhí)行其他初始化時,將覆蓋重寫此方法绸吸。典型的初始化與動畫鼻弧,控制器有關…
如果重寫此方法,則需要在第一個位置調(diào)用super.initState()方法锦茁。
在這個方法中攘轩,上下文context可用,但你還不能真正使用它码俩,因為框架還沒有完全將狀態(tài)與它相關聯(lián)度帮。
initState()方法完成后,State對象現(xiàn)在已初始化稿存,上下文可用笨篷。在此State對象的生命周期內(nèi)不再調(diào)用此方法。didChangeDependencies()
didChangeDependencies()方法是要調(diào)用的第二個方法瓣履。
在此階段率翅,由于上下文可用,您可以使用它袖迎。
如果您的Widget鏈接到InheritedWidget和/或您需要初始化一些偵聽器(基于BuildContext)冕臭,則通常會覆蓋此方法。請注意瓢棒,如果您的窗口小部件鏈接到InheritedWidget浴韭,則每次重建此窗口小部件時都會調(diào)用此方法。build()
build(BuildContext context)方法在didChangeDependencies()(和didUpdateWidget)之后調(diào)用脯宿。
這是您構建窗口小部件(可能還有任何子樹)的位置念颈。
每次State對象更改時(或者當InheritedWidget需要通知“已注冊”的小部件時)都會調(diào)用此方法!
為了強制重建连霉,您可以調(diào)用setState((){...})方法榴芳。dispose()
放棄窗口小部件時調(diào)用dispose()方法。
如果你需要執(zhí)行一些清理(例如監(jiān)聽器跺撼,控制器…)窟感,然后立即調(diào)用super.dispose(),則覆蓋此方法歉井。
State.setState
State中比較重要的一個方法是setState柿祈,當修改狀態(tài)時,widget會被更新。比方說點擊CheckBox躏嚎,會出現(xiàn)選中和非選中狀態(tài)之間的切換蜜自,就是通過修改狀態(tài)來達到的。查看setState源碼卢佣,在一些異常的情況下將會拋出異常:
- 傳入的為null重荠;
- 處在defunct階段;
- created階段還沒有被加載(mounted)虚茶;
- 參數(shù)返回一個Future對象戈鲁。
檢查完一系列異常后,最后調(diào)用代碼如下:
_element.markNeedsBuild();
markNeedsBuild內(nèi)部嘹叫,則是通過標記element為diry婆殿,在下一幀的時候重建(rebuild)≌稚龋可以看出setState并不是立即生效鸣皂,它只是將widget進行了標記,真正的rebuild操作暮蹂,則是等到下一幀的時候才會去進行寞缝。
StatefulWidget的兩個主要類別:
- 在initState中創(chuàng)建資源,在dispose中銷毀仰泻,但是不依賴于InheritedWidget或者調(diào)用setState方法荆陆,這類widget基本上用在一個應用或者頁面的root;
- 使用setState或者依賴于InheritedWidget集侯,這種在營業(yè)生命周期中會被重建(rebuild)很多次被啼。
總結一句話就是在每一幀的繪制過程,StatefulWidget會根據(jù)State或者配置變了來rebuild下一幀棠枉,也就是上面圖所說的弄臟了和下一幀又干凈了浓体。
選擇無狀態(tài)還是有狀態(tài)小部件?
在我的小部件的生命周期中辈讶,我是否需要考慮一個將要更改的變量命浴,何時更改,將強制重建小部件贱除?
如果問題的答案是肯定的生闲,那么您需要一個有狀態(tài)的小部件,否則月幌,您需要一個無狀態(tài)小部件碍讯。
比如:
- 用于顯示復選框列表的小組件。要顯示復選框扯躺,您需要考慮一系列項目捉兴。每個項目都是一個具有標題和狀態(tài)的對象蝎困。如果單擊復選框,則切換相應的item.status;在這種情況下倍啥,您需要使用有狀態(tài)窗口小部件來記住項目的狀態(tài)难衰,以便能夠重繪復選框。
- 帶有表格的屏幕逗栽。該屏幕允許用戶填寫表單的窗口小部件并將表單發(fā)送到服務器。在這種情況下失暂,在這種情況下彼宠,除非您在提交表單之前需要驗證表單或執(zhí)行任何其他操作,否則無狀態(tài)窗口小部件可能就足夠了弟塞。