StreamBuilder

简介

  StreamBuilder是一个根据Stream绘制的Widget,该Widget会根据流中的每个元素去绘制元素对应的Widget。二者关系如下:

StreamBuilder

  当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 stream,
    @required this.builder,
  }) : assert(builder != null),
       super(key: key, stream: stream)

  T initialData:用于绘制StreamBuilder第一帧的数据,如果不传则StreamBuilder的第一帧数据是null,我们可以在上面异步事件线图中看到,第一帧的异步数据是:AsyncSnapshot.withData(ConnectionState.waiting, null)
  Stream stream:用于监听的流
  AsyncWidgetBuilder builder:结合流元素与Widget的方法参数。builder的入参是用于构建Widget的上下文BuildContext和异步数据AsyncSnapshot。返回值就是使用者想要根据流元素显示的Widget。

使用案例

  StreamBuilder的典型使用场景是:数据驱动UI和兄弟widget之间通信。
  StreamBuilder的使用伪代码如下:

StreamBuilder(
  stream: 数据流, 
  builder: (BuildContext context, AsyncSnapshot 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 {
  StreamController 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: [
            Text(
              'You have pushed the button this many times:',
            ),
            //第二步:监听data的变化
            StreamBuilder(
              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 {
  StreamController 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(
        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树的渲染降到合适的层级,可以做自己的封装等等。

你可能感兴趣的:(StreamBuilder)