本文介紹了Flutter應(yīng)用程序中Widget,State勤家,Context和InheritedWidget的重要概念腹尖。特別注意InheritedWidget,它是最重要且記錄較少的Widget之一伐脖。
難度:初學(xué)者
前言
在Flutter中Widget, State和Context是一個(gè)非常重要的概念桐臊,每一個(gè)開(kāi)發(fā)人員需要充分了解。
但是晓殊,文檔量巨大断凶,并不能總是清楚地解釋這個(gè)概念。
我會(huì)用自己知識(shí)來(lái)解釋這些概念巫俺,本文的真正目的是試圖澄清以下內(nèi)容:
- Stateful和Stateless Widget的區(qū)別
- 什么是上下文Context
- 什么是state以及如何使用它
- context與state之間的關(guān)系
- InheritedWidget以及在 Widgets tree中傳播信息的方式
- rebuild的概念
本文也適用于Medium-Flutter社區(qū)认烁。
第1部分:概念
Widget的概念
在Flutter中,幾乎所有東西都是Widget介汹。
將Widget視為可視組件(或與應(yīng)用程序的可視方面交互的組件)却嗡。
當(dāng)您需要構(gòu)建與布局直接或間接相關(guān)的任何內(nèi)容時(shí),您正在使用** Widget**嘹承。
Widget Tree的概念
** Widgets**以樹(shù)結(jié)構(gòu)組織竿音。
包含其他Widgets小部件稱為Parent Widget(或Widget container)辉巡。包含在Parent WidgetWidgets部件稱為Children Widgets拍顷。
讓我們用Flutter自動(dòng)生成的基礎(chǔ)應(yīng)用程序來(lái)說(shuō)明這一點(diǎn)。這是簡(jiǎn)化的代碼坪它,僅限于build方法:
@override
Widget build(BuildContext){
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),
),
);
}
如果我們現(xiàn)在考慮這個(gè)基本示例,我們將獲得以下Widgets樹(shù)結(jié)構(gòu)(限制代碼中存在的Widgets列表):
語(yǔ)境概念
另一個(gè)重要的概念是Context帝牡。
*context *只是一個(gè)Widget在所有構(gòu)建的Widget的樹(shù)結(jié)構(gòu)中的位置的引用往毡。。
簡(jiǎn)而言之靶溜,將context視為Widgets樹(shù)的一部分开瞭,其中Widget附加到此樹(shù)。
Context與Widget一一對(duì)應(yīng)罩息。
如果一個(gè)Widget A有子Widget嗤详,Widget A的context將成為一級(jí)子Widget Context的Parent Context。
讀到這一點(diǎn)瓷炮,很明顯断楷,Context是鏈接的,并且正在組成一個(gè)Context樹(shù)(父子關(guān)系)崭别。
如果我們現(xiàn)在嘗試在上圖中說(shuō)明Context的概念,我們獲得(仍然是一個(gè)非常簡(jiǎn)化的視圖)每種顏色代表一個(gè)Context(除了MyApp恐锣,它是不同的):
Context可見(jiàn)性(簡(jiǎn)而言之):
某些Widget只能在其自己的Context中或在其父Context中可見(jiàn)茅主。
由子Context中我們很容易找到一個(gè)ancestor(= parent)Widget。
一個(gè)例子是土榴,考慮Scaffold> Center> Column> Text:context.ancestorWidgetOfExactType(Scaffold)=>通過(guò)從Text Context轉(zhuǎn)到樹(shù)結(jié)構(gòu)來(lái)返回第一個(gè)Scaffold诀姚。
從父Context中,也可以找到后代(=子)Widget玷禽,但不建議這樣做(我們稍后會(huì)討論)赫段。
Widget的類型
Widget有兩種類型:
Stateless Widget
這些可視組件中的一些除了它們自己的配置信息之外不依賴于任何其他信息,該信息在其直接父級(jí)構(gòu)建時(shí)提供矢赁。
換句話說(shuō)糯笙,這些Widget一旦創(chuàng)建就不必關(guān)心任何變體。
這些小部件稱為Stateless Widget撩银。
這種小部件的典型示例可以是Text给涕,Row,Column额获,Container ......其中够庙,在構(gòu)建時(shí),我們只是將一些參數(shù)傳遞給它們抄邀。
參數(shù)可以是裝飾(decoration)耘眨,尺寸(dimensions)甚至其他Widget中的任何內(nèi)容。不要緊境肾。唯一重要的是這個(gè)配置一旦應(yīng)用剔难,在下一個(gè)構(gòu)建過(guò)程之前不會(huì)改變胆屿。
無(wú)狀態(tài)Widget只能在加載/構(gòu)建(loaded/build)時(shí)才繪制一次,這意味著無(wú)法基于任何事件或用戶操作重繪該Widget钥飞。
Stateless Widget生命周期
這是與Stateless Widget相關(guān)的代碼的典型結(jié)構(gòu)莺掠。
如您所見(jiàn),我們可以將一些額外的參數(shù)傳遞給它的構(gòu)造函數(shù)读宙。但是彻秆,請(qǐng)記住,這些參數(shù)將不改變?cè)谝院箅A段结闸。
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({
Key key,
this.parameter,
}): super(key:key);
final parameter;
@override
Widget build(BuildContext context){
return new ...
}
}
即使有另一種方法可以被覆蓋(createElement)唇兑,后者也幾乎不會(huì)被覆蓋。唯一需要被覆蓋的是構(gòu)建(build)桦锄。
這種Stateless Widget的生命周期很簡(jiǎn)單:
- 初始化
- 通過(guò)build()渲染
Stateful Widget
其他一些小部件將處理一些在Widget生命周期內(nèi)會(huì)發(fā)生變化的內(nèi)部數(shù)據(jù)扎附。因此,該數(shù)據(jù)變得動(dòng)態(tài)结耀。
此Widget保存的數(shù)據(jù)集在此Widget的生命周期中可能會(huì)有所不同留夜,稱為State。
這些窗口小部件稱為Stateful Widget图甜。
此類Widget的示例可以是用戶可以選擇的復(fù)選框列表碍粥,也可以是根據(jù)條件禁用的Button。
State概念
一個(gè)** State**界定的“ 行為一部份” StatefulWidget實(shí)例黑毅。
它包含旨在與Widget 交互/干擾的信息:
- 行為
- 布局
應(yīng)用于State的任何更改都會(huì)強(qiáng)制Widget 重建嚼摩。
State與Context之間的關(guān)系
對(duì)于Stateful Widget,State與Context相關(guān)聯(lián)矿瘦。此關(guān)聯(lián)是永久性的枕面,State對(duì)象永遠(yuǎn)不會(huì)更改其Context。
即使可以在樹(shù)結(jié)構(gòu)周?chē)苿?dòng)Widget Context缚去,State仍將與該Context相關(guān)聯(lián)潮秘。
當(dāng)State與Context關(guān)聯(lián)時(shí),State被視為已掛載(mounted)易结。
超重要點(diǎn):
和Context相關(guān)聯(lián)的State對(duì)象唇跨,State對(duì)象是不能(直接)通過(guò)另一個(gè)Context來(lái)訪問(wèn)!(我們將在稍后討論這個(gè)問(wèn)題)衬衬。
Stateful Widget生命周期
既然已經(jīng)引入了基本概念买猖,那么現(xiàn)在是時(shí)候深入了解......
這是與Stateful Widget相關(guān)的典型代碼結(jié)構(gòu)。
由于本文的主要目的是用“變量(variable)”數(shù)據(jù)來(lái)解釋State的概念滋尉,我將故意跳過(guò)與某些Stateful Widget overridable方法相關(guān)的任何解釋玉控,這些方法與此沒(méi)有特別的關(guān)系。這些可* overridable方法是didUpdateWidget狮惜,deactivate高诺,reassemble*碌识。這些將在下一篇文章中討論。
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key key,
this.parameter,
}): super(key: key);
final parameter;
@override
_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
void initState(){
super.initState();
// Additional initialization of the State
}
@override
void didChangeDependencies(){
super.didChangeDependencies();
// Additional code
}
@override
void dispose(){
// Additional disposal code
super.dispose();
}
@override
Widget build(BuildContext context){
return new ...
}
}
下圖顯示了與創(chuàng)建Stateful Widget相關(guān)的操作/調(diào)用序列(簡(jiǎn)化版本)虱而。在圖的右側(cè)筏餐,您將注意到流中的State對(duì)象的內(nèi)部狀態(tài)。您還將看到Context與State關(guān)聯(lián)的時(shí)刻牡拇,從而變?yōu)榭捎茫╩ounted)魁瞪。
所以讓我們用一些額外的細(xì)節(jié)來(lái)解釋它:
initState()
該initState()方法是第一個(gè)方法(構(gòu)造函數(shù)后面),一旦State對(duì)象被創(chuàng)建被調(diào)用惠呼。需要執(zhí)行其他初始化時(shí)导俘,將覆蓋此方法。典型的初始化與動(dòng)畫(huà)剔蹋,控制器有關(guān)......如果重寫(xiě)此方法旅薄,則需要首先調(diào)用super.initState()方法。
在這個(gè)方法中泣崩,Context可用但你還不能真正使用它少梁,因?yàn)榭蚣苓€沒(méi)有完全將狀態(tài)與它相關(guān)聯(lián)。
一旦initState()方法完成矫付,State對(duì)象現(xiàn)在被初始化并且Context可用凯沪。
在此State對(duì)象的生命周期內(nèi)不再調(diào)用此方法。
didChangeDependencies()
所述didChangeDependencies()方法是將被調(diào)用的第二方法技即。
在此階段,由于Context可用樟遣,您可以使用它而叼。
如果您的Widget鏈接到InheritedWidget和/或您需要初始化某些監(jiān)聽(tīng)(listeners)(基于Context),則通常會(huì)覆蓋此方法豹悬。
請(qǐng)注意葵陵,如果您的窗口小部件鏈接到InheritedWidget,則每次重建此Widget時(shí)都會(huì)調(diào)用此方法瞻佛。
如果重寫(xiě)此方法脱篙,則應(yīng)首先調(diào)用super.didChangeDependencies()。
build()
build(BuildContext context)方法在didChangeDependencies() (和didUpdateWidget)之后調(diào)用伤柄。
這是您構(gòu)建Widget(可能還有任何子樹(shù))的地方绊困。
每次State對(duì)象更改時(shí)(或者當(dāng)InheritedWidget需要通知“ 已注冊(cè) ”的Widget時(shí))都會(huì)調(diào)用此方法!
為了強(qiáng)制重建适刀,您可以調(diào)用*setState((){…}) *方法秤朗。
dispose()
dispose()方法在Widget被銷(xiāo)毀時(shí)調(diào)用。
如果需要執(zhí)行一些清理(例如監(jiān)聽(tīng)器)笔喉,則重寫(xiě)此方法取视,然后立即調(diào)用super.dispose()硝皂。
Stateless or Stateful Widget?
這是許多開(kāi)發(fā)人員需要問(wèn)自己的問(wèn)題:我是否需要我的Widget無(wú)狀態(tài)或有狀態(tài)作谭?
為了回答這個(gè)問(wèn)題稽物,請(qǐng)問(wèn)問(wèn)自己:
在我的Widget的生命周期中,我是否需要考慮一個(gè)將要更改的變量折欠,何時(shí)更改贝或,將強(qiáng)制** rebuilt**Widget?
如果問(wèn)題的答案是肯定的怨酝,那么您需要一個(gè)有狀態(tài)Widget傀缩,否則,您需要一個(gè)無(wú)狀態(tài)Widget农猬。
一些例子:
-
用于顯示復(fù)選框列表的Widget赡艰。要顯示復(fù)選框,您需要考慮一系列項(xiàng)目斤葱。每個(gè)項(xiàng)目都是一個(gè)具有標(biāo)題和狀態(tài)的對(duì)象慷垮。如果單擊復(fù)選框,則切換相應(yīng)的item.status;
在這種情況下揍堕,您需要使用StatefulWidget來(lái)記住項(xiàng)目的狀態(tài)料身,以便能夠重繪復(fù)選框。
-
帶有表格的屏幕衩茸。該屏幕允許用戶填寫(xiě)表單的Widget并將表單發(fā)送到服務(wù)器芹血。
在這種情況下,除非你需要驗(yàn)證表或提交之前楞慈,做任何其他動(dòng)作幔烛,一個(gè)StatelessWidget可能就足夠了。
Stateful Widget由2部分組成
還記得Stateful小部件的結(jié)構(gòu)嗎囊蓝?有兩個(gè)部分:
Widget的主要定義
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key key,
this.color,
}): super(key: key);
final Color color;
@override
_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}
第一部分“ MyStatefulWidget ” 通常是Widget 的公共部分饿悬。當(dāng)您要將其添加到窗口小部件樹(shù)時(shí),可以實(shí)例化此部件聚霜。此部分在Widget的生命周期內(nèi)不會(huì)發(fā)生變化狡恬,但可能接受可能由其相應(yīng)的State實(shí)例使用的參數(shù)。
請(qǐng)注意蝎宇,在Widget的第一部分級(jí)別定義的任何變量通常 不會(huì)在其生命周期內(nèi)發(fā)生變化弟劲。
Widget State定義
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
...
@override
Widget build(BuildContext context){
...
}
}
第二部分“ _MyStatefulWidgetState ”是在Widget的生命周期中變化的部分,并強(qiáng)制每次應(yīng)用修改時(shí)重建Widget的這個(gè)特定實(shí)例姥芥。名稱開(kāi)頭的“ _ ”字符使該類對(duì).dart文件是私有的函卒。
如果需要在.dart文件之外引用此類,請(qǐng)不要使用“ _ ”前綴。
所述_MyStatefulWidgetState類可以訪問(wèn)被存儲(chǔ)在任何可變MyStatefulWidget报嵌,使用Widget虱咧。{變量名稱}。在此示例中:widget.color
Widget唯一標(biāo)識(shí)Key
在Flutter中锚国,每個(gè)Widget都是唯一標(biāo)識(shí)的腕巡。這個(gè)唯一標(biāo)識(shí)由構(gòu)建/渲染時(shí)的框架定義。
此唯一標(biāo)識(shí)對(duì)應(yīng)于可選的Key參數(shù)血筑。如果省略绘沉,F(xiàn)lutter將為您生成一個(gè)。
在某些情況下豺总,您可能需要強(qiáng)制使用此Key车伞,以便可以通過(guò)其key訪問(wèn)Widget。
為此喻喳,您可以使用以下幫助程序之一:GlobalKey另玖,LocalKey,UniqueKey或ObjectKey表伦。
該GlobalKey確保關(guān)鍵是在整個(gè)應(yīng)用程序唯一的谦去。
強(qiáng)制使用Widget的唯一標(biāo)識(shí):
GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
return new MyWidget(
key: myKey
);
}
第2部分:如何訪問(wèn)State?
如前所述蹦哼,State鏈接到一個(gè)Context鳄哭,Context鏈接到Widget的一個(gè)實(shí)例對(duì)象。
1. Widget本身
從理論上講纲熏,唯一能夠訪問(wèn)State是Widget State本身妆丘。
在這種情況下,沒(méi)有困難局劲。Widget State類訪問(wèn)其任何變量勺拣。
2.一個(gè)直接的子Widget
有時(shí),父Widget可能需要訪問(wèn)其直接子節(jié)點(diǎn)的State才能執(zhí)行特定任務(wù)容握。
在這種情況下宣脉,要訪問(wèn)這些直接的子State车柠,您需要了解它們剔氏。
給某人打電話的最簡(jiǎn)單方法是通過(guò)名字。在Flutter中竹祷,每個(gè)Widget都有一個(gè)唯一的標(biāo)識(shí)谈跛,它由框架在構(gòu)建/渲染時(shí)確定。如前所示塑陵,您可以使用key參數(shù)強(qiáng)制使用Widget的標(biāo)識(shí)感憾。
...
GlobalKey<MyStatefulWidgetState> myWidgetStateKey = new GlobalKey<MyStatefulWidgetState>();
...
@override
Widget build(BuildContext context){
return new MyStatefulWidget(
key: myWidgetStateKey,
color: Colors.blue,
);
}
一旦確定StateKey,父 Widget可以通過(guò)以下方式訪問(wèn)其子級(jí)的State:
myWidgetStateKey.currentState
讓我們考慮一個(gè)基本示例令花,當(dāng)用戶點(diǎn)擊按鈕時(shí)顯示SnackBar阻桅。由于SnackBar是Scaffold的子Widget凉倚,它不能直接訪問(wèn)Scaffold身體的任何其他孩子(請(qǐng)記住Context的概念及其層次結(jié)構(gòu)/樹(shù)結(jié)構(gòu)?)嫂沉。因此稽寒,訪問(wèn)它的唯一方法是通過(guò)ScaffoldState,它公開(kāi)一個(gè)公共方法來(lái)顯示SnackBar趟章。
class _MyScreenState extends State<MyScreen> {
/// the unique identity of the Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context){
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('My Screen'),
),
body: new Center(
new RaiseButton(
child: new Text('Hit me'),
onPressed: (){
_scaffoldKey.currentState.showSnackBar(
new SnackBar(
content: new Text('This is the Snackbar...'),
)
);
}
),
),
);
}
}
3.Ancestor Widget
假設(shè)您有一個(gè)屬于另一個(gè)Widget的子樹(shù)的Widget杏糙,如下圖所示。
為了實(shí)現(xiàn)這一目標(biāo)蚓土,需要滿足3個(gè)條件:
1.“ Widget with State ”(紅色)需要暴露其State
為了公開(kāi)它的State宏侍,Widget需要在創(chuàng)建時(shí)記錄它,如下所示:
class MyExposingWidget extends StatefulWidget {
MyExposingWidgetState myState;
@override
MyExposingWidgetState createState(){
myState = new MyExposingWidgetState();
return myState;
}
}
2.“ Widget State ”需要暴露一些getter / setter
為了讓“ * stranger* ” set/get State的屬性蜀漆,Widget State需要通過(guò)以下方式授權(quán)訪問(wèn):
- public property(不推薦)
- getter / setter
例:
class MyExposingWidgetState extends State<MyExposingWidget>{
Color _color;
Color get color => _color;
...
}
3.“ 對(duì)于想要獲取State對(duì)象的的Widget ”(藍(lán)色)需要獲得對(duì)State對(duì)象的的引用
class MyChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
final MyExposingWidgetState state = widget?.myState;
return new Container(
color: state == null ? Colors.blue : state.color,
);
}
}
這個(gè)解決方案很容易實(shí)現(xiàn)谅河,但子Widget如何知道它何時(shí)需要重建?
有了這個(gè)解決方案嗜愈,它沒(méi)有旧蛾。它必須等待重建才能刷新其內(nèi)容,這不是很方便蠕嫁。
下一節(jié)將討論Inherited Widget的概念锨天,它可以解決這個(gè)問(wèn)題。
InheritedWidget
簡(jiǎn)而言之剃毒,InheritedWidget允許在Widget樹(shù)中有效地傳播(和共享)信息病袄。
InheritedWidget是一個(gè)特殊的Widget,您將在widgets樹(shù)中作為另一個(gè)子樹(shù)的父Widget赘阀。該子樹(shù)的所有Widget都必須能夠與該InheritedWidget公開(kāi)的數(shù)據(jù)進(jìn)行交互益缠。
Basics
為了解釋它,讓我們考慮以下代碼:
class MyInheritedWidget extends InheritedWidget {
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}): super(key: key, child: child);
final data;
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
此代碼定義了一個(gè)名為“ MyInheritedWidget ” 的Widget基公,旨在“ 共享 ”所有Widget(子子樹(shù)的一部分)中的某些數(shù)據(jù)幅慌。
如前所述,為了能夠傳播/共享某些數(shù)據(jù)轰豆,需要將InheritedWidget定位在Widget的頂部胰伍,這解釋了傳遞給InheritedWidget基礎(chǔ)構(gòu)造函數(shù)的@required Widget child。
static MyInheritedWidget of(BuildContext context)方法酸休,允許所有Widget獲取最接近Context的MyInheritedWidget的實(shí)例(參見(jiàn)后面的內(nèi)容)骂租。
最后,重寫(xiě)updateShouldNotify方法用于告訴InheritedWidget斑司,如果對(duì)數(shù)據(jù)應(yīng)用了修改(請(qǐng)參閱下文)渗饮,是否必須將通知傳遞給所有子Widget(已注冊(cè)/已訂閱)。
因此,我們需要將它放在樹(shù)節(jié)點(diǎn)級(jí)別互站,如下所示:
class MyParentWidget... {
...
@override
Widget build(BuildContext context){
return new MyInheritedWidget(
data: counter,
child: new Row(
children: <Widget>[
...
],
),
);
}
}
子Widget如何訪問(wèn)InheritedWidget的數(shù)據(jù)私蕾?
在構(gòu)建子進(jìn)程時(shí),后者將獲得對(duì)InheritedWidget的引用胡桃,如下所示:
class MyChildWidget... {
...
@override
Widget build(BuildContext context){
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
///
/// From this moment, the widget can use the data, exposed by the MyInheritedWidget
/// by calling: inheritedWidget.data
///
return new Container(
color: inheritedWidget.data.color,
);
}
}
如何在Widget之間進(jìn)行交互是目?
請(qǐng)考慮以下顯示W(wǎng)idget樹(shù)結(jié)構(gòu)的圖表。
為了說(shuō)明一種交互方式标捺,我們假設(shè)如下:
- “Widget A”是一個(gè)將項(xiàng)目添加到購(gòu)物車(chē)的按鈕;
- “Widget B”是一個(gè)顯示購(gòu)物車(chē)中商品數(shù)量的文本;
- “Widget C”位于小部件B旁邊懊纳,是一個(gè)內(nèi)置任何文本的文本;
- 我們希望“Widget B”在按下“Widget A”時(shí)自動(dòng)在購(gòu)物車(chē)中顯示正確數(shù)量的項(xiàng)目,但我們不希望重建“Widget C”
InheritedWidget在此是最為合適的Widget亡容!
代碼示例
我們先寫(xiě)下代碼嗤疯,然后解釋如下:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
@override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
@override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
@override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
@override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
@override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
說(shuō)明
在這個(gè)非常基本的例子中闺兢,
- _MyInherited是一個(gè)InheritedWidget茂缚,每次我們通過(guò)點(diǎn)擊“Widget A”按鈕添加一個(gè)Item時(shí)都會(huì)重新創(chuàng)建
- MyInheritedWidget是一個(gè)Widget,其狀態(tài)包含Items列表屋谭〗拍遥可以通過(guò)(BuildContext context)的靜態(tài)MyInheritedWidgetState訪問(wèn)此狀態(tài)
- MyInheritedWidgetState公開(kāi)一個(gè)getter(itemsCount)和一個(gè)方法(addItem),以便它們可以被小部件使用桐磁,這是子小部件樹(shù)的一部分
- 每次我們將一個(gè)Item添加到State時(shí)悔耘,MyInheritedWidgetState都會(huì)重建
- MyTree類只是構(gòu)建一個(gè)小部件樹(shù),將MyInheritedWidget作為樹(shù)的父級(jí)
- WidgetA是一個(gè)簡(jiǎn)單的RaisedButton我擂,當(dāng)按下它時(shí)衬以,從最近的MyInheritedWidget調(diào)用addItem方法
- WidgetB是一個(gè)簡(jiǎn)單的文本,顯示最接近的 MyInheritedWidget級(jí)別的項(xiàng)目數(shù)
這一切如何運(yùn)作校摩?
注冊(cè)Widget以供以后通知
當(dāng)子Widget調(diào)用MyInheritedWidget.of(context)時(shí)看峻,傳遞它自己的context,它調(diào)用MyInheritedWidget的以下方法衙吩。
static MyInheritedWidgetState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
在內(nèi)部互妓,除了簡(jiǎn)單地返回MyInheritedWidgetState的實(shí)例之外,它還將將通知傳遞給所有子Widget(已注冊(cè)/已訂閱)坤塞。
在場(chǎng)景后面冯勉,對(duì)這個(gè)靜態(tài)方法的簡(jiǎn)單調(diào)用實(shí)際上做了兩件事:
- 消費(fèi)者(consumer)Widget被自動(dòng)添加到(subscribers)列表中,之后如果InheritedWidget(這里_MyInherited)數(shù)據(jù)被修改尺锚,會(huì)被重建
- _MyInheritedWidget(又名MyInheritedWidgetState)中引用的數(shù)據(jù)將返回給使用者(consumer)
流
由于'Widget A'和'Widget B'都已使用InheritedWidget訂閱珠闰,因此如果對(duì)_MyInherited應(yīng)用了修改惜浅,則當(dāng)單擊Widget A 的RaisedButton時(shí)瘫辩,操作流程如下(簡(jiǎn)化版本):
- 調(diào)用MyInheritedWidgetState的addItem方法
- MyInheritedWidgetState.addItem方法將新項(xiàng)添加到List
- 調(diào)用setState()以重建MyInheritedWidget
- 使用List的新內(nèi)容創(chuàng)建_MyInherited的新實(shí)例
- _MyInherited記錄在參數(shù)(數(shù)據(jù))中傳遞的新State
- 作為InheritedWidget,它會(huì)檢查是否有需要通知的消費(fèi)者(答案為真)
- 它迭代整個(gè)消費(fèi)者列表(這里是Widget A和Widget B)并請(qǐng)求他們重建
- 由于Wiget C不是消費(fèi)者,因此不會(huì)重建伐厌。
但是承绸,Widget A和Widget B都重建了,而重建Wiget A卻沒(méi)用挣轨,因?yàn)樗鼪](méi)有任何改變军熏。如何防止這種情況發(fā)生?
訪問(wèn)“Inherited Widget”Widget時(shí)阻止某些Widget重建
Widget A也被重建的原因來(lái)自它訪問(wèn)MyInheritedWidgetState的方式卷扮。
如前所述荡澎,調(diào)用context.inheritFromWidgetOfExactType()方法的事實(shí)會(huì)自動(dòng)將Widget訂閱到使用者列表中。
該解決方案晤锹,以防止該自動(dòng)訂閱摩幔,同時(shí)仍然允許該Widget A訪問(wèn)MyInheritedWidgetState是改變的靜態(tài)方法MyInheritedWidget如下:
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
通過(guò)添加布爾額外參數(shù)...
- 如果rebuild參數(shù)為true(默認(rèn)情況下),我們使用普通方法(并且Widget將添加到訂閱者列表中)
- 如果rebuild參數(shù)是假的鞭铆,我們?nèi)匀豢梢栽L問(wèn)數(shù)據(jù)或衡,但不使用內(nèi)部實(shí)現(xiàn)的的InheritedWidget
因此,要完成解決方案车遂,我們還需要稍微更新Widget A的代碼封断,如下所示(我們添加false額外參數(shù)):
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context, false);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
它就是,按下它時(shí)不再重建Widget A.
特別注意Routes, Dialogs…
Routes, Dialog Context與應(yīng)用程序綁定舶担。
這意味著即使在屏幕A內(nèi)部您要求顯示另一個(gè)屏幕B(例如坡疼,在當(dāng)前的屏幕上),也無(wú)法輕松地從兩個(gè)屏幕中的任何一個(gè)屏幕關(guān)聯(lián)它們自己的Context衣陶。
屏幕B了解屏幕A上下文的唯一方法是從屏幕A獲取它作為Navigator.of(context).push(...)的參數(shù)回梧。
參考鏈接
結(jié)論
關(guān)于這些主題還有很多話要說(shuō)......特別是在InheritedWidget上。
在下一篇文章中祖搓,我將介紹通知器/監(jiān)聽(tīng)器的概念狱意,這在使用State和傳送數(shù)據(jù)的方式中也非常有趣。
翻譯不容易拯欧,大家且看且珍惜
原文