前言
對于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,可以根據自己項目的復雜程度進行選擇。