在講解Provider
之前呢,因為Provider
會大量使用到 InheritedWidget
來實現資料共享的功能,所以我們來先為各位介紹InheritedWidget
前言:
首先Flutter 用分層設計來實現畫面的渲染,像很多文章會提到Render Tree,可參考,內容只要Widget
、Element
和RenderObject
Widget → Describes the configuration for an Element.
為我們的控件,用來描述對應的Element
的描述或配置
Element → An instantiation of a Widget at a particular location in the tree.
element
組成了element tree,Element
的主要功能就是維護這棵樹,節點的增加,刪除,更新,樹的遍歷都在這裡完成,Element
都是從Widget
中生成的,每個Widget
也都會對應一個Element
RenderObject → An object in the render tree
負責具體佈局,繪製
InheritedWidget:
為Flutter
中非常重要的一個功能型Widget,讓資料可以在Widget Tree 中向下傳遞、共享,提供子控件使用,例如:Flutter 的SDK 正是透過InheritedWidget
來共享主題( Theme ) 和當前語言環境( Locale ) 資訊的
可以理解成InheritedWidget
是一個能高效的在Widget 樹中傳遞共享資料的基本類別
繼承InheritedWidget
覆寫updateShouldNotify
方法,來決定什麼時候要通知說資料有變化
提供了靜態方法 of
來獲取實例
核心在使用BuildContext.dependOnInheritedWidgetOfExactType
獲取指定類型的 InheritedWidget 的實例
dependOnInheritedWidgetOfExactType
的BuildContext
註冊到對應的InheritedWidget
並監聽BuildContext
即調用者Widget 本身所對應的Element 實例dependOnInheritedWidgetOfExactType
,即建立了兩個Widget 的依賴關係範例一:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
範例二:
Theme.of
使用BuildContext.dependOnInheritedWidgetOfExactType
來查找InheritedWidget
並返回 ThemeData
class Theme extends StatelessWidget {
...
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
if (shadowThemeOnly) {
if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
return null;
return inheritedTheme.theme.data;
}
...
}
}
class _InheritedTheme extends InheritedWidget {
...
}
之前提到的StatefulWidget
,其內部所管理的狀態有一個didChangeDependencies
方法,它會在依賴發生變化時被Flutter Framework調用,而這個依賴指的就是是否使用了父widget中InheritedWidget
的數據,如果使用了,則代表有依賴,如果沒有使用則代表沒有依賴,這樣的模式可以使子widget在所依賴的InheritedWidget
變化時來更新內容,例如:Theme、Locale 等發生變化時,依賴其的子widget的didChangeDependencies
方法將會被調用
範例:
counter_inherited_widget.dart
import 'package:flutter/material.dart';
class CounterInheritedWidget extends InheritedWidget {
CounterInheritedWidget({@required this.count, Widget child})
: super(child: child);
// 共享的資料,計數的值
final int count;
// 獲取CounterInheritedWidget實例的方法, 方便widget樹中的子widget獲取共享的資料
// 必須在State中調用才會有效
static CounterInheritedWidget of(BuildContext context) {
// 調用共享數據的子widget將不會回調didChangeDependencies方法,即子widget將不會更新
return context.dependOnInheritedWidgetOfExactType<CounterInheritedWidget>();
}
// 決定是否通知樹中有依賴共享數據的子widget (true 就通知)
@override
bool updateShouldNotify(CounterInheritedWidget oldWidget) {
return oldWidget.count != count;
}
}
counter_text.dart
:
import 'package:flutter/material.dart';
import 'counter_inherited_widget.dart';
class CounterText extends StatefulWidget {
@override
_CounterTextState createState() {
return _CounterTextState();
}
}
class _CounterTextState extends State<CounterText> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享資料
return Text(
"count: ${CounterInheritedWidget.of(context).count.toString()}");
}
//父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時會被調用
//如果在build中沒有依賴InheritedWidget,則此回調就不會被調用
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
counter_widget.dart
:
import 'package:flutter/material.dart';
import 'counter_inherited_widget.dart';
import 'counter_text.dart';
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() {
return _CounterWidgetState();
}
}
class _CounterWidgetState extends State<CounterWidget> {
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: CounterInheritedWidget(
count: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CounterText(),
//每點擊一次,將count值遞增,然後重新build,CounterInheritedWidget的data將被更新
RaisedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
),
),
),
),
);
}
}
main.dart
:
import 'package:flutter/material.dart';
import 'counter_widget.dart';
void main() {
runApp(CounterWidget());
}
就能完成我們InheritedWidget
的範例囉
介紹完了基礎的InheritedWidget
用法,下一篇將正式進入Provider 的內容