简介
StreamBuilder是一个根据Stream绘制的Widget,该Widget会根据流中的每个元素去绘制元素对应的Widget。二者关系如下:
当Stream收到A元素时,StreamBuilder就会绘制并显示WidgetA,当Stream收到B元素时,StreamBuilder就会绘制并显示WidgetB,以此类推。
结合Stream和Widget就是:AsyncSnapshot,AsyncSnapshot是表示异步计算最新结果的接口,比如计算无结果、计算出错、计算的状态。我们使用者就是从StreamBuilder的builder函数参数中,取出AsyncSnapshot数据,并返回该AsyncSnapshot对应的Widget。上图的AsyncSnapshot顺序就是:
异步事件线
使用参数
const StreamBuilder({
Key key,
this.initialData,
Stream<T> stream,
@required this.builder,
}) : assert(builder != null),
super(key: key, stream: stream)
T initialData:用于绘制StreamBuilder第一帧的数据,如果不传则StreamBuilder的第一帧数据是null,我们可以在上面异步事件线图中看到,第一帧的异步数据是:AsyncSnapshot.withData(ConnectionState.waiting, null)
Stream<T> stream:用于监听的流
AsyncWidgetBuilder<T> builder:结合流元素与Widget的方法参数。builder的入参是用于构建Widget的上下文BuildContext和异步数据AsyncSnapshot。返回值就是使用者想要根据流元素显示的Widget。
使用案例
StreamBuilder的典型使用场景是:数据驱动UI和兄弟widget之间通信。
StreamBuilder的使用伪代码如下:
StreamBuilder<T>(
stream: 数据流,
builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
//计算出错的widget
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
//有数据的widget
if (snapShot.hasData){
return Text('data');
}
//兜底widget
return Container();
},
)
数据驱动UI
比如某个Widget需要监听数据变化,当数据变化时Widget重建。下面我们看计数器的stream实现
import 'dart:async';
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<int> controller;
int data;
@override
void initState() {
super.initState();
data = 0;
//第一步:构造数据数据的控制器,用于往流中添加数据
controller = StreamController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('计数器'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
//第二步:监听data的变化
StreamBuilder<int>(
stream: controller.stream,
//初始显示的值,如果不设置 第一帧将会不显示
initialData: data,
builder: (context, snap) {
//有数据的显示
if (snap.hasData) {
return Text(
'${snap.data}',
style: Theme
.of(context)
.textTheme
.display1,
);
}
return Container();
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//第三部:流中添加元素
controller.add(++data);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
效果如下:
数据驱动UI
上面的流程是:StreamBuilder监听了数据的变化,icon驱动了数据的变化。
兄弟widget之间通信
我们经常遇见的需求是,页面有个吸底按钮,但是吸底按钮可能与页面的内容以及交互有关。比如网络数据正常的情况才显示吸底按钮,点击某个button后,吸底按钮置灰等。👇
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<Event> controller;
@override
void initState() {
super.initState();
//用于往流中添加数据
controller = StreamController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('兄弟widget通信'),
),
body: FutureBuilder(
//模拟网络请求 延时两秒
future: Future.delayed(Duration(seconds: 2)).then((data) {
return "data";
}),
builder: (context, snap) {
if (snap.hasData) {
//当有了网络数据之后 吸地按钮才去显示
//流中添加用于显示吸底按钮的 事件
controller.add(Event(isGrey: false));
return Center(
child: Text(
'页面内容',
),
);
}
return Container();
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//流中添加元素 用于置灰按钮
//流中添加置灰按钮的 事件
controller.add(Event(isGrey: true));
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
//用于显示吸底按钮
bottomNavigationBar: StreamBuilder<Event>(
stream: controller.stream,
builder: (context, snap) {
if (snap.hasData) {
Event event = snap.data;
return RaisedButton(
color: Colors.green,
onPressed: !event.isGrey ? () {} : null,
child: Text('吸底按钮'),
);
}
return Container();
},
),
);
}
}
///兄弟widget之间通信的 事件模型
class Event {
//是否置灰
bool isGrey;
Event({this.isGrey});
}
事件流程:网络数据下来之后(上面的延时两秒),发送显示吸底按钮的事件。点击icon之后,发送显示置灰的事件。 吸底按钮监听了以上的事件,完成了兄弟widget之间的通信,避免了整个页面的setState。
效果如下:
兄弟之间通信
小结
以上就是StreamBuilder的使用介绍,其内部也是通过流的监听与setState实现的,对外暴漏了我们易于使用的API接口。通过StreamBuilder我们可以做监听,可以将widget树的渲染降到合适的层级,可以做自己的封装等等。
网友评论