Flutter 之使用 Event Bus 更改主题

3,748 阅读4分钟

介绍

随着每个工程的MVC模块逐渐增多,模块与模块之间的通信也变得越来越多,代码耦合必然增加。

Event Bus 为解耦而生,热别是设计模式为MVCMVP的项目。

源码

import 'dart:async';

class EventBus {
  StreamController _streamController;

  /// Controller for the event bus stream.
  StreamController get streamController => _streamController;

  /// Creates an [EventBus].
  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);

  /// Instead of using the default [StreamController] you can use this constructor
  EventBus.customController(StreamController controller)
      : _streamController = controller;

  /// Listens for events of Type [T] and its subtypes.
  Stream<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }

  /// Fires a new event on the event bus with the specified [event].
  void fire(event) {
    streamController.add(event);
  }

  /// Destroy this [EventBus]. This is generally only in a testing context.
  void destroy() {
    _streamController.close();
  }
}

是的你没有看错,源码就只有这些。EventBus之所以这样简洁得益于Dart中优秀的StreamStream的用法还是很多的,Event Bus中主要使用Streamlisten()方法。

从源码很容易看出Event bus的用法:

初始化

  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);

streamController作为Dart官方内置类,实际上就是stream数据的控制器。

sync参数代表事件流是否立刻传递到监听者,默认为false

broadcast这种广播的初始化方式可以让stream被多个人订阅,如果你只想被一个人订阅请使用StreamController对应single的初始化方式,这里不过多讨论。

EventBus也支持你使用EventBus.customController(StreamController controller)的方式自定义StreamController

监听

监听方式为eventBus.on<T>().listen()listen()方法是内置类StreamAPI

  Stream<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }
  StreamSubscription<T> listen(void onData(T event),
      {Function onError, void onDone(), bool cancelOnError});

Event Bus通过泛型过滤,你可以只处理自己定义的事件类型。每当streamController添加你监听的事件时,监听回调将会执行。listen()方法会返回一个StreamSubscription,如果你想取消监听可以调用对应的cancel()方法

触发事件

  void fire(event) {
    streamController.add(event);
  }

触发事件比较简单,streamController调用add()方法给Stream添加事件,添加完成后广播给所有对应监听者。

实战

这里我们使用Event Bus实现一个变换主题颜色的功能,先看下效果:

初始化

dependencies:
  event_bus: ^1.1.1

添加依赖之后别忘记flutter pub get

Coding

先定义一个主题颜色的事件:

import 'package:flutter/material.dart';

class ThemeColor {
  final Color color;
  
  ThemeColor(this.color);
}

然后项目首页初始化一个eventBus,并在initState时监听ThemeColor事件。

import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutterdemo/events/theme_color.dart';

void main() => runApp(MyApp());

EventBus eventBus = EventBus(); // 初始化 EventBus

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  Color _themeColor;

  @override
  void initState() {
    super.initState();
    // 监听 ThemeColor 事件
    eventBus.on<ThemeColor>().listen((event) {
      setState(() {
        _themeColor = event.color;
      });
    });
  }

  @override
    Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primaryColor:  _themeColor, // 事件回调的颜色赋值给 ThemeData
      ),
    );
  }
}

定义一套主题颜色,在更改颜色时发送ThemeColor事件。发送后监听的回调方法里重新设置主题颜色。

import 'package:flutter/material.dart';
import 'package:flutterdemo/components/common_app_bar.dart';
import 'package:flutterdemo/events/theme_color.dart';
import 'package:flutterdemo/main.dart';

class PersonSetting extends StatefulWidget {
  PersonSetting({Key key}) : super(key: key);

  @override
  _PersonSettingState createState() => _PersonSettingState();
}

class _PersonSettingState extends State<PersonSetting> {
  
  Color _themeColor;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CommonAppBar(title: '设置',),
      body: Center(
        child: DropdownButton(
          value: _themeColor,
          items: <DropdownMenuItem>[
            DropdownMenuItem(value: Color(0xFF2196F3), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF2196F3),),),),
            DropdownMenuItem(value: Color(0xFFE3F2FD), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFE3F2FD),),),),
            DropdownMenuItem(value: Color(0xFFBBDEFB), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFBBDEFB),),),),
            DropdownMenuItem(value: Color(0xFF90CAF9), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF90CAF9),),),),
            DropdownMenuItem(value: Color(0xFF64B5F6), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF64B5F6),),),),
            DropdownMenuItem(value: Color(0xFF42A5F5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF42A5F5),),),),
            DropdownMenuItem(value: Color(0xFF1E88E5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1E88E5),),),),
            DropdownMenuItem(value: Color(0xFF1976D2), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1976D2),),),),
            DropdownMenuItem(value: Color(0xFF1565C0), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1565C0),),),),
            DropdownMenuItem(value: Color(0xFF0D47A1), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF0D47A1),),),),
          ],
          onChanged: (color) {
              eventBus.fire(ThemeColor(color)); // 发送事件
              setState(() {
                _themeColor = color;
              });
          },
        ),
      ),
    );
  }
}

其实Event Bus部分的代码很少,耦合性也很低,这里大部分的代码都是UI布局。

总结

Event Bus使用起来很简单,常规的用法就是定义事件,监听事件,发送事件,如何取消监听上面也已经提到了。当然你也可以自定义streamController做更多的事情,这里只是抛砖引玉简单的替换了主题颜色。

当然更改主题颜色使用其他方案也可以实现,如何使用Event Bus大家也可以参考官方的 example,归根结底都是为了减少代码之间的依赖从而降低代码的耦合度。