前言
对于UI而言,最基础的就是展示数据,刷新数据,Flutter提供了一套状态管理机制来做这些事情
案例
通过一个案例来解释Flutter的状态管理机制是如何运作的,假设我们需要实现一个SegmentHeader,通过点击不同的按钮改变页面的背景色。
第一步创建一个继承自StatefulWidget
的SegmentHeader
类,同时创建_SegmentHeaderState
class SegmentHeader extends StatefulWidget {
const SegmentHeader({super.key});
@override
State<StatefulWidget> createState() => _SegmentHeaderState();
}
class _SegmentHeaderState extends State<SegmentHeader> {
@override
Widget build(BuildContext context) {
...
}
}
Flutter中Widget会随着每次更新重新创建,但是State会保留,所以状态字段都会放在State类中,目前需要保存的状态就是用户点击了那个Segment
class _SegmentHeaderState extends State<SegmentHeader> {
var selectedIndex = -1;
@override
Widget build(BuildContext context) {
...
在_SegmentHeaderState
中定义一个selectedIndex
表示用户选择了哪个SegmentTab,接下来增加几个tab,并响应点击事件
class _SegmentHeaderState extends State<SegmentHeader> {
var selectedIndex = -1;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: buildItem(0, "Red", Colors.red)),
Expanded(child: buildItem(1, "Blue", Colors.blue)),
Expanded(child: buildItem(2, "Cyan", Colors.cyan)),
Expanded(child: buildItem(3, "Purple", Colors.purple)),
],
);
}
Widget buildItem(int index, String title, Color color) {
return GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
},
child: Container(
margin: const EdgeInsets.all(5),
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: selectedIndex == index ? Colors.amber : Colors.white),
child: Text(
title,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
),
);
}
}
在tab的点击事件中,通过setState修改selectedIndex
的值,并且通过判断selectedIndex
的值是否与当前tab的index相等来高亮选中的tab。最后再增加一个回调,告知上层选中的tab改变了
class SegmentHeader extends StatefulWidget {
final Function(int, Color)? tabSelectedChanged;
const SegmentHeader({super.key, this.tabSelectedChanged});
@override
State<StatefulWidget> createState() => _SegmentHeaderState();
}
class _SegmentHeaderState extends State<SegmentHeader> {
var selectedIndex = -1;
...
Widget buildItem(int index, String title, Color color) {
return GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
if (widget.tabSelectedChanged != null) {
widget.tabSelectedChanged!.call(index, color);
}
},
child: ...
);
}
}
将写好的SegmentHeader
整合到页面中,并控制页面的颜色
class LearnStatefulWidget extends StatefulWidget {
const LearnStatefulWidget({super.key});
@override
State<StatefulWidget> createState() => _LearnStatefulWidgetState();
}
class _LearnStatefulWidgetState extends State<LearnStatefulWidget> {
Color bgColor = Colors.white;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("基本State管理"),
),
body: Column(
children: [
SegmentHeader(
tabSelectedChanged: (idx, color) {
setState(() {
bgColor = color;
});
},
),
Expanded(
child: Container(
color: bgColor,
))
],
),
);
}
}
通过SegmentHeader
的回调控制bgColor
,从而达到改变颜色的目的
Provider库
在上面的案例中,我们使用系统的setState进行状态管理,但是有个问题,子Widget向父Widget的数据传递只能依靠回调,这样的话层级多了就很容易陷入回调地狱,为了更好的管理状态,我们可以使用三方状态管理库,比如Flutter官方推荐的Provider,接下来我们通过Provider来改造上面的案例。首先添加依赖
dependencies:
provider: ^6.0.0
接下来定义一个数据模型来存储状态,目前这个页面需要存储的就是当前选择的索引和背景颜色
class SegmentPageModel extends ChangeNotifier {
int selectedIndex = -1;
Color bgColor = Colors.white;
void setSelectedIndex(int val) {
selectedIndex = val;
notifyListeners();
}
void setBgColor(Color val) {
bgColor = val;
notifyListeners();
}
}
这个模型继承了ChangeNotifier
,当数据改变时,调用notifyListeners
来通知外部,接下来重写SegmentHeader
,由于使用了Provider,我们可以将SegmentHeader
调整成为StatelessWidget
class SegmentHeader extends StatelessWidget {
const SegmentHeader({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: buildItem(0, "Red", Colors.red)),
Expanded(child: buildItem(1, "Blue", Colors.blue)),
Expanded(child: buildItem(2, "Cyan", Colors.cyan)),
Expanded(child: buildItem(3, "Purple", Colors.purple)),
],
);
}
Widget buildItem(int index, String title, Color color) {
return Consumer<SegmentPageModel>(builder: (context, value, child) {
return GestureDetector(
onTap: () {
value.setSelectedIndex(index);
value.setBgColor(color);
},
child: Container(
margin: const EdgeInsets.all(5),
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color:
value.selectedIndex == index ? Colors.amber : Colors.white),
child: Text(
title,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
),
);
});
}
}
这里一个重大改变就是需要动态改变的Widget被Consumer
包裹起来,顾名思义,Consumer
就是消费者的意思,当模型发送改变通知,Consumer
就会重新build。在onTap回调里,我们调用setSelectedIndex
和setBgColor
触发模型的数据更新。现在有了消费者,模型实例还缺少生产的地方,需要通过ChangeNotifierProvider
Widget来生产模型实例
class LearnProvider extends StatelessWidget {
const LearnProvider({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Provider管理"),
),
body: ChangeNotifierProvider(
create: (context) => SegmentPageModel(),
child: Column(
children: [
const SegmentHeader(),
Expanded(
child: Consumer<SegmentPageModel>(
builder: (context, value, child) => Container(
color: value.bgColor,
),
))
],
),
));
}
}
在ChangeNotifierProvider
的create中,返回模型实例即可,ChangeNotifierProvider
的Consumer
子Widget都会接收到同一个模型实例。
总结
本篇博客介绍了基础的状态管理方式以及三方状态管理库Provider,当然还有很多其他状态管理库,比如redux,Bloc,可以根据自己项目的复杂程度进行选择。