Flutter 99: 初识 EventBus

作者: 阿策神奇 | 来源:发表于2020-08-30 22:16 被阅读0次

          小菜在 Android 开发过程中经常会用到 EventBus 事件分发机制,EventBus 遵从 publish/subscribe 模式,即发布/订阅模式;简化了模块之间通信,对于项目的解耦很实用;而 Flutter 也提供了相应的 event_bus 插件,今天小菜学习尝试一下;

    EventBus

    源码分析

    class EventBus {
      StreamController _streamController;
      StreamController get streamController => _streamController;
    
      EventBus({bool sync = false})
          : _streamController = StreamController.broadcast(sync: sync);
    
      EventBus.customController(StreamController controller)
          : _streamController = controller;
    
      Stream<T> on<T>() {
        if (T == dynamic) {
          return streamController.stream;
        } else {
          return streamController.stream.where((event) => event is T).cast<T>();
        }
      }
    
      void fire(event) {
        streamController.add(event);
      }
    
      void destroy() {
        _streamController.close();
      }
    }
    

          简单分析源码可得,EventBus 核心主要是通过 Stream 来进行事件分发的,其中初始化时会创建一个 StreamController.broadcast(sync: sync) 广播流;fire() 广播发送方法主要是向 StreamController 中添加事件,on() 为广播监听,都是对 Stream 流操作;

    案例尝试

          小菜尝试做一个主题切换的小尝试,同时尝试了 EventBusProvider 两种方式;

    1. EventBus 初始化

          小菜首先创建一个全局的 EventBus,通常每个应用只有一个事件总线,但如果需要多个事件总线的话可以在初始化时设置 sync = false

    EventBus eventBus = EventBus();
    
    2. EventBus 定义事件

          小菜创建两个自定义事件,分别为语言切换和主题色切换;使用方法和 Android 一致;

    class LanguageEvent {
      String languageType;
      LanguageEvent(this.languageType);
    }
    
    class ThemeColorEvent {
      Color color;
      ThemeColorEvent(this.color);
    }
    
    3. EventBus 发布事件

          EventBus 通过 fire(event) 进行事件分发;

    return GestureDetector(
        child: Padding(
            padding: EdgeInsets.symmetric(vertical: 14),
            child: Row(children: <Widget>[
              _itemColorWid(_colorList[index]),
              Expanded(child: dataIndex == 1 ? _itemColorWid(_colorList[index]) : Text(_languageList[index])),
              SizedBox(width: 20),
              Icon(Icons.done)
            ])),
        onTap: () {
          eventBus.fire(dataIndex == 1 ? ThemeColorEvent(_colorList[index]) : LanguageEvent(_languageList[index]));
          Navigator.pop(context);
        });
    
    4. EventBus 接收事件

          对于 EventBus 的接收可以通过 on(event).listen() 来监听;其中若 on() 可以监听所有事件也可以监听固定的事件,区别是是否限制当前广播;

    // 监听所有广播
    eventBus.on().listen((event) {
      if (event is LanguageEvent) {
        print('EventBus --> LanguageEvent --> ${event.languageType}');
      } else if (event is ThemeColorEvent) {
        themeColor = event.color;
        print('EventBus --> ThemeColorEvent --> ${event.color}');
      }
      print('EventBus --> Type --> ${event.runtimeType}');
    });
    
    // 监听固定类型广播
    eventBus.on<LanguageEvent>().listen((event) {
      print('EventBus.on<LanguageEvent>() --> ${event.languageType}');
    });
    
    5. EventBus 销毁

          为了防止内存泄漏,一般在应用销毁时都需要对 EventBus 进行销毁;

    eventBus.cancel();
    eventBus.destroy();
    

          小菜预想的是在 main.dartrunApp(MyApp()) 中直接更改 ThemeData,但是 MyApp()StatelessWidget 无状态类型的,虽然可以通过 EventBus 监听切换主题,但是直接更新 UI 相对复杂一些;此时小菜尝试用 Provider 来进行主题切换,Provider 核心是 InheritedWidget 可以直接更新主题色;

    1. Provider 定义事件
    class ThemeColorNotifier with ChangeNotifier {
      Color themeColor;
    
      Color get getThemeColor => themeColor;
    
      setThemeColor(getThemeColor) {
        themeColor = getThemeColor;
        notifyListeners();
      }
    }
    
    2. Provider 发送通知
    _itemClick(dataIndex, index) {
      return Consumer<ThemeColorNotifier>(builder: (context, themeColor, _) {
        return GestureDetector(
            child: Padding(
                padding: EdgeInsets.symmetric(vertical: 14),
                child: Row(children: <Widget>[
                  _itemColorWid(_colorList[index]),
                  Expanded(child: dataIndex == 1 ? _itemColorWid(_colorList[index]) : Text(_languageList[index])),
                  SizedBox(width: 20),
                  Icon(Icons.done)
                ])),
            onTap: () {
              themeColor.setThemeColor(_colorList[index]);
              Navigator.pop(context);
            });
      });
    }
    
    3. Provider 接收通知
    return MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => ThemeColorNotifier())
        ],
        child: Consumer<ThemeColorNotifier>(builder: (context, themeColor, _) {
          return _mainProviderWid(themeColor);
        }));
        
    _mainProviderWid(themeColor) {
      return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
              primarySwatch: themeColor != null ? themeColor.getThemeColor : Colors.blue),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
          routes: _commonRoute());
    }
    

          小菜尝试了 EventBusProvider 两种方式进行主题色切换,对于不同的场景可以自由选择;给小菜最直接的感觉是 EventBus 主要是事件分发,只发送/接收数据,更偏向于数据层,而 Provider 实际是对 InheritedWidget 的优化和封装,可以在发送/接收数据同时更新 UI 层;

    小扩展

          小菜在测试过程中设置 ListView 对话框时出现如下错误:

    I/flutter (28408): The following assertion was thrown during performLayout():
    I/flutter (28408): RenderShrinkWrappingViewport does not support returning intrinsic dimensions.
    I/flutter (28408): Calculating the intrinsic dimensions would require instantiating every child of the viewport, which
    I/flutter (28408): defeats the point of viewports being lazy.
    I/flutter (28408): If you are merely trying to shrink-wrap the viewport in the main axis direction, you should be able
    I/flutter (28408): to achieve that effect by just giving the viewport loose constraints, without needing to measure its
    I/flutter (28408): intrinsic dimensions.
    

          小菜测试可以设置 ListViewContainer 宽或高即可,也可以将 ListView 包裹在容器中并为其设置宽度为 double.maxFinite

    _itemDialog(context, dataIndex) {
      return showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
                title: Row(children: <Widget>[
                  Icon(Icons.settings), SizedBox(width: 8),
                  Text(dataIndex == 1 ? 'Theme Color' : 'Language')
                ]),
                content: Container(
                    width: double.maxFinite,
                    child: ListView.builder(
                        itemCount: dataIndex == 1 ? _colorList.length : _languageList.length,
                        physics: ScrollPhysics(),
                        primary: false, shrinkWrap: true,
                        itemBuilder: (BuildContext context, int index) {
                          return _itemClick(dataIndex, index);
                        })));
          });
    }
    

          小菜仅是在应用中尝试了 EventBus 并未对源码进行系统的研究,涉及还很浅显;如有错误请多多指导!

    来源: 阿策小和尚

    相关文章

      网友评论

        本文标题:Flutter 99: 初识 EventBus

        本文链接:https://www.haomeiwen.com/subject/qzqysktx.html