目录
目录20 | 跨组件传递数据,只需要记住这三招
对于 Flutter 这样大量依靠组合 Widget 的行为来实现用户界面的框架来说,如何确保数据的改变能够映射到最终的视觉效果上优为重要。
在 Flutter 中,实现跨组件数据传递的标准方式是通过属性传值。但是,对于稍微复杂一点的、尤其视图层级比较深的 UI 样式,一个属性可能需要跨越很多层才能传递给子组件,这种传递方式就会导致中间很多并不需要这个属性的组件也需要接收其子 Widget 的数据,不仅繁琐而且冗余。
所以,对于数据的跨层传递,Flutter 还提供了三种方案:InheritedWidget、Notification 和 EventBus。
InheritedWidget
InheritedWidget 是 Flutter 中的一个功能型 Widget,适用于在 Widget 树中共享数据的场景。通过它,我们可以高效地将数据在 Widget 树中进行跨层传递。
Theme 类是通过 InheritedWidget 实现的典型案例。在子 Widget 中通过
Theme.of
方法找到上层 Theme 的 Widget,获取到其属性的同时,建立子 Widget 和上层父 Widget 的观察者关系,当上层父 Widget 属性修改的时候,子 Widget 也会触发更新。
不过,InheritedWidget 仅提供了数据读的能力,如果我们想要修改它的数据,则需要把它和 StatefulWidget 中的 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
- InheritedWidget 的数据流动方式是从父 Widget 到子 Widget 逐层传递
- Notificaiton 的数据流动方式是从子 Widget 向上传递至父 Widget
在前面的第 13 篇文章中我们知道,通过给 ListView 嵌套一层 NotificationListener,便可在 onNotification 回调中获取到子 Widget 的滚动事件。自定义通知的监听则与此类似。
定义通知
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 和 Notificaiton 的使用场景都需要依靠 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),
));
}
}
四种数据共享方式总结
- 对于视图层级比较深的 UI 样式,直接通过属性传值的方式会导致很多中间层增加冗余属性,而使用 InheritedWidget 可以实现子 Widget 跨层共享父 Widget 的属性。
- InheritedWidget 中的属性在子 Widget 中只能读,如果有修改的场景,我们需要把它和 StatefulWidget 中的 State 配套使用。
- 使用 NotificationListener,可以在父 Widget 监听来自子 Widget 的事件。
- EventBus 是一种无需发布者与订阅者之间存在父子关系的数据同步机制。
2023-1-7
标签:CountContainer,Widget,20,Notification,陈航,override,child,msg,InheritedWidget From: https://www.cnblogs.com/baiqiantao/p/17033590.html