Flutter_Bloc

2020-01-24  本文已影响0人  shz_Minato

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再去更新局部或者全部自己。

上一篇下一篇

猜你喜欢

热点阅读