美文网首页
StreamBuilder

StreamBuilder

作者: shz_Minato | 来源:发表于2020-07-05 11:52 被阅读0次

    简介

      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<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树的渲染降到合适的层级,可以做自己的封装等等。

    相关文章

      网友评论

          本文标题:StreamBuilder

          本文链接:https://www.haomeiwen.com/subject/zpamqktx.html