1、Widget
Widget是個抽象類,定義如下:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
說明:
(1)Key: 這個key屬性主要的作用是決定是否在下一次build時複用舊的widget,決定的條件在canUpdate()方法中。
(2)canUpdate(...)是一個靜态方法,它主要用于在Widget樹重新build時複用舊的widget。隻要newWidget與oldWidget的runtimeType和key同時相等時就會用newWidget去更新Element對象的配置,否則就會建立新的Element。
(3)StatelessWidget和StatefulWidget是Widget常用的兩個子類,他們兩也是abstract抽象類。
2、StatefulWidget
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
說明:
(1)一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀态。本質上就是一個StatefulElement對應一個State執行個體。
(2)當State被改變時,可以手動調用其setState()方法通知Flutter framework狀态發生改變,Flutter framework在收到消息後,會重新調用其build方法重新建構widget樹,進而達到更新UI的目的。
3、State生命周期
(1)舉個生命周期例子
(a)首次啟動StatefulWidget,生命周期執行如下:
initState
didChangeDependencies
build
(b)然後點選熱重載按鈕,生命周期執行如下:
reassemble
didUpdateWidget
build
(c)在widget樹中将目前StatefulWidget移除,然後熱重載,生命周期執行如下:
reassemble
deactivate
dispose
(2)生命周期方法說明
(a)initState:當Widget第一次插入到Widget樹時會被調用,Flutter framework隻會調用一次該回調。
(b)didChangeDependencies:當State對象的依賴發生變化時會被調用。
(c)build:用于建構Widget子樹。
(d)reassemble:此回調是專門為了開發調試而提供的,在熱重載(hot reload)時會被調用,此回調在Release模式下永遠不會被調用。
(e)didUpdateWidget:在widget重新建構時,如果Widget.canUpdate傳回true則會調用此回調。
(f)deactivate:當State對象從樹中被移除時,會調用此回調。如果移除後沒有重新插入到樹中則緊接着會調用dispose()方法。
(g) dispose:當State對象從樹中被永久移除時調用;通常在此回調中釋放資源。
注意:在繼承StatefulWidget重寫其方法時,對于包含@mustCallSuper标注的父類方法,都要在子類方法中先調用父類方法。
4、狀态管理
最常見的狀态管理方法如下:
(1)Widget管理自己的state。
該Widget本身是StatefulWidget。
(2)父widget管理子widget狀态。
父類為StatefulWidget, 子類為StatelessWidget。子類通過回調将其狀态導出到其父項。
(3)混合管理(父widget和子widget都管理狀态)。
此時父類和子類都繼承于StatefulWidget。
5、全局狀态管理
當應用中包括一些跨widget(甚至跨路由)的狀态需要同步時,上面介紹的方法很難勝任了。比如,我們有一個設定頁,裡面可以設定應用語言,但是我們為了讓設定實時生效,我們期望在語言狀态發生改變時,我們的APP Widget能夠重新build一下,但我們的APP Widget和設定頁并不在一起。正确的做法是通過一個全局狀态管理器來處理這種“相距較遠”的widget之間的通信。
目前主要方法:實作一個全局的事件總線,将語言狀态改變對應為一個事件,然後在APP Widget所在的父widget initState 方法中訂閱語言改變的事件,當使用者在設定頁切換語言後,我們觸發語言改變事件,然後APP Widget那邊就會收到通知,然後重新build一下即可。
6、基礎widget
(1)Text:可以建立一個帶格式的文本。
(2)Row、 Column:可讓在水準(Row)和垂直(Column)方向上建立靈活的布局。
(3)Stack:(和Android中的FrameLayout相似),Stack允許子 widget 堆疊, 你可以使用 Positioned 來定位他們相對于Stack的上下左右四條邊的位置。Stacks是基于Web開發中的絕對定位(absolute positioning )布局模型設計的。
(4)Container:Container可以建立矩形視覺元素。container 可以裝飾一個BoxDecoration, 如 background、一個邊框、或者一個陰影。 Container 也可以具有邊距(margins)、填充(padding)和應用于其大小的限制(constraints)。另外, Container可以使用矩陣在三維空間中對其進行變換。
7、按鈕
Material widget庫中提供了多種按鈕Widget如RaisedButton、FlatButton、OutlineButton等。
所有Material 庫中的按鈕都有如下相同點:按下時都會有“水波動畫”;有一個onPressed屬性來設定點選回調。
8、圖檔
(1)在Flutter中,我們可以通過Image來加載并顯示圖檔,Image的資料源可以是asset、檔案、記憶體以及網絡。
(2)ImageProvider
ImageProvider 是一個抽象類,主要定義了圖檔資料擷取的接口load()。從不同的資料源擷取圖檔需要實作不同的ImageProvider,如AssetImage是實作了從Asset中加載圖檔的ImageProvider,而NetworkImage實作了從網絡加載圖檔的ImageProvider。
(3)Image widget有一個必選的image參數,它對應一個ImageProvider。
9、字型圖示
(1)Flutter中,可以像web開發一樣使用iconfont“字型圖示”,它是将圖示做成字型檔案,然後通過指定不同的字元而顯示不同的圖檔。
(2)在Flutter開發中,iconfont和圖檔相比有如下優勢:
體積小:可以減小安裝包大小。
矢量的:iconfont都是矢量圖示,放大不會影響其清晰度。
可以應用文本樣式:可以像文本一樣改變字型圖示的顔色、大小對齊等。
可以通過TextSpan和文本混用。
10、單選開關Switch和複選框Checkbox
單選開關Switch和複選框Checkbox,它們都是繼承自StatelessWidget,是以它們本身不會儲存目前選擇狀态,是以一般都是在父widget中管理選中狀态。當使用者點選Switch或Checkbox時,它們會觸發onChanged回調,我們可以在此回調中處理選中狀态改變邏輯。
11、輸入框TextField
(1)監聽文本變化的方式有:onChange回調、controller監聽。
(2)控制焦點
焦點可以通過FocusNode和FocusScopeNode來控制,預設情況下,焦點由FocusScope來管理,它代表焦點控制範圍,可以在這個範圍内可以通過FocusScopeNode在輸入框之間移動焦點、設定預設焦點等。我們可以通過FocusScope.of(context) 來擷取widget樹中預設的FocusScopeNode。
12、表單Form
(1)可以對輸入框資料進行合法性校驗。
(2)可以對一組輸入框進行統一處理。
13、布局類Widget概述
Flutter中,根據Widget是否需要包含子節點将Widget分為了三類:
LeafRenderObjectWidget:沒有子節點的widget。
SingleChildRenderObjectWidget:包含一個子widget。
MultiChildRenderObjectWidget:包含多個子Widget,一般都有一個children參數。
舉幾個布局類Widget:
(1)線性布局Row/Column
(2)彈性布局(Flex和Expanded配合)
(3)流式布局Wrap/Flow
(4)層疊布局Stack(使用Positioned定位)
14、Row/Column(線性布局)
(1)Row和(水準布局)Column(垂直布局)都繼承自Flex(彈性布局)。
(2)對于線性布局,有主軸和縱軸之分,如果布局是沿水準方向,那麼主軸就是指水準方向,而縱軸即垂直方向;如果布局沿垂直方向,那麼主軸就是指垂直方向,而縱軸就是水準方向。線上性布局中,有兩個定義對齊方式的枚舉類MainAxisAlignment和CrossAxisAlignment,分别代表主軸對齊和縱軸對齊。
15、Flex和Expanded配合(彈性布局)
const Expanded({
int flex = 1,
@required Widget child,
})
flex為彈性系數,如果為0或null,則child是沒有彈性的,即不會被擴伸占用的空間。如果大于0,所有的Expanded按照其flex的比例來分割Flex主軸的全部空閑空間。例如:
//Flex的兩個子widget按1:2來占據水準空間
Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
16、Wrap和Flow(流式布局)
我們把超出螢幕顯示範圍會自動折行的布局稱為流式布局。Flutter中通過Wrap和Flow來支援流式布局。
17、容器類Widget和布局類Widget差別
布局類Widget一般都需要接收一個widget數組(children),他們直接或間接繼承自(或包含)MultiChildRenderObjectWidget ;而容器類Widget一般隻需要接收一個子Widget(child),他們直接或間接繼承自(或包含)SingleChildRenderObjectWidget。
舉幾個容器類Widget:
(1)padding
(2)限制類容器ConstrainedBox/SizedBox
ConstrainedBox用于對子widget添加額外的限制。
(3)裝飾容器DecoratedBox
(4)變換Transform
Transform可以在其子Widget繪制時對其應用一個矩陣變換。
注意:Transform的變換是應用在繪制階段,而并不是應用在布局(layout)階段,是以無論對子widget應用何種變化,其占用空間的大小和在螢幕上的位置都是固定不變的,因為這些是在布局階段就确定的。
(5)Container
18、可滾動的widget
(1)滾動條Scrollbar
如果要給可滾動widget添加滾動條,隻需将Scrollbar作為可滾動widget的父widget即可。
Scrollbar(
child: SingleChildScrollView(
...
),
);
(2)SingleChildScrollView
SingleChildScrollView類似于Android中的ScrollView,它隻能接收一個子Widget。
(3)ListView
如何自動拉升ListView以填充螢幕剩餘空間呢?答案是使用Flex。前面已經介紹過在Flex布局中,可以使用Expanded自動拉伸元件大小的Widget,我們也說過Column是繼承自Flex的,是以我們可以直接使用Column+Expanded來實作,代碼如下:
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
ListTile(title:Text("商品清單")),
Expanded(
child: ListView.builder(itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}),
),
]);
}
(4)GridView
(5)CustomScrollView自定義滾動模型
CustomScrollView是可以使用sliver來自定義滾動模型(效果)的widget。它可以包含多種滾動模型,舉個例子,假設有一個頁面,頂部需要一個GridView,底部需要一個ListView,而要求整個頁面的滑動效果是統一的,即它們看起來是一個整體,如果使用GridView+ListView來實作的話,就不能保證一緻的滑動效果,因為它們的滾動效果是分離的,是以這時就需要一個"膠水",把這些彼此獨立的可滾動widget(Sliver)"粘"起來,而CustomScrollView的功能就相當于“膠水”。
在Flutter中,Sliver通常指具有特定滾動效果的可滾動塊。可滾動widget,如ListView、GridView等都有對應的Sliver實作如SliverList、SliverGrid等。對于大多數Sliver來說,它們和可滾動Widget最主要的差別是Sliver不會包含Scrollable Widget,也就是說Sliver本身不包含滾動互動模型 ,正因如此,CustomScrollView才可以将多個Sliver"粘"在一起,這些Sliver共用CustomScrollView的Scrollable,最終實作統一的滑動效果。