Flutter中的异步(四)

Stream流实现信号灯

信号灯的流程

信号灯的跳转可以看做是在一个时间轴上发生的连续的事件,可以将信号灯的每次变化都看做是stream中的一个元素,每秒监听状态的通知,然后更新UI界面。

信号灯组件构建

信号灯状态

js 复制代码
const int _kAllowMaxCount = 10;
const int _kWaitMaxCount = 3;
const int _kDenialMaxCount = 10;

class SignalState {
  final int counter;
  final SignalType type;

  SignalState({
    required this.counter,
    required this.type,
  });
}

enum SignalType {
  allow, // 允许 - 绿灯
  denial, // 拒绝 - 红灯
  wait, // 等待 - 黄灯
}

SignalState表示秒数和当前状态。

其中SignalType是一个枚举类型分别为红绿灯的三种状态。

信号灯组件构建

如下所示,信号灯由三个 Lamp 组件和数字构成。三个灯分别表示 红、黄、绿 ,某一时刻只会量一盏,不亮的使用灰色示意。三个灯水平排列,有一个黑色背景装饰,和文字呈上下结构。

lamp组件构建如下所示:

js 复制代码
class Lamp extends StatelessWidget {
  final Color? color;

  const Lamp({Key? key, required this.color}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 40,
      height: 40,
      decoration: BoxDecoration(
        color: color ?? Colors.grey.withOpacity(0.8),
        shape: BoxShape.circle,
      ),
    );
  }
}

如下表示SinalLamp组件:

js 复制代码
class SignalLamp extends StatelessWidget {
  final SignalState state;

  const SignalLamp({Key? key, required this.state}) : super(key: key);

  Color get activeColor {
    switch (state.type) {
      case SignalType.allow:
        return Colors.green;
      case SignalType.denial:
        return Colors.red;
      case SignalType.wait:
        return Colors.amber;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
          decoration: BoxDecoration(
              color: Colors.black, borderRadius: BorderRadius.circular(30),),
          child: Wrap(
            alignment: WrapAlignment.center,
            crossAxisAlignment: WrapCrossAlignment.center,
            spacing: 15,
            children: [
              Lamp(color: state.type == SignalType.denial ? activeColor : null),
              Lamp(color: state.type == SignalType.wait ? activeColor : null),
              Lamp(color: state.type == SignalType.allow ? activeColor : null),
            ],
          ),
        ),
        Text(
          state.counter.toString(),
          style: TextStyle(
              fontWeight: FontWeight.bold, fontSize: 50, color: activeColor,
          ),
        )
      ],
    );
  }
}

stream事件添加与监听

使用Stream流来监听信号灯状态,并根据状态的改变来渲染界面。

js 复制代码
---->[SignalState]----
SignalState next() {
  if (counter > 1) {
    return SignalState(type: type, counter: counter - 1);
  } else {
    switch (type) {
      case SignalType.allow:
        return SignalState(
            type: SignalType.denial, counter: _kDenialMaxCount);
      case SignalType.denial:
        return SignalState(type: SignalType.wait, counter: _kWaitMaxCount);
      case SignalType.wait:
        return SignalState(type: SignalType.allow, counter: _kAllowMaxCount);
    }
  }
}

把每个事件通知看做元素,Stream 应用处理事件序列,只不过序列中的元素在此刻是未知的,何时触发也是不定的。Stream 基于 发布-订阅 的思想通过监听来处理这些事件。 其中两个非常重要的角色: 发布者 是元素的生产者,订阅者 是元素的消费者。

在引擎中的 async 包中封装了 StreamController 类用于控制元素的添加操作,同时提供 Stream 对象用于监听。代码处理如下,tag1 处,监听 streamControllerstream 对象。事件到来时触发 emit 方法 ( 方法名任意 ),在 emit 中会回调出 SignalState 对象,根据这个新状态更新界面即可。然后延迟 1s 继续添加下一状态。

js 复制代码
---->[_MyHomePageState]----
final StreamController<SignalState> streamController = StreamController();
SignalState _signalState = SignalState(counter: 10, type: SignalType.denial);

@override
void initState() {
  super.initState();
  streamController.stream.listen(emit); // tag1
  streamController.add(_signalState);
}

@override
void dispose() {
  super.dispose();
  streamController.close();
}

void emit(SignalState state) async {
  _signalState = state;
  setState(() {});
  await Future.delayed(const Duration(seconds: 1));
  streamController.add(state.next());
}

stream的控制与异常监听

Stream#listen 方法会返回一个 StreamSubscription 的订阅对象,通过该对象可以暂停、恢复、取消对流的监听。 StreamController 在构造时可以传入四个函数来监听流的状态:

  • onListen()
  • onPause()
  • onResume()
  • onCancel()

onListen 会在 stream 成员被监听时触发一次;onPauseonResumeonCancel 分别对应订阅者的 pauseresumecancel 方法。

相关推荐
今天又在摸鱼2 分钟前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿4 分钟前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再6 分钟前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref
jingling55511 分钟前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架
拾光拾趣录16 分钟前
CSS 深入解析:提升网页样式技巧与常见问题解决方案
前端·css
莫空000017 分钟前
深入理解JavaScript属性描述符:从数据属性到存取器属性
前端·面试
guojl18 分钟前
深度剖析Kafka读写机制
前端
FogLetter18 分钟前
图片懒加载:让网页飞起来的魔法技巧 ✨
前端·javascript·css
Mxuan19 分钟前
vscode webview 插件开发(精装篇)
前端
Mxuan20 分钟前
vscode webview 插件开发(交付篇)
前端