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
处,监听 streamController
的 stream
对象。事件到来时触发 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
成员被监听时触发一次;onPause
、onResume
、onCancel
分别对应订阅者的 pause
、 resume
、cancel
方法。