前言:Flutter 的状态管理插件有很多,比如 Provider,GetX 还有本篇要讲述的 Bloc 。Bloc 目前最新的版本是 flutter_bloc: ^8.0.1
。
BLoC 依赖 Stream和 StreamController实现,组件通过Sinks发送更新状态的事件,然后再通过 Streams通知其他组件更新。事件处理和通知刷新的业务逻辑都是由 BLoC 完成,从而实现业务逻辑与 UI 层的分离,并且逻辑部分可以做到复用。
之前我们更新数据通常是通过 setState
的方式实现的,这种会刷新整个页面,而使用 Bloc 只会刷新想要更新的UI部分。下面会通过几个例子来说明下。
一、使用 Bloc 来实现计数器且把数据传递给跳转的页面
计算器要实现加减一的功能,所以先定义2个 Event,且都继承 CounterEvent ,如下:
// 定义 event 的基类
abstract class CounterEvent {}
// 加1的 event
class IncrementEvent extends CounterEvent {}
// 减1的event
class DecrementEvent extends CounterEvent {}
然后定义一个 CounterBloc 如下:
/// 表示通过 Bloc 发送的事件只能是 CounterEvent, 返回值是 int
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc(int initialState) : super(initialState) {
/// 减1的事件就是把当前的 state - 1
on<DecrementEvent>((event, emit) {
/// 把对应的状态发送出去,在页面中就可以通过 BlocBuilder 来观察数据的改变
emit(state - 1);
});
/// 加1的事件就是把当前的 state + 1
on<IncrementEvent>((event, emit) {
emit(state + 1);
});
}
}
页面具体的代码如下:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
/// 要使用 BlocProvider 来提供 Bloc
home: BlocProvider<CounterBloc>(
create: (BuildContext context) {
return CounterBloc(0);
},
child: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
print(
"---------------------- CounterPage build ----------------------- ${counterBloc.hashCode}");
return Scaffold(
appBar: AppBar(
title: Text('test bloc '),
),
body: Container(
width: double.infinity,
child: Column(
children: [
/// 当增加或者减少计数时,只会局部更新 BlocBuilder ,不会整个刷新 build
BlocBuilder<CounterBloc, int>(
builder: (BuildContext context, int count) {
print(
"---------------------- BlocBuilder build -----------------------");
return Text(
'当前计数: $count',
style: TextStyle(fontSize: 24),
);
},
buildWhen: (previous, next) {
/// 这样写只有 increment 才有用,用来控制触发刷新的逻辑
return previous < next;
},
),
SizedBox(
height: 12,
),
ElevatedButton(
onPressed: () {
counterBloc.add(IncrementEvent());
},
child: Text(
'increment',
),
),
ElevatedButton(
onPressed: () {
counterBloc.add(DecrementEvent());
},
child: Text(
'decrement',
),
),
ElevatedButton(
onPressed: () {
/// 需要 BlocProvider 的 context , 且 BlocProvider 的 create 中返回当前的 counterBloc
/// 如果你在 create 中 重新 new 一个 CounterBloc ,那么在 page2 中增加计数,不会刷新本页面的计数
Navigator.push(context, MaterialPageRoute(builder: (context) {
return BlocProvider<CounterBloc>(
create: (BuildContext context) {
return counterBloc;
},
child: BlocPage2(),
);
}));
},
child: Text(
'jump page 2',
),
),
],
),
),
);
}
}
其中的 buildWhen 是过滤触发条件的,代码中有注释了。BlocPage2 的代码如下:
class BlocPage2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
print('xxxxxxxxxxxxxxx ${counterBloc.hashCode} ');
return Scaffold(
appBar: AppBar(
title: Text('BlocPage2'),
),
body: Container(
width: double.infinity,
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (BuildContext context, int count) {
return Text(
'page2 当前计数: $count',
style: TextStyle(fontSize: 24),
);
}),
ElevatedButton(
onPressed: () {
counterBloc.add(IncrementEvent());
},
child: Text('add')),
],
),
),
);
}
}
BlocPage2 中改变了计数,返回到 CounterPage 页面时计数会同步更新。
结语:本篇这里就结束了,下篇会通过一个真正的例子来说明 Bloc 是怎么做到 UI 和业务逻辑分离的,会有真正的网络请求和页面的刷新。