ValueListenableBuilder
InheritedWidget提供一种在Widget树中从上到下共享数据的方式,但是很多场景的数据流向并不是从上到下,也有横向或从下到上等方式。为了解决这个问题,Flutter提供了一个ValueListenableBuilder组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行builder:
const ValueListenableBuilder({
Key? key,
required this.valueListenable, // 数据源,类型为ValueListenable<T>
required this.builder, // builder
this.child,
}
- valueListenable:类型为ValueListenable,表示一个可监听的数据源。
- builder:数据源发生变化通知时,会重新调用builder重新build子组件树。
- child:builder中每次都会重新构建整个子组件树,如果子组件树中有一些不变的部分,可以传递给child,child会作为builder的第三个参数传递给builder,通过这种方式就可以实现组件缓存,原理和AnimatedBuilder第三个child相同。
ValueListenableBuilder和数据流向无关,只要整个数据源发生变化,它就会重新构建子组件树,可以实现任意流向的数据共享。
实例:
class ValueListenableRoute extends StatefulWidget {
const ValueListenableRoute({Key? key}) : super(key: key);
@override
State<ValueListenableRoute> createState() => _ValueListenableState();
}
class _ValueListenableState extends State<ValueListenableRoute> {
// 定义一个ValueNotifier,当数字变化时会通知 ValueListenableBuilder
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
static const double textScaleFactor = 1.5;
@override
Widget build(BuildContext context) {
// 添加 + 按钮不会触发整个 ValueListenableRoute 组件的 build
print('build');
return Scaffold(
appBar: AppBar(title: Text('ValueListenableBuilder 测试')),
body: Center(
child: ValueListenableBuilder<int>(
builder: (BuildContext context, int value, Widget? child) {
// builder 方法只会在 _counter 变化时被调用
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
child!,
Text('$value 次',textScaleFactor: textScaleFactor),
],
);
},
valueListenable: _counter,
// 当子组件不依赖变化的数据,且子组件收件开销比较大时,指定 child 属性来缓存子组件非常有用
child: const Text('点击了 ', textScaleFactor: textScaleFactor),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
// 点击后值 +1,触发 ValueListenableBuilder 重新构建
onPressed: () => _counter.value += 1,
),
);
}
}
建议:尽可能让ValueListenableBuilder只构建依赖数据源的widget,这样的话可以缩小重新构建的范围,也就是ValueListenableBuilder的拆分粒度应该尽可能细。
- 和数据流向无关,可以实现任意流向的数据共享。
- 实践中,ValueListenableBuilder的粒度尽可能细,提供性能。
异步更新UI-FutureBuilder
FutureBuilder会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身:
FutureBuilder({
this.future,
this.initialData,
required this.builder,
})
- future:FutureBuilder依赖的Future,通常是一个异步耗时任务。
- initialData:初始数据,用户设置默认数据。
- builder:Widget构建器,在Future执行的不同阶段被多次调用,构建器签名如下:
Function (BuildContext context, AsyncSnapshot snapshot)
snapshot会包含当前异步任务的状态信息及结果信息,比如通过snapshot.connectionState获取异步任务的状态信息、通过snapshot.hasError判断异步任务是否有错误等。 FlutureBuilder的builder函数签名和StreamBuilder的builder相同。
实例:
Future<String> mockNetworkData() async {
return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");
}
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<String>(
future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// 请求已结束
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
// 请求失败,显示错误
return Text("Error: ${snapshot.error}");
} else {
// 请求成功,显示数据
return Text("Contents: ${snapshot.data}");
}
} else {
// 请求未结束,显示loading
return CircularProgressIndicator();
}
},
),
);
}
注意:实例代码中,每次组件重新build都会重新发起请求,每次future都是新构建的,实践中通常会有一些缓存策略,常见的处理方式是在future成功后将future缓存,这样下次build时,就不会再重新发起异步任务。
builder中根据当前异步任务状态ConnectionState来返回不同的widget。ConnectionState是一个枚举类:
enum ConnectionState {
/// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
none,
/// 异步任务处于等待状态
waiting,
/// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态。
///仅在StreamBuilder中才有
active,
/// 异步任务已经终止.
done,
}
StreamBuilder
Stream是用于接收异步事件数据,和Future不同的是,它可以接收多个异步操作的结果,常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件:
StreamBuilder({
this.initialData,
Stream<T> stream,
required this.builder,
})
可以看到和FutureBuilder的构造函数只有一点不同:前者需要Future后者需要stream。
实例:
Stream<int> counter() {
return Stream.periodic(Duration(seconds: 1), (i) {
return i;
});
}
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counter(), //
//initialData: ,// a Stream<int> or null
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('没有Stream');
case ConnectionState.waiting:
return Text('等待数据...');
case ConnectionState.active:
return Text('active: ${snapshot.data}');
case ConnectionState.done:
return Text('Stream 已关闭');
}
return null; // unreachable
},
);
}