本文介紹了Flutter應(yīng)用程序中Widget,State朗儒,BuildContext和InheritedWidget的重要概念而咆。
特別注意InheritedWidget,它是最重要且記錄較少的小部件之一擂红。
本文內(nèi)容很長(zhǎng),但做技術(shù)就是要沉得下心!
難度:初學(xué)者
前言
Flutter中Widget,State和BuildContext的概念是每個(gè)Flutter開發(fā)人員需要完全理解的最重要概念之一围小。
但是昵骤,文檔很龐大,并不總是清楚地解釋這個(gè)概念肯适。
我會(huì)用自己的話語(yǔ)和捷徑來(lái)解釋這些概念变秦,本文的真正目的是試圖澄清以下主題:
- 有狀態(tài)和無(wú)狀態(tài)小部件之間的區(qū)別
- 什么是BuildContext
- 什么是State以及如何使用它
- BuildContext與其State對(duì)象之間的關(guān)系
- InheritedWidget以及在Widgets樹中傳播信息的方式
- 重建的概念
第1部分:概念
小工具的概念
在Flutter中,幾乎所有東西都是Widget框舔。
將Widget視為可視組件(或與應(yīng)用程序的可視方面交互的組件)蹦玫。
當(dāng)您需要構(gòu)建與布局直接或間接相關(guān)的任何內(nèi)容時(shí),您正在使用窗口小部件雨饺。
小部件樹的概念
窗口小部件以樹形結(jié)構(gòu)組織钳垮。
包含其他小部件的小部件稱為父Widget(或Widget容器)惑淳。
包含在父窗口小部件中的窗口小部件稱為子窗口小部件额港。
讓我們用Flutter自動(dòng)生成的基本應(yīng)用程序來(lái)說(shuō)明這一點(diǎn)。
這是簡(jiǎn)化的代碼歧焦,僅限于構(gòu)建方法:
@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樹結(jié)構(gòu)(限制代碼中存在的Widgets列表):
[站外圖片上傳中...(image-c411a3-1539427274357)]
BuildContext的概念
另一個(gè)重要的概念是BuildContext。
BuildContext只不過(guò)是對(duì)構(gòu)建的所有窗口小部件的樹結(jié)構(gòu)中的窗口小部件的位置的引用绢馍。
簡(jiǎn)而言之向瓷,將BuildContext視為Widgets樹的一部分,Widget將附加到此樹舰涌。
一個(gè)BuildContext只屬于一個(gè)小部件猖任。
如果窗口小部件“A”具有子窗口小部件,則窗口小部件“A”的BuildContext將成為直接子窗口BuildContexts的父BuildContext瓷耙。
閱讀本文朱躺,很明顯BuildContexts是鏈接的,并且正在組成BuildContexts樹(父子關(guān)系)搁痛。
如果我們現(xiàn)在嘗試在上圖中說(shuō)明BuildContext的概念长搀,我們可以看到(仍然是一個(gè)非常簡(jiǎn)化的視圖),其中每種顏色代表一個(gè)BuildContext(除了MyApp鸡典,它是不同的):
[站外圖片上傳中...(image-3be46-1539427274357)]
BuildContext可見性(簡(jiǎn)化語(yǔ)句):
“ Something ”僅在其自己的BuildContext或其父BuildContext的BuildContext中可見源请。
從這個(gè)語(yǔ)句我們可以從子BuildContext派生出來(lái),很容易找到一個(gè)祖先(= parent)Widget。
一個(gè)例子是谁尸,考慮Scaffold> Center> Column> Text:
context.ancestorWidgetOfExactType(Scaffold)=>通過(guò)從Text上下文轉(zhuǎn)到樹結(jié)構(gòu)來(lái)返回第一個(gè)Scaffold舅踪。
從父BuildContext,也可以找到一個(gè)后代(=子)Widget症汹,但不建議這樣做(我們稍后會(huì)討論)
小部件的類型
小部件有兩種類型:
無(wú)狀態(tài)小工具 Stateless Widget
這些可視組件中的一些除了它們自己的配置信息之外不依賴于任何其他信息硫朦,該信息在其直接父級(jí)構(gòu)建時(shí)提供。
換句話說(shuō)背镇,這些小部件一旦創(chuàng)建就不必關(guān)心任何變化咬展。
這些小部件稱為無(wú)狀態(tài)小部件。
這種小部件的典型示例可以是Text瞒斩,Row破婆,Column,Container ......其中胸囱,在構(gòu)建時(shí)祷舀,我們只是將一些參數(shù)傳遞給它們。
參數(shù)可以是裝飾烹笔,尺寸甚至其他小部件中的任何內(nèi)容裳扯。不要緊。唯一重要的是這個(gè)配置一旦應(yīng)用谤职,在下一個(gè)構(gòu)建過(guò)程之前不會(huì)改變饰豺。
無(wú)狀態(tài)窗口小部件只能在加載/構(gòu)建窗口小部件時(shí)繪制一次,這意味著無(wú)法基于任何事件或用戶操作重繪窗口小部件允蜈。
無(wú)狀態(tài)小部件生命周期
以下是與無(wú)狀態(tài)小組件相關(guān)的代碼的典型結(jié)構(gòu)冤吨。
如您所見,我們可以將一些額外的參數(shù)傳遞給它的構(gòu)造函數(shù)饶套。但是漩蟆,請(qǐng)記住,這些參數(shù)不會(huì)在稍后階段發(fā)生變化(變異)妓蛮,只能按原樣使用
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({
Key key,
this.parameter,
}): super(key:key);
final parameter;
@override
Widget build(BuildContext context){
return new ...
}
}
即使有另一種方法可以被重寫(createElement)怠李,后者幾乎從不被重寫。
唯一需要被重寫的是build
蛤克。
這種無(wú)狀態(tài)小部件的生命周期很簡(jiǎn)單:
- 初始化
- 通過(guò)build()渲染
有狀態(tài)的小工具 Stateful Widget
其他一些小部件將處理一些在Widget生命周期內(nèi)會(huì)發(fā)生變化的內(nèi)部數(shù)據(jù)捺癞。因此,該數(shù)據(jù)變得動(dòng)態(tài)咖耘。
此Widget保存的數(shù)據(jù)集可能會(huì)在此Widget的生命周期內(nèi)發(fā)生變化翘簇,稱為State。
這些窗口小部件稱為有狀態(tài)窗口小部件(Stateful Widget)儿倒。
這樣的Widget的示例可以是用戶可以選擇的復(fù)選框列表或者根據(jù)條件禁用的Button版保。
State的概念
State定義StatefulWidget實(shí)例的“行為”部分呜笑。
它包含旨在與Widget交互/干擾的信息:
- 行為
- 布局
應(yīng)用于狀態(tài)的任何更改都會(huì)強(qiáng)制Widget重建。
State和BuildContext之間的關(guān)系
對(duì)于有狀態(tài)窗口小部件彻犁,狀態(tài)與BuildContext關(guān)聯(lián)叫胁。
此關(guān)聯(lián)是永久性的 ,State對(duì)象永遠(yuǎn)不會(huì)更改其BuildContext汞幢。
即使可以在樹結(jié)構(gòu)周圍移動(dòng)Widget BuildContext驼鹅,State仍將與該BuildContext保持關(guān)聯(lián)。
當(dāng)State與BuildContext關(guān)聯(lián)時(shí)森篷,State被視為已掛載输钩。
重點(diǎn):
由于State對(duì)象與BuildContext相關(guān)聯(lián),這意味著State對(duì)象不能(直接)通過(guò)另一個(gè)BuildContext訪問(wèn)V僦恰(我們將在稍后討論這個(gè)問(wèn)題)买乃。
有狀態(tài)的小部件Stateful Widget 的生命周期
這是與Stateful Widget相關(guān)的典型代碼結(jié)構(gòu)。
由于本文的主要目的是用“變量”數(shù)據(jù)來(lái)解釋State的概念钓辆,我將故意跳過(guò)與某些Stateful Widget overridable方法相關(guān)的任何解釋剪验,這些方法與此沒(méi)有特別的關(guān)系。
這些可覆蓋的方法是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)建有狀態(tài)窗口小部件相關(guān)的操作/調(diào)用序列(簡(jiǎn)化版本)啸臀。在圖的右側(cè),您將注意到流中的State對(duì)象的內(nèi)部狀態(tài)双谆。您還將看到上下文與狀態(tài)關(guān)聯(lián)的時(shí)刻壳咕,從而變?yōu)榭捎茫ㄒ寻惭b)席揽。
[站外圖片上傳中...(image-6003dd-1539427274357)]
所以讓我們用一些額外的細(xì)節(jié)來(lái)解釋它:
initState()
initState()方法是創(chuàng)建State對(duì)象后要調(diào)用的第一個(gè)方法(在構(gòu)造函數(shù)之后)顽馋。
需要執(zhí)行其他初始化時(shí),將覆蓋重寫此方法幌羞。典型的初始化與動(dòng)畫寸谜,控制器有關(guān)......
如果重寫此方法,則需要在第一個(gè)位置調(diào)用super.initState()方法属桦。
在這個(gè)方法中熊痴,上下文context
可用,但你還不能真正使用它聂宾,因?yàn)榭蚣苓€沒(méi)有完全將狀態(tài)與它相關(guān)聯(lián)果善。
initState()方法完成后,State對(duì)象現(xiàn)在已初始化系谐,上下文可用巾陕。
在此State對(duì)象的生命周期內(nèi)不再調(diào)用此方法讨跟。
didChangeDependencies()
didChangeDependencies()
方法是要調(diào)用的第二個(gè)方法。
在此階段鄙煤,由于上下文可用晾匠,您可以使用它。
如果您的Widget鏈接到InheritedWidget和/或您需要初始化一些偵聽器(基于BuildContext)梯刚,則通常會(huì)覆蓋此方法凉馆。
請(qǐng)注意,如果您的窗口小部件鏈接到InheritedWidget亡资,則每次重建此窗口小部件時(shí)都會(huì)調(diào)用此方法澜共。
如果重寫此方法,則應(yīng)首先調(diào)用super.didChangeDependencies()
锥腻。
build()
build(BuildContext context)
方法在didChangeDependencies()
(和didUpdateWidget
)之后調(diào)用咳胃。
這是您構(gòu)建窗口小部件(可能還有任何子樹)的位置。
每次State對(duì)象更改時(shí)(或者當(dāng)InheritedWidget需要通知“已注冊(cè)”的小部件時(shí))都會(huì)調(diào)用此方法旷太!
為了強(qiáng)制重建展懈,您可以調(diào)用setState((){...})
方法。
dispose()
放棄窗口小部件時(shí)調(diào)用dispose()方法供璧。
如果你需要執(zhí)行一些清理(例如監(jiān)聽器存崖,控制器......),然后立即調(diào)用super.dispose()
睡毒,則覆蓋此方法来惧。
選擇無(wú)狀態(tài)還是有狀態(tài)小部件?
這是許多開發(fā)人員需要問(wèn)自己的問(wèn)題:“我是否需要我的Widget無(wú)狀態(tài)或有狀態(tài)演顾?”
為了回答這個(gè)問(wèn)題供搀,請(qǐng)問(wèn)自己:
在我的小部件的生命周期中,我是否需要考慮一個(gè)將要更改的變量钠至,何時(shí)更改葛虐,將強(qiáng)制重建小部件?
如果問(wèn)題的答案是肯定的棉钧,那么您需要一個(gè)有狀態(tài)的小部件屿脐,否則,您需要一個(gè)無(wú)狀態(tài)小部件宪卿。
一些例子:
- 用于顯示復(fù)選框列表的小組件的诵。要顯示復(fù)選框,您需要考慮一系列項(xiàng)目佑钾。每個(gè)項(xiàng)目都是一個(gè)具有標(biāo)題和狀態(tài)的對(duì)象西疤。如果單擊復(fù)選框,則切換相應(yīng)的
item.status
;在這種情況下休溶,您需要使用有狀態(tài)窗口小部件來(lái)記住項(xiàng)目的狀態(tài)代赁,以便能夠重繪復(fù)選框撒遣。 - 帶有表格的屏幕。該屏幕允許用戶填寫表單的窗口小部件并將表單發(fā)送到服務(wù)器管跺。在這種情況下义黎,在這種情況下,除非您在提交表單之前需要驗(yàn)證表單或執(zhí)行任何其他操作豁跑,否則無(wú)狀態(tài)窗口小部件可能就足夠了廉涕。
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í)例化此部件。
此部分在Widget的生命周期內(nèi)不會(huì)發(fā)生變化卸夕,但可能接受可由其相應(yīng)的State實(shí)例使用的參數(shù)层释。
請(qǐng)注意,在Widget的第一部分定義的任何變量通常在其生命周期內(nèi)不會(huì)更改快集。
Widget State定義
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
...
@override
Widget build(BuildContext context){
...
}
}
第二部分“_MyStatefulWidgetState”是在Widget的生命周期中變化的部分贡羔,并強(qiáng)制每次應(yīng)用修改時(shí)重建Widget的這個(gè)特定實(shí)例。
名稱以_
開頭的字符使其成為.dart文件的私有个初。
如果需要在.dart文件之外引用此類乖寒,請(qǐng)不要使用“_”前綴。
_MyStatefulWidgetState類可以訪問(wèn)存儲(chǔ)在MyStatefulWidget中的任何變量院溺,使用widget.{變量的名稱}
楣嘁。
例如:widget.color
小部件唯一標(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)制使用此密鑰褥民,以便可以通過(guò)其密鑰訪問(wèn)窗口小部件盔憨。
為此馒胆,您可以使用以下幫助程序之一:GlobalKey <T>缨称,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部分:如何進(jìn)入State?
如前所述型雳,State鏈接到一個(gè)BuildContext当凡,BuildContext鏈接到Widget的一個(gè)實(shí)例山害。
1. Widget本身
理論上,唯一能夠訪問(wèn)狀態(tài)的是Widget State本身沿量。
在這種情況下浪慌,沒(méi)有困難。Widget State類訪問(wèn)其任何變量朴则。
2. 一個(gè)直接的 child Widget
有時(shí)权纤,父窗口小部件可能需要訪問(wèn)其直接子節(jié)點(diǎn)的狀態(tài)才能執(zhí)行特定任務(wù)。
在這種情況下乌妒,要訪問(wèn)這些直接子項(xiàng)State汹想,您需要了解它們。
給某人打電話的最簡(jiǎn)單方法是通過(guò)一個(gè)名字撤蚊。在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,
);
}
一旦確定光涂,父Widget可以通過(guò)以下方式訪問(wèn)其子級(jí)的狀態(tài):
myWidgetStateKey.currentState
讓我們考慮一個(gè)基本示例夏漱,當(dāng)用戶點(diǎn)擊按鈕時(shí)顯示SnackBar。
由于SnackBar是Scaffold的子Widget顶捷,它不能直接被Scaffold身體的任何其他孩子訪問(wèn)(還記得上下文的概念及其層次結(jié)構(gòu)/樹結(jié)構(gòu)嗎挂绰?)。因此服赎,訪問(wèn)它的唯一方法是通過(guò)ScaffoldState葵蒂,它公開一個(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. 祖先Widget
假設(shè)您有一個(gè)屬于另一個(gè)Widget的子樹的Widget重虑,如下圖所示践付。
[站外圖片上傳中...(image-65249f-1539427274357)]
為了實(shí)現(xiàn)這一目標(biāo),需要滿足3個(gè)條件:
1.“帶狀態(tài)的小工具”(紅色)需要暴露其State
為了公開它的狀態(tài)缺厉,Widget需要在創(chuàng)建時(shí)記錄它永高,如下所示:
class MyExposingWidget extends StatefulWidget {
MyExposingWidgetState myState;
@override
MyExposingWidgetState createState(){
myState = new MyExposingWidgetState();
return myState;
}
}
2.“Widget State”需要暴露一些getter / setter
為了讓“stranger”設(shè)置/獲取狀態(tài)屬性,Widget State需要通過(guò)以下方式授權(quán)訪問(wèn):
- 公共屬性 (不推薦)
- getter / setter
例如:
class MyExposingWidgetState extends State<MyExposingWidget>{
Color _color;
Color get color => _color;
...
}
3.“想要獲得State的Widget”(上圖中藍(lán)色的widget)需要引用State
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)提针,但子窗口小部件如何知道它何時(shí)需要重建命爬?
在這個(gè)解決方案,它不知道辐脖。它必須等待重建才能刷新其內(nèi)容饲宛,這不是很方便。
下一節(jié)將討論Inherited Widget的概念嗜价,它可以解決這個(gè)問(wèn)題艇抠。
InheritedWidget
簡(jiǎn)而言之幕庐,InheritedWidget允許在窗口小部件樹中有效地傳播(和共享)信息。
InheritedWidget是一個(gè)特殊的Widget家淤,您可以將其作為另一個(gè)子樹的父級(jí)放在Widgets樹中异剥。該子樹的所有小部件都必須能夠與該InheritedWidget公開的數(shù)據(jù)進(jìn)行交互。
為了解釋它絮重,讓我們看下代碼:
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届吁,旨在“共享”所有小部件(與子樹的一部分)中的某些數(shù)據(jù)。
如前所述绿鸣,為了能夠傳播/共享一些數(shù)據(jù)疚沐,需要將InheritedWidget定位在窗口小部件樹的頂部,這解釋了傳遞給InheritedWidget基礎(chǔ)構(gòu)造函數(shù)的“@required Widget child”潮模。
“static MyInheritedWidget(BuildContext context)”方法允許所有子窗口小部件獲取最接近上下文的MyInheritedWidget的實(shí)例(參見后面)
最后亮蛔,“updateShouldNotify”重寫方法用于告訴InheritedWidget是否必須將通知傳遞給所有子窗口小部件(已注冊(cè)/已訂閱),如果對(duì)數(shù)據(jù)應(yīng)用了修改(請(qǐng)參閱下文)擎厢。
因此究流,我們需要將它放在樹節(jié)點(diǎn)級(jí)別,如下所示:
class MyParentWidget... {
...
@override
Widget build(BuildContext context){
return new MyInheritedWidget(
data: counter,
child: new Row(
children: <Widget>[
...
],
),
);
}
}
子child如何訪問(wèn)InheritedWidget的數(shù)據(jù)动遭?
在構(gòu)建子child時(shí)芬探,后者將獲得對(duì)InheritedWidget的引用,如下所示:
class MyChildWidget... {
...
@override
Widget build(BuildContext context){
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
/// 從此刻開始厘惦,窗口小部件可以使用MyInheritedWidget公開的數(shù)據(jù)
/// 通過(guò)調(diào)用:inheritedWidget.data
return new Container(
color: inheritedWidget.data.color,
);
}
}
如何在小部件之間進(jìn)行交互偷仿?
請(qǐng)考慮以下顯示窗口小部件樹結(jié)構(gòu)的圖表。
[站外圖片上傳中...(image-42aa42-1539427274357)]
為了說(shuō)明一種交互方式宵蕉,我們假設(shè)如下:
- '小部件A'是一個(gè)將項(xiàng)目添加到購(gòu)物車的按鈕;
- “小部件B”是一個(gè)顯示購(gòu)物車中商品數(shù)量的文本;
- “小部件C”位于小部件B旁邊酝静,是一個(gè)內(nèi)部帶有任何文本的文本;
- 我們希望“Widget B”在按下“Widget A”時(shí)自動(dòng)在購(gòu)物車中顯示正確數(shù)量的項(xiàng)目,但我們不希望重建“Widget C”
InheritedWidget就是用來(lái)干這個(gè)的Widget羡玛!
代碼示例我們先寫下代碼别智,然后解釋如下:
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公開一個(gè)getter(itemsCount)和一個(gè)方法(addItem)是越,以便它們可以被小部件使用耳舅,這是子小部件樹的一部分
- 每次我們向State添加一個(gè)Item時(shí),MyInheritedWidgetState都會(huì)重建
- MyTree類只是構(gòu)建一個(gè)小部件樹倚评,將MyInheritedWidget作為樹的父級(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í),它會(huì)調(diào)用MyInheritedWidget的以下方法呢岗,并傳遞自己的BuildContext冕香。
static MyInheritedWidgetState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
在內(nèi)部,除了簡(jiǎn)單地返回MyInheritedWidgetState的實(shí)例之外后豫,它還將消費(fèi)者窗口小部件訂閱到更改通知悉尾。
在場(chǎng)景后面,對(duì)這個(gè)靜態(tài)方法的簡(jiǎn)單調(diào)用實(shí)際上做了兩件事:
- 當(dāng)對(duì)InheritedWidget應(yīng)用修改時(shí)挫酿,“consumer”窗口小部件會(huì)自動(dòng)添加到將重建的訂戶列表中(此處為_MyInherited)
- _MyInherited小部件(又名MyInheritedWidgetState)中引用的數(shù)據(jù)將返回給“使用者”
過(guò)程
由于'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 <Item>
- 調(diào)用setState()以重建MyInheritedWidget
- 使用List <Item>的新內(nèi)容創(chuàng)建_MyInherited的新實(shí)例
- _MyInherited記錄在參數(shù)(數(shù)據(jù))中傳遞的新State作為InheritedWidget惫霸,它檢查是否需要“通知”“使用者”(答案為是)
- 它迭代整個(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)“繼承的”小組件時(shí)阻止某些小組件重建
Widget A也被重建的原因來(lái)自它訪問(wèn)MyInheritedWidgetState的方式老赤。
正如我們之前看到的,調(diào)用context.inheritFromWidgetOfExactType()
方法的實(shí)際上是自動(dòng)將Widget訂閱到“使用者”列表制市。
防止此自動(dòng)訂閱同時(shí)仍允許Widget A訪問(wèn)MyInheritedWidgetState的解決方案是更改MyInheritedWidget的靜態(tài)方法抬旺,如下所示:
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)情況下),我們使用常規(guī)方法(并且Widget將添加到訂閱者列表中)
- 如果“rebuild”參數(shù)為false祥楣,我們?nèi)匀豢梢栽L問(wèn)數(shù)據(jù)开财,但不使用InheritedWidget的內(nèi)部實(shí)現(xiàn)
因此,要完成解決方案误褪,我們還需要稍微更新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');
},
),
);
}
}
在那里,當(dāng)我們按下它時(shí)兽间,Widget A不再重建历葛。
Routes, Dialogs的特別說(shuō)明......
路由Routes,對(duì)話框Dialogs , BuildContexts與應(yīng)用程序綁定。這意味著即使在屏幕A內(nèi)部您要求顯示另一個(gè)屏幕B(例如恤溶,在當(dāng)前的屏幕上)乓诽,兩個(gè)屏幕中的任何一個(gè)都沒(méi)有“簡(jiǎn)單的方法”來(lái)關(guān)聯(lián)它們自己的上下文。屏幕B了解屏幕A上下文的唯一方法是從屏幕A獲取它作為Navigator.of(context).push(...咒程。)的參數(shù)鸠天。
推薦閱讀:
[1] : flutter屏幕適配
[2] : Maksim Ryzhikov
[3] : Chema Molins
[4] : Official documentation
[5] : Video from Google I/O 2018
[6] : Scoped_Model
結(jié)論
關(guān)于這些主題還有很多話要說(shuō)......特別是在InheritedWidget上。
其他感興趣的主題是Notifiers / Listeners 以及(可能主要)Streams的概念帐姻,但這將在其他文章中介紹稠集。
感謝您閱讀這篇相當(dāng)長(zhǎng)的文章,請(qǐng)繼續(xù)關(guān)注下一個(gè)快樂(lè)的編碼......