美文网首页
Flutter_Bloc

Flutter_Bloc

作者: shz_Minato | 来源:发表于2020-01-24 17:45 被阅读0次

    Flutter中的Bloc

    Bloc和Widget是一种强绑定的关系,下面介绍一些核心的概念。下面提到的状态并不是Flutter原生的State,而是Bloc中的 概念。

    常用Widget

    BlocBuilder

    是一个widget,需要一个Bloc参数和builder参数。BlocBuilder会自动响应状态去 build widget。根据状态去build widget,并不是BlocBuilder的特点,像FutureBuilder、StreamBuilder都可以做到这一点。但是BlocBuilder是为了结合 Bloc使用。参数中的builder可能会被调用多次,因为只要有了状态改变就会 触发一次builder调用(响应式)。

    BlocBuilder<BlocA, BlocAState>(
     bloc: blocA,
      builder: (context, state) {
         //根据state 返回widget
      }
    )
    
    

    其中bloc参数不是必填项,如果不传入bloc参数,那么会根据自己的context自动向上寻找 合适 的Bloc。那么是如何向上寻找呢?通过Provider。

    //不带bloc参数
    BlocBuilder<BlocA, BlocAState>(
      builder: (context, state) {
         //根据state 返回widget
      }
    )
    
    

    那么带bloc参数和不带bloc参数的区别是什么呢?
    我们知道 bloc的作用是 将事件流 map为 状态流。如果不带参数 默认是使用 父widget的 bloc,那就是说 子也需要这个状态,这样可以做到 将状态层层下发下来,实现局部的widget刷新。比如将状态 传给了整个页面级别的widget,子widget也可以方便的获取到这个 状态,不需要重新生成整个页面,也不需要通过构造参数层层传递。

    那么什么时候使用带参数?什么时候使用不带bloc参数呢?

    第一:如果想要很好的自己控制自己,那么就使用带参数的。

    第二:如果通过context 向上寻找不到合适的bloc,那么就使用带参数的。

    我们知道Bloc是基于订阅关系的,那么就存在一种情况:每个输入事件---都会产生一个状态事件---都会调用build方法。我们知道flutter的widget是不可变的,大多数情况下都会生成一个新的widget树,这样是十分消耗资源的。有些状态不需要执行build方法,针对这种情况,BlocBuilder提供了一个condition方法参数,他接受两个两个参数:前一个状态、新生成的状态,返回一个bool结果。如果true则执行builder方法,如果false则 不执行。这样就起到了过滤的作用。

    BlocBuilder<BlocA, BlocAState>(
      condition: (previousState, state) {
        //根据返回的bool值去决定是否 指定builder方法
      },
      builder: (context, state) {
      
      }
    )
    
    

    BlocProvider

    上面我们提到了 通过provider向上寻找到合适的Bloc。这个provider就是BlocProvider。BlocProvider也是一个widget,它可以为他的子树提供一个Bloc。子树可以通过BlocProvider.of<T>(context)去获取到Bloc。是不是很像InheritedWidget。

    既然BlocProvider可以提供一个Bloc。那么根据构造Bloc的方式就可以分为两类了:

    一:构造一个新的Bloc。

    BlocProvider(
      create: (BuildContext context) => BlocA(),
      child: ChildA(),
    );
    
    

    二:使用一个已经存在的Bloc。

    BlocProvider.value(
      value: BlocProvider.of<BlocA>(context),
      child: ScreenA(),
    );
    
    

    那么使用这两者有什么区别吗?谁负责创建了Bloc,那么谁就要去关闭Bloc。

    MultiBlocProvider

    MultiBlocProvider同样是一个Widget,它的作用是为了:将多个BlocProvider合并到一个之中。因为这样的可以减少多BlocProviders的嵌套层级,提高代码的可阅读性。如下所示使用与不使用的对比:

    //层层嵌套 ----》串行
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
      child: BlocProvider<BlocB>(
        create: (BuildContext context) => BlocB(),
        child: BlocProvider<BlocC>(
          create: (BuildContext context) => BlocC(),
          child: ChildA(),
        )
      )
    )
    
    //多个合并----》并行
    MultiBlocProvider(
      providers: [
        BlocProvider<BlocA>(
          create: (BuildContext context) => BlocA(),
        ),
        BlocProvider<BlocB>(
          create: (BuildContext context) => BlocB(),
        ),
        BlocProvider<BlocC>(
          create: (BuildContext context) => BlocC(),
        ),
      ],
      child: ChildA(),
    )
    
    

    BlocListener

    BlocListener也是一个Widget,它需要一个BlocWidgetListener构造参数。BlocWidgetListener的用处是响应Bloc的状态变化。

    我们知道BlocBuilder的builder也是响应状态变化,那两者的区别是什么呢?第一:BlocListener的listener函数 对于一种状态只会被调用一次,而BlocBuilder的builder一种状态可能会调用多次。第二:BlocListener的listener函数是没有返回值的,而BlocBuilder的builder需要一个Widget返回值。

    和上述的其他widget相似,BlocListener也有可选的Bloc参数以及condition参数。

    BlocListener<BlocA, BlocAState>(
      condition: (previousState, state) {
        //根据返回的bool值,判断是否调用listener方法
      },
      listener: (context, state) {
      }
      child: Container(),
    )
    
    

    MultiBlocListener

    MultiBlocListener会合并多个BlocListener到一个中,用法同上述的MultiBlocProvider。

    MultiBlocListener(
      listeners: [
        BlocListener<BlocA, BlocAState>(
          listener: (context, state) {},
        ),
        BlocListener<BlocB, BlocBState>(
          listener: (context, state) {},
        ),
        BlocListener<BlocC, BlocCState>(
          listener: (context, state) {},
        ),
      ],
      child: ChildA(),
    )
    
    

    BlocConsumer

    上面我们将了BlocBuilder和BlocListener。BlocBuilder提供了一种根据状态 构建 widget的能力,BlocListener提供了一种 监听状态的能力。 但是,我们即像 构建Ui 又像 执行操作 怎么办呢?要么在BlocBuilder的builder代码中 融合 操作代码,要么在BlocListener中融合 rebuild操作,不管哪种操作,都十分的怪异。
    BlocConsumer就提供了解决这种怪异的能力,它对外暴露了builder 和 listener,两者的方法同上述的builder和listener。除此之外,为了更加精细的控制函数的调用时机,它还提供了可选的listenWhen和buildWhen参数,这两个参数都返回bool值,用法同上述的condition。

    BlocConsumer<BlocA, BlocAState>(
      listenWhen: (previous, current) {
        //根据前一个状态和当前状态 判断 是否调用listener
      },
      listener: (context, state) {
        
      },
      buildWhen: (previous, current) {
        //根据前一个状态和当前状态 判断是否调用builder
      },
      builder: (context, state) {
        
      }
    )
    
    

    RepositoryProvider

    我们在开发中经常需要使用父Widget或祖Widget的某些数据,RepositoryProvider就提供了这样的能力。它的子树们可以很方便的通过RepositoryProvider.of<T>(context)获取到它的某些信息。思路同BlocProvider,只不过BlocProvider对子树们提供了Bloc,RepositoryProvider提供的是任意指定的信息。

    RepositoryProvider(
      //指定了RepositoryA()信息
      builder: (context) => RepositoryA(),
      child: ChildA(),
    );
    
    //子树们使用的方式
    RepositoryProvider.of<RepositoryA>(context)
    
    

    使用

    上面我们介绍了几个常用的widget,下面我们看一下具体的使用,仍以计数器为例。

    事件

    具体的事件就是用户点击的加一、减一。那么就是两种事件。

    enum CounterEvent { increment, decrement }
    
    

    Bloc

    Bloc就是将事件流转为 状态流。

    //范型为:点击的事件、数值状态
    class CounterBloc extends Bloc<CounterEvent, int> {
      @override
      int get initialState => 0;
    
     //状态的转换
      @override
      Stream<int> mapEventToState(CounterEvent event) async* {
        switch (event) {
          case CounterEvent.decrement:
            yield state - 1;
            break;
          case CounterEvent.increment:
            yield state + 1;
            break;
        }
      }
    }
    
    

    页面

    
    class CounterPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //构造bloc
        CounterBloc counterBloc = CounterBloc();
        return BlocProvider(
          create: (BuildContext context) => counterBloc,
          child: Scaffold(
            appBar: AppBar(title: Text('Counter')),
            body: BlocBuilder<CounterBloc, int>(
              //指定builder需要的bloc
              bloc: counterBloc,
              //每次状态变化 都会执行builder
              builder: (context, count) {
                return Center(
                  child: Text(
                    '$count',
                    style: TextStyle(fontSize: 24.0),
                  ),
                );
              },
            ),
            floatingActionButton: Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 5.0),
                  child: FloatingActionButton(
                    child: Icon(Icons.add),
                    onPressed: () {
                     //将事件 添加至 bloc
                     counterBloc.add(CounterEvent.increment);
                    },
                  ),
                ),
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 5.0),
                  child: FloatingActionButton(
                    child: Icon(Icons.remove),
                    onPressed: () {
                     //将事件添加至 bloc
                     counterBloc.add(CounterEvent.decrement);
                    },
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    我们发现Bloc就是 一个中转站,将事件转为状态。那么为什么可以将状态转完更新UI呢?其实是流的订阅。

    总结

    image

    UI组件将事件分派给Bloc,Bloc内部做一个转换,将状态发送给UI,UI再去更新局部或者全部自己。

    相关文章

      网友评论

          本文标题:Flutter_Bloc

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