Flutter 陈航 20-通讯 数据传递 Notification EventBus
目录
20 | 跨组件传递数据,只需要记住这三招
在 Flutter 中,实现跨组件数据传递的标准方式是通过属性传值。但是,对于视图层级比较深的 UI 样式,一个属性可能需要跨越很多层才能传递给子组件,这种传递方式会导致中间很多并不需要这个属性的组件,也需要接收其子 Widget 的数据,不仅繁琐而且冗余。
对于数据的跨层传递,Flutter 还提供了三种方案:
- InheritedWidget:数据从父 Widget 传递到子 Widget
- Notification:数据从子 Widget 传递到父 Widget
- EventBus:跨组件通信
InheritedWidget
Theme 类是通过 InheritedWidget 实现的典型案例:在子 Widget 中通过 Theme.of
找到上层 Theme 的 Widget,并建立观察者关系,当上层父 Widget 属性修改的时候,子 Widget 也会触发更新。
InheritedWidget 仅提供了数据读的能力,如果想要修改它的数据,需要把它和 State 配套使用:把 InheritedWidget 中的数据和相关的数据修改方法,全部移到 StatefulWidget 中的 State 上,而 InheritedWidget 只需要保留对它们的引用。
父 Widget
class CountContainer extends InheritedWidget {
/// 提供一个 of 方法,方便其子 Widget 在 Widget 树中找到它
static CountContainer of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CountContainer>() as CountContainer;
final IHomePageCount model;
final String time = formatDate(DateTime.now(), [hh, '-', nn, '-', ss]);
CountContainer({required this.model, Key? key, required Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(CountContainer oldWidget) => model.getCount() != oldWidget.model.getCount(); // 是否需要重建
}
子 Widget
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
CountContainer parent = CountContainer.of(context); //获取 InheritedWidget 节点
return Container(
color: Colors.amber,
child: GestureDetector(
child: Text('时间 ${parent.time},已点击 ${parent.model.getCount()} 次'),
onTap: () => parent.model.addCount(10),
),
);
}
}
Widget 树
class _HomePageState extends State<HomePage> implements IHomePageCount {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: CountContainer(
model: this,
// ignore: prefer_const_constructors
child: Counter(), // 注意,千万别加 const 关键字,否则 Counter 将不会刷新
),
floatingActionButton: FloatingActionButton(
onPressed: () => addCount(1),
child: Text("$_count"),
));
}
@override
int getCount() => _count;
@override
void addCount(int add) => setState(() => _count += add);
}
abstract class IHomePageCount {
int getCount();
void addCount(int add);
}
- 无论子 Counter 在父 CountContainer 下层什么位置,都能获取到父 Widget 的属性 time 和 model
- 通过 model 提供的方法,子 Counter 也可以间接更新父 CountContainer 的 time 属性
Notification
通过给 ListView 嵌套一层 NotificationListener,便可在 onNotification 回调中获取到子 Widget 的滚动事件。
Notification 通知的监听则与此类似。
定义通知
Notification 提供的 dispatch
方法,可以沿着 context 对应的 Element
节点树向上逐层发送通知。
class CustomNotification extends Notification {
CustomNotification(this.msg);
final String msg;
@override
void dispatch(BuildContext? target) {
super.dispatch(target);
flog("dispatch target=${target?.widget}");
}
}
发送通知
点击子 Widget 时发送通知
class CustomChild extends StatelessWidget {
const CustomChild({super.key});
@override
Widget build(BuildContext context) => ElevatedButton(
onPressed: () => CustomNotification(DateTime.now().millisecond.toString()).dispatch(context),
child: const Text("发送通知"),
);
}
监听通知
通过给 子 Widget 嵌套一层 NotificationListener,便可在 onNotification 回调中获取到子 Widget 发送的通知。
NotificationListener<CustomNotification>(
onNotification: (notification) {
flog("收到子 Widget 发送的通知:${notification.msg}");
setState(() => _msg = notification.msg);
return true;
},
child: Column(children: <Widget>[Text(_msg), const CustomChild()]),
)
EventBus
InheritedWidget 和 Notification 的使用场景都需要依靠 Widget 树,只能在有父子关系的 Widget 之间进行数据共享。
事件总线 event_bus 是在 Flutter 中实现跨组件通信的机制。它遵循发布/订阅
模式,发布者和订阅者之间无需有父子关系,并且非 Widget 对象也可以发布/订阅
。
dependencies:
flutter:
sdk: flutter
event_bus: ^2.0.0
自定义事件类
EventBus 可以支持任意对象的传递。
class CustomEvent {
String msg;
CustomEvent(this.msg);
}
发送和监听
class _HomePageState extends State<HomePage> {
EventBus eventBus = EventBus();
String _msg = "通知";
late StreamSubscription subscription;
@override
initState() {
subscription = eventBus.on<CustomEvent>().listen((event) {
flog("onData event=${event.msg}"); // 监听 CustomEvent 事件
setState(() => _msg = event.msg);
});
super.initState();
}
@override
dispose() {
subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Text(_msg),
floatingActionButton: FloatingActionButton(
onPressed: () => eventBus.fire(CustomEvent(DateTime.now().millisecond.toString())),
child: Text(_msg),
));
}
}
四种数据共享方式总结
- 对于层级较深的视图,使用 InheritedWidget 可以实现子 Widget 跨层共享父 Widget 的属性
- InheritedWidget 中的属性在子 Widget 中只能读,和 StatefulWidget 中的 State 配套使用便可以修改
- 使用 NotificationListener 可以在父 Widget 监听来自子 Widget 的事件
- EventBus 是一种无需发布者与订阅者之间存在父子关系的数据同步机制
2023-1-7
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/17033590.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2019-01-07 Javassist 字节码 简介 案例
2019-01-07 自动化打包 Jenkins 持续集成
2018-01-07 AS 功能 设置 SDK AVD 简介