InheritedWidget 是 Flutter 中的一個功能型 Widget,適用于在 Widget 樹中共享數(shù)據(jù)的場景背稼。
class CounterPage extends StatefulWidget {
CounterPage({Key ? key}) :super(key: key);
@override
? _CounterPageState createState() =>_CounterPageState();
}
//
class _CounterPageState extends State {
int count =0;
//3我們通過 InheritedCountContainer.of 方法找到它衷恭,獲取計數(shù)狀態(tài) count 并展示:
? void _incrementCounter() => setState(() {count++;});// 修改計數(shù)器
? @override
? Widget build(BuildContext context) {
//2我們使用 CountContainer 作為根節(jié)點离福,并用 0 初始化 count谐岁。
? ? return CountContainer(
model:this,
increment: _incrementCounter,// 提供修改數(shù)據(jù)的方法
? ? ? ? child:Counter()
);
}
}
/*1
* 首先,為了使用 InheritedWidget贱纠,我們定義了一個繼承自它的新類 CountContainer。
* 然后响蕴,我們將計數(shù)器狀態(tài) count 屬性放到 CountContainer 中谆焊,
* 并提供了一個 of 方法方便其子 Widget 在 Widget 樹中找到它。
* 最后浦夷,我們重寫了 updateShouldNotify 方法辖试,這個方法會在 Flutter
*? 判斷 InheritedWidget 是否需要重建,從而通知下層觀察者組件更新數(shù)據(jù)時被調(diào)用到劈狐。
* 在這里罐孝,我們直接判斷 count 是否相等即可。
* */
class CountContainer extends InheritedWidget {
static CountContainer?of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
final _CounterPageState model;// 直接使用 MyHomePage 中的 State 獲取數(shù)據(jù)
? final Function()increment;
CountContainer({
Key ?key,
required this.model,
required this.increment,
required Widget child,
}):super(key: key, child: child);
@override
? bool updateShouldNotify(CountContainer oldWidget) =>model != oldWidget.model;
}
class Counter extends StatelessWidget {
@override
? Widget build(BuildContext context) {
// 獲取 InheritedWidget 節(jié)點
? ? CountContainer?state =CountContainer.of(context);
return Scaffold(
appBar:AppBar(
title:Text("InheritedWidget demo"),
),
body:Text(
'You have pushed the button this many times: ${state?.model.count}',// 關(guān)聯(lián)數(shù)據(jù)讀方法
? ? ? ),
floatingActionButton:FloatingActionButton(onPressed:state?.increment),// 關(guān)聯(lián)數(shù)據(jù)修改方法
? ? );
}
}
Notification 是 Flutter 中進行跨層數(shù)據(jù)共享的另一個重要的機制肥缔。如果說 InheritedWidget 的數(shù)據(jù)流動方式是從父 Widget 到子 Widget 逐層傳遞莲兢,那 Notificaiton 則恰恰相反,數(shù)據(jù)流動方式是從子 Widget 向上傳遞至父 Widget续膳。這樣的數(shù)據(jù)傳遞機制適用于子 Widget 狀態(tài)變更怒见,發(fā)送通知上報的場景。
class CustomNotification extends Notification {
CustomNotification(this.msg);
final String msg;
}
class CustomChild extends StatelessWidget {
@override
? Widget build(BuildContext context) {
return RaisedButton(
//按鈕點擊時分發(fā)通知
? ? ? onPressed: () =>CustomNotification("Hi").dispatch(context),
child:Text("Fire Notification"),
);
}
}
class NotificationWidget extends StatefulWidget {
@override
? StatecreateState()=>_NotificationState();
}
class _NotificationState extends State {
String _msg ="通知:";
@override
? Widget build(BuildContext context) {
//監(jiān)聽通知
? ? return NotificationListener(
onNotification: ( notification) {
setState(() {_msg += notification.msg+"? ";});
return true;
},
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children: [Text(_msg),CustomChild()],
)
);
}
}
無論是 InheritedWidget 還是 Notificaiton姑宽,它們的使用場景都需要依靠 Widget 樹遣耍,也就意味著只能在有父子關(guān)系的 Widget 之間進行數(shù)據(jù)共享。但是炮车,組件間數(shù)據(jù)傳遞還有一種常見場景:這些組件間不存在父子關(guān)系舵变。這時,事件總線 EventBus 就登場了瘦穆。
事件總線是在 Flutter 中實現(xiàn)跨組件通信的機制纪隙。它遵循發(fā)布 / 訂閱模式,允許訂閱者訂閱事件扛或,當(dāng)發(fā)布者觸發(fā)事件時绵咱,訂閱者和發(fā)布者之間可以通過事件進行交互。發(fā)布者和訂閱者之間無需有父子關(guān)系熙兔,甚至非 Widget 對象也可以發(fā)布 / 訂閱悲伶。
// 所以在這里艾恼,我們傳輸數(shù)據(jù)的載體就選擇了一個有字符串屬性的自定義事件類 CustomEvent:
class CustomEvent {
String msg;
CustomEvent(this.msg);
}
// 建立公共的 event bus
EventBus eventBus =new EventBus();
/*
* 我們定義了一個全局的 eventBus 對象,并在第一個頁面監(jiān)聽了 CustomEvent 事件麸锉,
* 一旦收到事件钠绍,就會刷新 UI。
* 需要注意的是花沉,千萬別忘了在 State 被銷毀時清理掉事件注冊柳爽,
* 否則你會發(fā)現(xiàn) State 永遠被 EventBus 持有著,無法釋放碱屁,從而造成內(nèi)存泄漏:
*
*? */
class FirstPage extends StatefulWidget {
@override
? StatecreateState()=>_FirstPageState();
}
class _FirstPageState extends State {
String msg ="通知:";
late StreamSubscription subscription;
@override
? void initState() {
//監(jiān)聽CustomEvent事件磷脯,刷新UI
? ? subscription =eventBus.on().listen((event) {
print(event.msg);
setState(() {
msg += event.msg;
});
});
super.initState();
}
dispose() {
subscription.cancel();//State銷毀時,清理注冊
? ? super.dispose();
}
@override
? Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(title:Text("First Page"),),
body:Text(msg),
floatingActionButton:FloatingActionButton(onPressed: ()=>Navigator.push(context,MaterialPageRoute(builder: (context) =>SecondPage()))),
);
}
}
//我們在第二個頁面以按鈕點擊回調(diào)的方式娩脾,觸發(fā)了 CustomEvent 事件:
class SecondPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(title:Text("Second Page"),),
body:RaisedButton(
child:Text('Fire Event'),
// 觸發(fā)CustomEvent事件
? ? ? ? ? onPressed: ()=>eventBus.fire(CustomEvent("hello"))
),
);
}
}