往期回顧
從零開始的Flutter之旅: StatelessWidget
從零開始的Flutter之旅: StatefulWidget
在之前的文章中,介紹了StatelessWidget與StatefulWidget的特性與它們的呈現原理。
這期要聊的是它們的另一個兄弟InheritedWidget。
特性
InheritedWidget是Flutter中的一個非常重要的功能元件,它能夠提供資料在widget樹中從上到下進行傳遞。保證資料在不同子widget中進行共享。這對於一些需要使用共享資料的場景非常有效,例如,在Flutter SDK中就是通過InheritedWidget來共享應用的主題與語言資訊。
可能你還有點模糊,別急,下面我們通過一個簡單的示例來了解InheritedWidget。
示例
相信開始學Flutter時都看過官方的計數器示例。我們將官方提供的計數器示例使用InheritedWidget進行改造。
首先我們需要一個CountInheritedWidget,它繼承於InheritedWidget。
class CountInheritedWidget extends InheritedWidget {
CountInheritedWidget({@required this.count, Widget child})
: super(child: child);
// 共享資料,計數的數量
final int count;
// 統一的獲取CountInheritedWidget例項, 方便樹中子widget的獲取共享資料
// 必須在State中呼叫才會有效
static CountInheritedWidget of(BuildContext context) {
// 呼叫共享資料的子widget將不會回撥didChangeDependencies方法,即子widget將不會更新
// return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
// true -> 通知樹中依賴改共享資料的子widget
@override
bool updateShouldNotify(CountInheritedWidget oldWidget) {
return oldWidget.count != count;
}
}
複製程式碼
- 在CountInheritedWidget中提供共享計數的數量count
- 同時為外部提供統一的獲取CountInheritedWidget例項的of方法
- 最後再重寫updateShouldNotify方法,來通知依賴該共享count的子widget進行更新
現在已經有了共享資料count的提供,接下來是在具體的子widget中進行使用。
我們抽離出一個CountText子widget
class CountText extends StatefulWidget {
@override
_CountTextState createState() {
return _CountTextState();
}
}
class _CountTextState extends State<CountText> {
@override
Widget build(BuildContext context) {
return Text("count: ${CountInheritedWidget.of(context).count.toString()}");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
複製程式碼
- 內部引用了CountInheritedWidget中的共享資料count,通過of方法獲取CountInheritedWidget例項
- didChangeDependencies可以用來監聽子widget依賴是否反生改變
最後,我們再將CountInheritedWidget與CountText結合起來,通過簡單的點選自增事件來看下效果
class CountWidget extends StatefulWidget {
@override
_CountState createState() {
return _CountState();
}
}
class _CountState extends State<CountWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Count App',
theme: new ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
title: Text("Count"),
),
body: Center(
child: CountInheritedWidget(
count: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CountText(),
RaisedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
),
),
),
),
);
}
}
複製程式碼
上面的層級關係是CountText剛好是CountInheritedWidget的子widget。
現在我們通過點選事件直接改變外部的count值,如果InheritedWidget從上到下資料傳到的效果能夠生效,那麼在CountText中引用的count將會與外部count同步,程式呈現的效果將會是自增的,同時由於依賴的count發生改變CountText中的didChangeDependencies也會回撥。
我們直接執行一下
點選後的輸出日誌
I/flutter: didChangeDependencies
複製程式碼
說明InheritedWidget的效果已經生效,通過InheritedWidget的使用,我們可以很方便的在巢狀下層的子widget中拿到上層的資料,或者說整個widget的共享資料。
分析
在依賴方式改變時子widget的didChangeDependencies會回撥,但由於你可能會在該方法中做一些特殊的操作,例如網路請求。只是需要一次就可以。如果是套用我們上面的示例,將會在count子增時反覆呼叫。
為了防止didChangeDependencies的呼叫,我們再來看CountInheritedWidget的of方法中註釋的那部分
static CountInheritedWidget of(BuildContext context) {
// 呼叫共享資料的子widget將不會回撥didChangeDependencies方法,即子widget將不會更新
// return context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>().widget;
return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
複製程式碼
我們使用的是dependOnInheritedWidgetOfExactType方法,依賴的共享資料發生改變時會回撥子widget中的didChangeDependencies方法,如果我們不想要子widget呼叫該方法,可以使用註釋的程式碼,通過getElementForInheritedWidgetOfExactType方法來獲取共享資料。
如果此時我們再執行一下專案,點選count自增,控制檯將不會再輸出日誌。這樣就可以解決didChangeDependencies的反覆呼叫。
而這兩個方法的主要區別是在dependOnInheritedWidgetOfExactType呼叫的過程中會進行註冊依賴關係
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
複製程式碼
所以dependOnInheritedWidgetOfExactType更新依賴的子widget中的didChangeDependencies方法。
思考下一個問題,雖然現在didChangeDependencies方法不會呼叫,但是CountText的build方法還是會執行。原因是在CountWidget中通過setState來改變count值,會重新build所用的子widget。但我們真正想要的只是更新子widget中依賴的CountInheritedWidget的元件值。
那麼如何解決呢?這裡提供一個解決方案是為子widget提供快取。可以通過封裝一個簡單的StatefulWidget,將子widget快取起來。如果對這塊感興趣的,可以期待我的後續文章。
推薦專案
下面介紹一個完整的Flutter專案,對於新手來說是個不錯的入門。
flutter_github,這是一個基於Flutter的Github客戶端同時支援Android與IOS,支援賬戶密碼與認證登陸。使用dart語言進行開發,專案架構是基於Model/State/ViewModel的MSVM;使用Navigator進行頁面的跳轉;網路框架使用了dio。專案正在持續更新中,感興趣的可以關注一下。
當然如果你想了解Android原生,相信flutter_github的純Android版本AwesomeGithub是一個不錯的選擇。
如果你喜歡我的文章模式,或者對我接下來的文章感興趣,建議您關注我的微信公眾號:【Android補給站】
或者掃描下方二維碼,與我建立有效的溝通,同時更快更準的收到我的更新推送。