1 Widget只是UI元素的一個(gè)配置數(shù)據(jù),并且一個(gè)Widget可以對(duì)應(yīng)多個(gè)Element
Widget實(shí)際上就是Element的配置數(shù)據(jù)复斥,Widget樹實(shí)際上是一個(gè)配置樹霉旗,而真正的UI渲染樹是由Element構(gòu)成召川;不過南缓,由于Element是通過Widget生成的,所以它們之間有對(duì)應(yīng)關(guān)系荧呐,在大多數(shù)場(chǎng)景汉形,我們可以寬泛地認(rèn)為Widget樹就是指UI控件樹或UI渲染樹。
一個(gè)Widget對(duì)象可以對(duì)應(yīng)多個(gè)Element對(duì)象坛增。這很好理解,根據(jù)同一份配置(Widget)薄腻,可以創(chuàng)建多個(gè)實(shí)例(Element)
2.1 StatelessWidget
StatelessElement 間接繼承自Element類收捣,與StatelessWidget相對(duì)應(yīng)(作為其配置數(shù)據(jù))。
StatelessWidget用于不需要維護(hù)狀態(tài)的場(chǎng)景庵楷,它通常在build方法中通過嵌套其它Widget來(lái)構(gòu)建UI罢艾,在構(gòu)建過程中會(huì)遞歸的構(gòu)建其嵌套的Widget楣颠。
2.2 Context
build方法有一個(gè)context參數(shù),它是BuildContext類的一個(gè)實(shí)例咐蚯,表示當(dāng)前widget在widget樹中的上下文童漩,每一個(gè)widget都會(huì)對(duì)應(yīng)一個(gè)context對(duì)象(因?yàn)槊恳粋€(gè)widget都是widget樹上的一個(gè)節(jié)點(diǎn))。實(shí)際上春锋,context是當(dāng)前widget在widget樹中位置中執(zhí)行”相關(guān)操作“的一個(gè)句柄矫膨,比如它提供了從當(dāng)前widget開始向上遍歷widget樹以及按照widget類型查找父級(jí)widget的方法
3 StatefulWidget
StatefulElement 間接繼承自Element類,與StatefulWidget相對(duì)應(yīng)(作為其配置數(shù)據(jù))期奔。StatefulElement中可能會(huì)多次調(diào)用createState()來(lái)創(chuàng)建狀態(tài)(State)對(duì)象侧馅。
createState() 用于創(chuàng)建和Stateful widget相關(guān)的狀態(tài),它在Stateful widget的生命周期中可能會(huì)被多次調(diào)用呐萌。例如馁痴,當(dāng)一個(gè)Stateful widget同時(shí)插入到widget樹的多個(gè)位置時(shí),F(xiàn)lutter framework就會(huì)調(diào)用該方法為每一個(gè)位置生成一個(gè)獨(dú)立的State實(shí)例肺孤,其實(shí)罗晕,本質(zhì)上就是一個(gè)StatefulElement對(duì)應(yīng)一個(gè)State實(shí)例
4.1 State
一個(gè)StatefulWidget類會(huì)對(duì)應(yīng)一個(gè)State類,State表示與其對(duì)應(yīng)的StatefulWidget要維護(hù)的狀態(tài)赠堵,State中的保存的狀態(tài)信息可以:
1.在widget 構(gòu)建時(shí)可以被同步讀取小渊。
2.在widget生命周期中可以被改變,當(dāng)State被改變時(shí)顾腊,可以手動(dòng)調(diào)用其setState()方法通知Flutter framework狀態(tài)發(fā)生改變粤铭,F(xiàn)lutter framework在收到消息后,會(huì)重新調(diào)用其build方法重新構(gòu)建widget樹杂靶,從而達(dá)到更新UI的目的梆惯。
State中有兩個(gè)常用屬性:
1.widget,它表示與該State實(shí)例關(guān)聯(lián)的widget實(shí)例吗垮,由Flutter framework動(dòng)態(tài)設(shè)置垛吗。注意,這種關(guān)聯(lián)并非永久的烁登,因?yàn)樵趹?yīng)用聲明周期中怯屉,UI樹上的某一個(gè)節(jié)點(diǎn)的widget實(shí)例在重新構(gòu)建時(shí)可能會(huì)變化,但State實(shí)例只會(huì)在第一次插入到樹中時(shí)被創(chuàng)建饵沧,當(dāng)在重新構(gòu)建時(shí)锨络,如果widget被修改了,F(xiàn)lutter framework會(huì)動(dòng)態(tài)設(shè)置State.widget為新的widget實(shí)例狼牺。
2.context羡儿。StatefulWidget對(duì)應(yīng)的BuildContext,作用同StatelessWidget的BuildContext是钥。
initState:當(dāng)Widget第一次插入到Widget樹時(shí)會(huì)被調(diào)用掠归,對(duì)于每一個(gè)State對(duì)象缅叠,F(xiàn)lutter framework只會(huì)調(diào)用一次該回調(diào),所以虏冻,通常在該回調(diào)中做一些一次性的操作肤粱,如狀態(tài)初始化、訂閱子樹的事件通知等厨相。不能在該回調(diào)中調(diào)用BuildContext.inheritFromWidgetOfExactType(該方法用于在Widget樹上獲取離當(dāng)前widget最近的一個(gè)父級(jí)InheritFromWidget领曼,關(guān)于InheritedWidget我們將在后面章節(jié)介紹),原因是在初始化完成后领铐,Widget樹中的InheritFromWidget也可能會(huì)發(fā)生變化悯森,所以正確的做法應(yīng)該在在build()方法或didChangeDependencies()中調(diào)用它。
didChangeDependencies():當(dāng)State對(duì)象的依賴發(fā)生變化時(shí)會(huì)被調(diào)用绪撵;例如:在之前build() 中包含了一個(gè)InheritedWidget瓢姻,然后在之后的build() 中InheritedWidget發(fā)生了變化,那么此時(shí)InheritedWidget的子widget的didChangeDependencies()回調(diào)都會(huì)被調(diào)用音诈。典型的場(chǎng)景是當(dāng)系統(tǒng)語(yǔ)言Locale或應(yīng)用主題改變時(shí)幻碱,F(xiàn)lutter framework會(huì)通知widget調(diào)用此回調(diào)。
build():此回調(diào)讀者現(xiàn)在應(yīng)該已經(jīng)相當(dāng)熟悉了细溅,它主要是用于構(gòu)建Widget子樹的褥傍,會(huì)在如下場(chǎng)景被調(diào)用:
在調(diào)用initState()之后。
在調(diào)用didUpdateWidget()之后喇聊。
在調(diào)用setState()之后恍风。
在調(diào)用didChangeDependencies()之后。
在State對(duì)象從樹中一個(gè)位置移除后(會(huì)調(diào)用deactivate)又重新插入到樹的其它位置之后誓篱。
reassemble():此回調(diào)是專門為了開發(fā)調(diào)試而提供的朋贬,在熱重載(hot reload)時(shí)會(huì)被調(diào)用,此回調(diào)在Release模式下永遠(yuǎn)不會(huì)被調(diào)用窜骄。
didUpdateWidget():在widget重新構(gòu)建時(shí)锦募,F(xiàn)lutter framework會(huì)調(diào)用Widget.canUpdate來(lái)檢測(cè)Widget樹中同一位置的新舊節(jié)點(diǎn),然后決定是否需要更新邻遏,如果Widget.canUpdate返回true則會(huì)調(diào)用此回調(diào)糠亩。正如之前所述,Widget.canUpdate會(huì)在新舊widget的key和runtimeType同時(shí)相等時(shí)會(huì)返回true准验,也就是說(shuō)在在新舊widget的key和runtimeType同時(shí)相等時(shí)didUpdateWidget()就會(huì)被調(diào)用赎线。
deactivate():當(dāng)State對(duì)象從樹中被移除時(shí),會(huì)調(diào)用此回調(diào)糊饱。在一些場(chǎng)景下垂寥,F(xiàn)lutter framework會(huì)將State對(duì)象重新插到樹中,如包含此State對(duì)象的子樹在樹的一個(gè)位置移動(dòng)到另一個(gè)位置時(shí)(可以通過GlobalKey來(lái)實(shí)現(xiàn))。如果移除后沒有重新插入到樹中則緊接著會(huì)調(diào)用dispose()方法矫废。
dispose():當(dāng)State對(duì)象從樹中被永久移除時(shí)調(diào)用;通常在此回調(diào)中釋放資源砰蠢。
為什么要將build方法放在State中蓖扑,而不是放在StatefulWidget中?
1.狀態(tài)訪問不便台舱。
試想一下律杠,如果我們的StatefulWidget有很多狀態(tài),而每次狀態(tài)改變都要調(diào)用build方法竞惋,由于狀態(tài)是保存在State中的柜去,如果build方法在StatefulWidget中,那么build方法和狀態(tài)分別在兩個(gè)類中拆宛,那么構(gòu)建時(shí)讀取狀態(tài)將會(huì)很不方便嗓奢!試想一下,如果真的將build方法放在StatefulWidget中的話浑厚,由于構(gòu)建用戶界面過程需要依賴State股耽,所以build方法將必須加一個(gè)State參數(shù),
這樣的話就只能將State的所有狀態(tài)聲明為公開的狀態(tài)钳幅,這樣才能在State類外部訪問狀態(tài)物蝙!但是,將狀態(tài)設(shè)置為公開后敢艰,狀態(tài)將不再具有私密性诬乞,這就會(huì)導(dǎo)致對(duì)狀態(tài)的修改將會(huì)變的不可控。但如果將build()方法放在State中的話钠导,構(gòu)建過程不僅可以直接訪問狀態(tài)震嫉,而且也無(wú)需公開私有狀態(tài),這會(huì)非常方便辈双。
2.繼承StatefulWidget不便
例如责掏,F(xiàn)lutter中有一個(gè)動(dòng)畫widget的基類AnimatedWidget,它繼承自StatefulWidget類湃望。AnimatedWidget中引入了一個(gè)抽象方法build(BuildContext context)换衬,繼承自AnimatedWidget的動(dòng)畫widget都要實(shí)現(xiàn)這個(gè)build方法。現(xiàn)在設(shè)想一下证芭,如果StatefulWidget 類中已經(jīng)有了一個(gè)build方法瞳浦,正如上面所述,此時(shí)build方法需要接收一個(gè)state對(duì)象废士,這就意味著AnimatedWidget必須將自己的State對(duì)象(記為_animatedWidgetState)提供給其子類叫潦,因?yàn)樽宇愋枰谄鋌uild方法中調(diào)用父類的build方法
這樣很顯然是不合理的,因?yàn)?br>
a.AnimatedWidget的狀態(tài)對(duì)象是AnimatedWidget內(nèi)部實(shí)現(xiàn)細(xì)節(jié)官硝,不應(yīng)該暴露給外部矗蕊。
b.如果要將父類狀態(tài)暴露給子類短蜕,那么必須得有一種傳遞機(jī)制,而做這一套傳遞機(jī)制是無(wú)意義的傻咖,因?yàn)楦缸宇愔g狀態(tài)的傳遞和子類本身邏輯是無(wú)關(guān)的朋魔。
5獲取State
5.1 在Widget樹中獲取State對(duì)象
由于StatefulWidget的的具體邏輯都在其State中,所以很多時(shí)候卿操,我們需要獲取StatefulWidget對(duì)應(yīng)的State對(duì)象來(lái)調(diào)用一些方法警检,比如Scaffold組件對(duì)應(yīng)的狀態(tài)類ScaffoldState中就定義了打開SnackBar(路由頁(yè)底部提示條)的方法。我們有兩種方法在子widget樹中獲取父級(jí)StatefulWidget的State對(duì)象害淤。
5.2 通過Context獲取
context對(duì)象有一個(gè)ancestorStateOfType(TypeMatcher)方法扇雕,該方法可以從當(dāng)前節(jié)點(diǎn)沿著widget樹向上查找指定類型的StatefulWidget對(duì)應(yīng)的State對(duì)象。下面是實(shí)現(xiàn)打開SnackBar的示例:
5.3 通過GlobalKey獲取
_globalKey.currentState.openDrawer()
GlobalKey是Flutter提供的一種在整個(gè)APP中引用element的機(jī)制窥摄。如果一個(gè)widget設(shè)置了GlobalKey镶奉,那么我們便可以通過globalKey.currentWidget獲得該widget對(duì)象、globalKey.currentElement來(lái)獲得widget對(duì)應(yīng)的element對(duì)象崭放,如果當(dāng)前widget是StatefulWidget腮鞍,則可以通過globalKey.currentState來(lái)獲得該widget對(duì)應(yīng)的state對(duì)象。
注意:使用GlobalKey開銷較大莹菱,如果有其他可選方案移国,應(yīng)盡量避免使用它。另外同一個(gè)GlobalKey在整個(gè)widget樹中必須是唯一的道伟,不能重復(fù)迹缀。
6 Flutter SDK內(nèi)置組件庫(kù)介紹
Flutter提供了一套豐富、強(qiáng)大的基礎(chǔ)組件蜜徽,在基礎(chǔ)組件庫(kù)之上Flutter又提供了一套Material風(fēng)格(Android默認(rèn)的視覺風(fēng)格)和一套Cupertino風(fēng)格(iOS視覺風(fēng)格)的組件庫(kù)祝懂。
6.1 基礎(chǔ)組件 import 'package:flutter/widgets.dart';
-
Text
:該組件可讓您創(chuàng)建一個(gè)帶格式的文本。 -
Row
拘鞋、Column
: 這些具有彈性空間的布局類Widget可讓您在水平(Row)和垂直(Column)方向上創(chuàng)建靈活的布局砚蓬。其設(shè)計(jì)是基于Web開發(fā)中的Flexbox布局模型。 -
Stack
: 取代線性布局 (譯者語(yǔ):和Android中的FrameLayout
相似)盆色,Stack
允許子 widget 堆疊灰蛙, 你可以使用Positioned
來(lái)定位他們相對(duì)于Stack
的上下左右四條邊的位置。Stacks是基于Web開發(fā)中的絕對(duì)定位(absolute positioning )布局模型設(shè)計(jì)的隔躲。 -
Container
:Container
可讓您創(chuàng)建矩形視覺元素摩梧。container 可以裝飾一個(gè)BoxDecoration
, 如 background、一個(gè)邊框宣旱、或者一個(gè)陰影仅父。Container
也可以具有邊距(margins)、填充(padding)和應(yīng)用于其大小的約束(constraints)。另外笙纤,Container
可以使用矩陣在三維空間中對(duì)其進(jìn)行變換耗溜。
6.2 Material組件 import 'package:flutter/material.dart';
Flutter提供了一套豐富的Material組件,它可以幫助我們構(gòu)建遵循Material Design設(shè)計(jì)規(guī)范的應(yīng)用程序省容。Material應(yīng)用程序以MaterialApp
組件開始强霎, 該組件在應(yīng)用程序的根部創(chuàng)建了一些必要的組件,比如Theme
組件蓉冈,它用于配置應(yīng)用的主題。 是否使用MaterialApp
完全是可選的轩触,但是使用它是一個(gè)很好的做法寞酿。在之前的示例中,我們已經(jīng)使用過多個(gè)Material 組件了脱柱,如:Scaffold
伐弹、AppBar
、FlatButton
等榨为。要使用Material 組件惨好,需要先引入它:
6.3 Cupertino組件 import 'package:flutter/cupertino.dart';
Flutter也提供了一套豐富的Cupertino風(fēng)格的組件,盡管目前還沒有Material 組件那么豐富随闺,但是它仍在不斷的完善中日川。值得一提的是在Material 組件庫(kù)中有一些組件可以根據(jù)實(shí)際運(yùn)行平臺(tái)來(lái)切換表現(xiàn)風(fēng)格,比如MaterialPageRoute矩乐,在路由切換時(shí)龄句,如果是Android系統(tǒng),它將會(huì)使用Android系統(tǒng)默認(rèn)的頁(yè)面切換動(dòng)畫(從底向上)散罕;如果是iOS系統(tǒng)分歇,它會(huì)使用iOS系統(tǒng)默認(rèn)的頁(yè)面切換動(dòng)畫(從右向左)。由于在前面的示例中還沒有Cupertino組件的示例欧漱,下面我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Cupertino組件風(fēng)格的頁(yè)面:
總結(jié)
Flutter提供了豐富的組件职抡,在實(shí)際的開發(fā)中你可以根據(jù)需要隨意使用它們,而不必?fù)?dān)心引入過多組件庫(kù)會(huì)讓你的應(yīng)用安裝包變大误甚,這不是web開發(fā)缚甩,dart在編譯時(shí)只會(huì)編譯你使用了的代碼。由于Material和Cupertino都是在基礎(chǔ)組件庫(kù)之上的窑邦,所以如果我們的應(yīng)用中引入了這兩者之一蹄胰,則不需要再引入flutter/widgets.dart了,因?yàn)樗鼈儍?nèi)部已經(jīng)引入過了奕翔。