【Flutter 專題】99 初識 EventBus

阿策小和尚發表於2021-07-10

      小菜在 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 並未對原始碼進行系統的研究,涉及還很淺顯;如有錯誤請多多指導!

來源: 阿策小和尚

相關文章