1 概述
Flutter 这几年得到迅速的发展,自支持桌面系统开发以来,已不再是纯粹的移动开发框架。然而,当我们使用 Flutter 构建应用时, 有大量的状态管理库或包可供选择,这势必会成为初学者的噩梦。
本文就从简单讲起,着重介绍 ValueNotifiers
和 InheritedWidgets
是如何进行状态管理的。
2 从一个例子开始
我们将实现一个水果列表的收藏夹,效果如下:
2.1 创建 Flutter 项目
默认情况下 main.dart
代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: MyWidget()
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wish List'),
),
body: Center(
child: Text('Hello, World!'),
),
);
}
}
ValueNotifier
是 ChangeNotifier
的子类,它管理单个值的状态,并在值更改时通知侦听器。它与 ValueListableBuilder
小部件一起工作,自动监听 ValueListenableBuilder
上的更改。下面是 ValueNotifier
的简单应用。
class MyWidget extends StatelessWidget {
// 创建监听器
final ValueNotifier<int> counter = ValueNotifier<int>(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wish List'),
),
body: Center(
// 必须配合 ValueListenableBuilder 使用
child: ValueListenableBuilder(
valueListenable: counter,
builder: (context, value, _) {
return Text('${counter.value}');
}
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counter.value += 1;
print(counter.value);
}
)
);
}
}
2.2 自定义 ValueNotifier
我们可以通过继承 ValueNotifier
类,来扩展功能。
// 在构造函数里,添加需要监听的值
class FavoriteNotifier extends ValueNotifier<List<String>> {
FavoriteNotifier(List<String> value) : super(value);
}
接下来,我们增加收藏夹的添加和删除方法。
class FavoriteNotifier extends ValueNotifier<List<String>> {
FavoriteNotifier(List<String> value) : super(value);
void toggleFavorite(String item) {
if (!value.contains(item)) {
value.add(item);
} else {
value.remove(item);
}
notifyListeners();
}
void clearFavorites() {
value.clear();
notifyListeners();
}
}
注意:我们在每个方法中都使用了 notifyListener
方法。如果没有它,当值更改或更新时,侦听器将不会得到通知。
那么问题来了,如何将自定义的 FavoriteNotifier
类,传给部件呢?这里就要使用 InheritedWidgets
,不过要首先更新先 UI 代码。
class MyWidget extends StatelessWidget {
final List<String> fruits = ['Mango', 'Pear', 'Cashew',
'Grape', 'Guava', 'Coconut', 'Orange'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorite Fruits'),
actions: [
Icon(Icons.favorite),
FavoriteCount(),
]
),
body: ListView.separated(
itemCount: fruits.length,
separatorBuilder: (context, _) => Divider(),
itemBuilder: (context, index) {
final fruit = fruits[index];
return ListTile(
leading: Icon(Icons.local_florist),
title: Text(fruit),
onTap: () {},
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
}
)
);
}
}
class FavoriteCount extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text("0"),
),
);
}
}
2.4 使用 InheritedWidgets
InheritedWidgets
允许我们接受来自小部件祖先的数据,不必再向下传递。
class FavoriteState extends InheritedWidget{
final FavoriteNotifier favoriteNotifier;
FavoriteState({required this.favoriteNotifier, required Widget child})
: super(child: child);
// 调用子部件
static FavoriteState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType()!;
}
@override
bool updateShouldNotify(FavoriteState oldWidget) {
return oldWidget.favoriteNotifier.value != favoriteNotifier.value;
}
}
接下来,就要使用 FavoriteState
来包装 MaterialsApp
。
class MyApp extends StatelessWidget {
final List<String> favoriteList = [];
@override
Widget build(BuildContext context) {
return FavoriteState(
favoriteNotifier: FavoriteNotifier(favoriteList),
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: MyWidget()
),
);
}
}
最后,再使用 ValueListenableBuilder
包装 FavoriteNotifier
接受数据更新,通知 UI 界面刷新。
class MyWidget extends StatelessWidget {
final List<String> fruits = ['Mango', 'Pear', 'Cashew',
'Grape', 'Guava', 'Coconut', 'Orange'];
@override
Widget build(BuildContext context) {
final favoriteNotifier = FavoriteState.of(context).favoriteNotifier;
return Scaffold(
appBar: AppBar(
title: Text('Favorite Fruits'),
actions: [
Icon(Icons.favorite),
FavoriteCount(),
]
),
body: ListView.separated(
itemCount: fruits.length,
separatorBuilder: (context, _) => Divider(),
itemBuilder: (context, index) {
final fruit = fruits[index];
return ValueListenableBuilder(
valueListenable: favoriteNotifier,
builder: (context, List<String> value, _) {
return ListTile(
leading: Icon(Icons.local_florist, color: value.contains(fruit)
? Colors.orange
: Colors.grey[600]),
title: Text(fruit),
onTap: () {
favoriteNotifier.toggleFavorite(fruit);
},
);
}
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
favoriteNotifier.clearFavorites();
}
),
);
}
}