在Flutter中,跨Widget的數據共享,能夠以下圖這樣表示。android
當Child Widget想要跨Widget拿到其它Widget的數據時,一般就須要使用構造函數,將數據一層層傳遞到Child Widget,這顯然不是一個好的解決方案,不只讓Widget之間有了很大的耦合,也產生不少的冗餘數據。web
爲了解決這個問題,Flutter SDK提供了InheritedWidget這個Widget,InheritedWidget是除了StatefulWidget和StatelessWidget以外的第三個經常使用的Widget。當把InheritedWidget做爲Widget Tree的根節點時,這個Widget Tree就具備了一些新的功能,例如,Child Widget能夠根據BuildContext找到最近的指定類型的InheritedWidget,而不是經過Widget Tree的構造函數一層層進行傳遞,以下圖所示。微信
InheritedWidget的使用其實很是簡單,即共享數據給Child。因此它的核心點,其實就是兩個。less
-
須要共享的數據 -
從新updateShouldNotify的條件
經過BuildContext的dependOnInheritedWidgetOfExactType函數,就能夠直接獲取父Widget中的InheritedWidget。因此在InheritedWidget內部,一般會有一個of函數,用過調用BuildContext的dependOnInheritedWidgetOfExactType函數來獲取對應的父InheritedWidget。編輯器
只讀的InheritedWidget
InheritedWidget默認狀況下都是隻讀的,即只能將某個數據共享給Child Widget,而不能讓Child Widget對數據作更新。下面這個例子演示了一個最基本的InheritedWidget是如何共享數據的。ide
class InheritedWidgetReadOnlyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReadOnlyRoot(
count: 1008,
child: ChildReadOnly(),
);
}
}
class ChildReadOnly extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('build');
ReadOnlyRoot root = ReadOnlyRoot.of(context);
return Column(
children: <Widget>[
SubtitleWidget('InheritedWidget自己不具備寫數據的功能,須要結合State來獲取數據修改的能力'),
Text(
'show ${root.count}',
style: TextStyle(fontSize: 20),
),
],
);
}
}
// 僅支持讀取屬性
class ReadOnlyRoot extends InheritedWidget {
static ReadOnlyRoot of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ReadOnlyRoot>();
final int count;
ReadOnlyRoot({
Key key,
@required this.count,
@required Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(ReadOnlyRoot oldWidget) => count != oldWidget.count;
}
給InheritedWidget增長讀寫功能
數據的狀態一般狀況下都是保存在StatefulWidget的State中的,因此,InheritedWidget必需要結合StatefulWidget才能具備修改數據的能力,所以,思路就是在InheritedWidget中持有一個StatefulWidget的State實例,同時,使用一個StatefulWidget,將本來的Child Widget之上,插入這個InheritedWidget,這樣就能夠藉助StatefulWidget來完成數據的修改能力,經過InheritedWidget來實現數據的共享能力。函數
class RootContainer extends StatefulWidget {
final Widget child;
RootContainer({
Key key,
this.child,
}) : super(key: key);
@override
_RootContainerState createState() => _RootContainerState();
static _RootContainerState of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<Root>().state;
}
class _RootContainerState extends State<RootContainer> {
int count = 0;
void incrementCounter() => setState(() => count++);
@override
Widget build(BuildContext context) {
return Root(state: this, child: widget.child);
}
}
// 同時支持讀取和寫入
class Root extends InheritedWidget {
final _RootContainerState state;
Root({
Key key,
@required this.state,
@required Widget child,
}) : super(key: key, child: child);
// 判斷是否須要更新
@override
bool updateShouldNotify(Root oldWidget) => true;
}
在這種寫法中,InheritedWidget(Root)是在StatefulWidget(RootContainer)中初始化的,當使用StatefulWidget(RootContainer)的setState函數時,InheritedWidget(Root)重建了,可是其child並不會重建,由於它是widget.child,並不會由於State的重建而重建。flex
要注意的是,雖然這裏的StatefulWidget經過setState來修改數據了,但其子Widget並不會所有重繪,由於InheritedWidget的存在,Child Widget會有選擇性的進行重繪。ui
在這基礎上,使用就比較簡單了,代碼以下所示。this
class InheritedWidgetWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RootContainer(
child: Column(
children: <Widget>[
Widget1(),
Widget2(),
Widget3(),
],
),
);
}
}
class Widget1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('build Widget1');
return SubtitleWidget('InheritedWidget自己不具備寫數據的功能,須要結合State來獲取數據修改的能力');
}
}
class Widget2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('build Widget2');
return Text(
'show ${RootContainer.of(context).count}',
style: TextStyle(fontSize: 20),
);
}
}
class Widget3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('build Widget3');
return RaisedButton(
onPressed: () {
RootContainer.of(context).incrementCounter();
},
child: Text('Add'),
);
}
}
相關代碼 Flutter Dojo-Widgets-Async-InheritedWidget
在上面這個Demo中,Widget二、3分別獲取和修改了InheritedWidget中的共享數據,實現了跨Widget的數據共享。
經過Log咱們能夠發現,初始化的時候,Widget一、二、3都執行了build,但點擊的時候,只有Widget二、3從新build了,可是Widget1並不會從新build。
這是什麼緣由呢?
其實這就是RootContainer.of(context)致使的。
當咱們執行RootContainer.of(context)這個函數的時候,實際上調用的是context.dependOnInheritedWidgetOfExactType函數,這個函數不只僅會返回指定類型的InheritedWidget,同時也會將Context對應的Widget添加到訂閱者列表中,也就是說,即便你調用這個函數,只是爲了執行某個函數,並非想刷新UI,可是系統依然認爲你須要刷新,從而致使Widget二、3都會執行rebuild。而Widget1,因爲沒有調用過of函數,因此不會被添加到訂閱者列表中,因此不會執行rebuild。
要想解決這個問題也很是簡單,那就是在不須要監聽的時候,使用findAncestorWidgetOfExactType便可,這個函數只會返回指定類型的Widget,而不會將監聽加入訂閱者列表中。
static _RootContainerState ofNoBuild(BuildContext context) => context.findAncestorWidgetOfExactType<Root>().state;
點擊按鈕的函數,只須要調用上面的這個函數,在點擊的時候,Widget3就不會執行rebuild了。
除了這種方式之外,還有一個方式,那就是經過const關鍵字,將一個Widget設置爲常量Widget,即不會發生改變,這個時候rebuild的時候,系統會發現const Widget並無發生改變,就不會rebuild了,這也是爲何在Flutter中,不少不須要改變的Padding、Margin、Theme、Size等參數須要儘量設置爲const的緣由,這樣能夠在rebuild的時候,提升效率。
在Flutter中,Theme的實現,就是採用的這種方式。
Widget Tree的遍歷
前面提到了兩種方式來獲取Widget Tree中的InheritedWidget,dependOnInheritedWidgetOfExactType和findAncestorWidgetOfExactType,從調用結果上來看,一種是會被加入訂閱者名單,一種只是單純的查找。
下面再來繼續仔細的看看這兩個函數的區別。
findAncestorWidgetOfExactType
首先來看下這個函數的註釋。
從中咱們能夠提取幾個關鍵信息。
-
不會觸發rebuild -
O(n)複雜度 -
最好在didChangeDependencies中調用
因此findAncestorWidgetOfExactType有幾個比較經常使用的使用場景。
-
在斷言中判斷父Widget的使用條件 -
獲取父Widget對象,調用其方法
例如在一些Widget中,能夠經過Assert來判斷當前是否有使用該Widget的條件,例如Hero Widget。
dependOnInheritedWidgetOfExactType
首先也來看下這個函數的註釋。
-
會觸發rebuild -
O(1)複雜度 -
最好在didChangeDependencies中調用
能夠發現,其實他跟findAncestorWidgetOfExactType是很是相似的,主要的區別仍是在因而否會rebuild,另外,dependOnInheritedWidgetOfExactType的效率很高。
項目地址 Flutter Dojo
本文分享自微信公衆號 - Android羣英傳(android_heroes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。