首页 > 其他分享 >Flutter 陈航 20-通讯 数据传递 Notification EventBus

Flutter 陈航 20-通讯 数据传递 Notification EventBus

时间:2023-01-07 21:36:19浏览次数:62  
标签:CountContainer Widget 20 Notification 陈航 override child msg InheritedWidget

本文地址


目录

目录

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

相关文章

  • 三目运算符——the fifteenth——2023.1.6
    三目运算符表达式1?表达式2:表达式3;意思是:先执行表达式1,如果表达式1的结果为真,则执行表达式2,结果就是表达式2的结果;如果表达式1的结果为假,则执行表达式3,结果为表达3的结......
  • 2022USACO-DEC-Silver
    题目链接T1.BarnTreeT2.CircularBarnT3.RangeReconstructionT1下面均以\(1\)为根来进行分析。算法思路:首先,定义一个数组dis表示当前子树的干草数和与应有的干草......
  • 1.4 SMU Winter 2023 Round #1 (Div.2)
    [语言月赛202212]不可以,总司令思路:比较大小 if(x>y)cout<<"NO";elseif(x<y)cout<<"YES";elsecout<<"equalprobability"; [语言月赛202212]计算......
  • 2022CSP-J线上游记
    写在前面安徽CSP取消了……去年CSP考炸的我本来想今年一雪前耻(bushi),结果……T1第一题大毒瘤!首先观察数据可以分类如下两种情况:\(a=1\)直接输出\(1\),return0\(a......
  • 【NOI2019】序列 题解(贪心模拟费用流)
    (感觉是有史以来自己代码最好看的一次贪心模拟费用流。LG传送门Solution1经过一番思考,不难发现我们可以根据题面建图跑费用流。具体见下图:(从@cmd大佬那里薅来的。)然......
  • 【题解】P4632 [APIO2018] 新家
    码力底下,思维迟钝,我该怎么办?还是说这题太毒?题意给定一个\(n\)个商店,第\(i\)个商店的类型为\(t_i\),在\([a_i,b_i]\)时间营业,位于位置\(x_i\)。定义某一时刻一......
  • [点分治记录] P4292 [WC2010]重建计划
    题目看到需要求的柿子首先想到分数规划。也就是二分答案,然后在check里将所有边权减去$mid$,检验是否有路经权值$\ge$0。现在问题转化成求路径长度在$[l,r]$范围内的权值......
  • USACO 2022 Cu 题解
    USACO2022Cu题解AK用时:$3$小时$30$分钟。A-CowCollege原题FarmerJohn计划为奶牛们新开办一所大学!有$N$($1\leN\le10^5$)头奶牛可能会入学。每......
  • 代码随想录day10 LeetCode20 有效的括号 1047. 删除字符串中的所有相邻重复项
     LeetCode20有效的括号 https://leetcode.cn/problems/valid-parentheses/submissions/流程为遍历每一个字符并判断是否为左括号还是有括号,若为左括号则放入栈中,若为......
  • Flutter 陈航 19-手势识别 PointerEvent GestureDetector GestureRecognizer
    本文地址目录目录目录19|用户交互事件该如何响应?指针事件ListenerListener完整代码手势识别GestureDetector拖拽和缩放手势竞技场竞技场默认行为改变竞技场行为Gestu......