Flutter_Bloc
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呢?其实是流的订阅。
总结
imageUI组件将事件分派给Bloc,Bloc内部做一个转换,将状态发送给UI,UI再去更新局部或者全部自己。