08.Flutter功能型组件
08.Flutter功能型组件
异步 UI 更新(FutureBuilder、StreamBuilder)
FutureBuilder
FutureBuilder 会依赖一个 Future,它会根据所依赖的 Future 的状态来动态构建自身。我们看一下 FutureBuilder 构造函数:
1
2
3
4
5
FutureBuilder({
this.future,
this.initialData,
required this.builder,
})
- future:FutureBuilder 依赖的 Future,通常是一个异步耗时任务
- initialData:初始数据,用户设置默认数据
- builder:Widget 构建器;该构建器会在 Future 执行的不同阶段被多次调用,构建器签名如下:
1
Widget Function(BuildContext context, AsyncSnapshot<T> snapshot)
snapshot 会包含当前异步任务的状态信息及结果信息:
- snapshot.connectionState 获取异步任务的状态信息
1
2
3
4
5
6
7
8
9
10
11
12
13
enum ConnectionState {
/// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
none,
/// 异步任务处于等待状态
waiting,
/// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态。
active, // ConnectionState.active只在StreamBuilder中才会出现
/// 异步任务已经终止.
done,
}
- snapshot.hasError 判断异步任务是否有错误
示例:我们实现一个路由,当该路由打开时我们从网上获取数据,获取数据时弹一个加载框;获取结束时,如果成功则显示获取到的数据,如果失败则显示错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Widget FutureBuilderWidget() {
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 const CircularProgressIndicator();
}
},
),
);
}
Future<String> mockNetworkData() async {
return Future.delayed(const Duration(seconds: 2), () => "我是从互联网上获取的数据");
}
注意:示例的代码中,每次组件重新 build 都会重新发起请求,因为每次的 future 都是新的,实践中我们通常会有一些缓存策略,常见的处理方式是在 future 成功后将 future 缓存,这样下次 build 时,就不会再重新发起异步任务。
StreamBuilder
在 Dart 中 Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder 正是用于配合 Stream 来展示流上事件(数据)变化的 UI 组件。下面看一下 StreamBuilder 的默认构造函数:
1
2
3
4
5
StreamBuilder({
this.initialData,
Stream<T> stream,
required this.builder,
})
示例:
创建一个计时器的示例:每隔 1 秒,计数加 1。这里,我们使用 Stream 来实现每隔一秒生成一个数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Stream<int> counter() {
return Stream.periodic(Duration(seconds: 1), (i) {
return i;
});
}
Widget StreamBuilderWidget() {
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 const Text('没有Stream');
case ConnectionState.waiting:
return const Text('等待数据...');
case ConnectionState.active:
return Text('active: ${snapshot.data}');
case ConnectionState.done:
return const Text('Stream 已关闭');
}
},
);
}
WillPopScope 导航返回拦截
1
2
3
4
5
const WillPopScope({
// ...
required WillPopCallback onWillPop,
required Widget child
})
- onWillPop 是一个回调函数,当用户点击返回按钮时被调用(包括导航返回按钮及 Android 物理返回按钮)。该回调需要返回一个 Future 对象,如果返回的 Future 最终值为 false 时,则当前路由不出栈 (不会返回);最终值为 true 时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。
示例:为了防止用户误触返回键退出,我们拦截返回事件。当用户在 1 秒内点击两次返回按钮时,则退出;如果间隔超过 1 秒则不退出,并重新记时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class WillPopScopeDemo extends StatelessWidget {
const WillPopScopeDemo({super.key});
@override
Widget build(BuildContext context) {
return const WillPopScopeTestRoute();
}
}
class WillPopScopeTestRoute extends StatefulWidget {
const WillPopScopeTestRoute({super.key});
@override
WillPopScopeTestRouteState createState() {
return WillPopScopeTestRouteState();
}
}
class WillPopScopeTestRouteState extends State<WillPopScopeTestRoute> {
DateTime? _lastPressedAt; // 上次点击时间
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// 1. 返回true,表示可以返回
// 2. 返回false,表示不可以返回
// 3. 返回Future.value(false),表示不可以返回
// 4. 返回Future.value(true),表示可以返回
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) >
const Duration(seconds: 1)) {
// 两次点击间隔超过1秒则重新计时
print('两次点击间隔超过1秒则重新计时');
_lastPressedAt = DateTime.now();
return false;
}
return true;
},
child: MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: Scaffold(
appBar: AppBar(
title: const Text("WillPopScope Demo"),
),
body: Container(
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.transparent,
),
child: const Text("1秒内连续按两次返回键退出"),
),
),
),
);
}
}
本文由作者按照 CC BY 4.0 进行授权