一疤剑、什么是狀態(tài)管理?為什么要狀態(tài)管理?
一般開發(fā)一款應(yīng)用程序闷堡,頁面間極有可能需要互相進(jìn)行數(shù)據(jù)傳遞隘膘,而這里的數(shù)據(jù)傳遞也就是指頁面間的狀態(tài)同步(管理)。
頁面內(nèi)部的狀態(tài)是可以用StatefulWidget維護(hù)其狀態(tài)杠览,當(dāng)我們需要使用跨組件的狀態(tài)時(shí)弯菊,StatefulWidget 將不再是一個(gè)好的選擇。在多個(gè) Widget 之間進(jìn)行交流的時(shí)候踱阿,雖然你可以使用事件處理的方式解決(setState管钳、callback、EventBus软舌、Notification)才漆,但是當(dāng)嵌套足夠深的話,很容易就增大代碼耦合度葫隙。狀態(tài)管理就是來幫助我們理清這些關(guān)系的栽烂!
二、事件處理
1.setState
2.Function callback(類似ios Block)
widget:
class StateManageListItem extends StatelessWidget {
final VoidCallback callback;//聲明
const StateManageListItem({this.callback});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: callback,//回調(diào)
child: Container(
),
);
}
}
調(diào)用:
class StateManagementPage extends StatefulWidget {
@override
_StateManagementPageState createState() => _StateManagementPageState();
}
class _StateManagementPageState extends State<StateManagementPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
brightness: Brightness.light,
centerTitle: true,
title: Text(
'狀態(tài)管理',
style: HSLTextStyles.textWhite16,
),
),
body: new ListView.separated(
itemCount: _items.length,
physics: AlwaysScrollableScrollPhysics(),
separatorBuilder:(BuildContext context, int index) {
return Divider(height: 1, color: HSLColors.selago);
},
itemBuilder: (BuildContext context, int index) {
return StateManageListItem(
callback: (){
if(index == 0){
showToast('callBack傳值');
}
},
);
},
),
);
}
}
3.事件總線-EventBus
在APP中恋脚,我們經(jīng)常會(huì)需要一個(gè)廣播機(jī)制腺办,用以跨頁面事件通知,比如一個(gè)需要登錄的APP中糟描,頁面會(huì)關(guān)注用戶登錄或注銷事件怀喉,來進(jìn)行一些狀態(tài)更新。這時(shí)候船响,一個(gè)事件總線便會(huì)非常有用躬拢,事件總線通常實(shí)現(xiàn)了訂閱者模式躲履,訂閱者模式包含發(fā)布者和訂閱者兩種角色,可以通過事件總線來觸發(fā)事件和監(jiān)聽事件
單例模式實(shí)現(xiàn)全局的事件總線
++Dart中實(shí)現(xiàn)單例模式的標(biāo)準(zhǔn)做法就是使用static變量+工廠構(gòu)造函數(shù)的方式聊闯,這樣就可以保證new EventBus()始終返回都是同一個(gè)實(shí)例工猜,讀者應(yīng)該理解并掌握這種方法。++
//訂閱者回調(diào)簽名
typedef void EventCallback(arg);
class EventBus {
//私有構(gòu)造函數(shù)
EventBus._internal();
//保存單例
static EventBus _singleton = new EventBus._internal();
//工廠構(gòu)造函數(shù)
factory EventBus()=> _singleton;
//保存事件訂閱者隊(duì)列菱蔬,key:事件名(id)篷帅,value: 對應(yīng)事件的訂閱者隊(duì)列
var _emap = new Map<Object, List<EventCallback>>();
//添加訂閱者
void on(eventName, EventCallback f) {
if (eventName == null || f == null) return;
_emap[eventName] ??= new List<EventCallback>();
_emap[eventName].add(f);
}
//移除訂閱者
void off(eventName, [EventCallback f]) {
var list = _emap[eventName];
if (eventName == null || list == null) return;
if (f == null) {
_emap[eventName] = null;
} else {
list.remove(f);
}
}
//觸發(fā)事件,事件觸發(fā)后該事件所有訂閱者會(huì)被調(diào)用
void emit(eventName, [arg]) {
var list = _emap[eventName];
if (list == null) return;
int len = list.length - 1;
//反向遍歷拴泌,防止訂閱者在回調(diào)中移除自身帶來的下標(biāo)錯(cuò)位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
}
//定義一個(gè)top-level(全局)變量魏身,頁面引入該文件后可以直接使用bus
var bus = new EventBus();
使用:
//頁面A中
...
//監(jiān)聽登錄事件
bus.on("login", (arg) {
// do something
});
//登錄頁B中
...
//登錄成功后觸發(fā)登錄事件,頁面A中訂閱者會(huì)被調(diào)用
bus.emit("login", userInfo);
4.通知-Notification
通知(Notification)是Flutter中一個(gè)重要的機(jī)制蚪腐,在widget樹中箭昵,每一個(gè)節(jié)點(diǎn)都可以分發(fā)通知,通知會(huì)沿著當(dāng)前節(jié)點(diǎn)向上傳遞回季,所有父節(jié)點(diǎn)都可以通過NotificationListener來監(jiān)聽通知家制。Flutter中將這種由子向父的傳遞通知的機(jī)制稱為通知冒泡(Notification Bubbling)。通知冒泡和用戶觸摸事件冒泡是相似的茧跋,但有一點(diǎn)不同:通知冒泡可以中止慰丛,但用戶觸摸事件不行。
1.定義一個(gè)通知類瘾杭,要繼承自Notification類诅病;
class MyNotification extends Notification {
MyNotification(this.msg);
final String msg;
}
2.分發(fā)通知。
class NotificationRouteState extends State<NotificationRoute> {
String _msg="";
@override
Widget build(BuildContext context) {
//監(jiān)聽通知
return NotificationListener<MyNotification>(
onNotification: (notification) {
setState(() {
_msg+=notification.msg+" ";
});
return true;
},
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Builder(
builder: (context) {
return RaisedButton(
//按鈕點(diǎn)擊時(shí)分發(fā)通知
onPressed: () => MyNotification("Hi").dispatch(context),
child: Text("Send Notification"),
);
},
),
Text(_msg)
],
),
),
);
}
}
5.InheritedWidget
InheritedWidget提供了一種數(shù)據(jù)在widget樹中從上到下傳遞粥烁、共享的方式贤笆,比如我們在應(yīng)用的根widget中通過InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget中來獲取該共享的數(shù)據(jù)
import 'package:flutter/material.dart';
class InheritedWidgetPage extends StatefulWidget {
@override
_InheritedWidgetPageState createState() => _InheritedWidgetPageState();
}
class _InheritedWidgetPageState extends State<InheritedWidgetPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('數(shù)據(jù)共享InheritedWidget'),
),
body: Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依賴ShareDataWidget
),
RaisedButton(
child: Text("Increment"),
//每點(diǎn)擊一次讨阻,將count自增芥永,然后重新build,ShareDataWidget的data將被更新
onPressed: () => setState(() => ++count),
)
],
),
),
),
);
}
}
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Widget child
}) :super(child: child);
final int data; //需要在子樹中共享的數(shù)據(jù),保存點(diǎn)擊次數(shù)
//定義一個(gè)便捷方法钝吮,方便子樹中的widget獲取共享數(shù)據(jù)
static ShareDataWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
//該回調(diào)決定當(dāng)data發(fā)生變化時(shí)埋涧,是否通知子樹中依賴data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,則子樹中依賴(build函數(shù)中有調(diào)用)本widget
//的子widget的`state.didChangeDependencies`會(huì)被調(diào)用
return old.data != data;
}
}
class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => new __TestWidgetState();
}
class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享數(shù)據(jù)
return Text(ShareDataWidget
.of(context)
.data
.toString());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時(shí)會(huì)被調(diào)用奇瘦。
//如果build中沒有依賴InheritedWidget棘催,則此回調(diào)不會(huì)被調(diào)用。
print("Dependencies change");
}
}
三耳标、狀態(tài)管理常用插件
1.Provider
我們往往需要管理不同頁面之間的數(shù)據(jù)共享醇坝,在頁面功能復(fù)雜,狀態(tài)達(dá)到幾十個(gè)上百個(gè)的時(shí)候次坡,我們會(huì)難以清楚的維護(hù)我們的數(shù)據(jù)狀態(tài),這時(shí)候就需要跨組件管理呼猪。
1画畅、創(chuàng)建Model
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {//1
int _count;
Counter(this._count);
void add() {
_count++;
notifyListeners();//2
}
get count => _count;//3
}
2.使用ChangeNotifierProvider
這里用的全局的
main() {
runApp(ChangeNotifierProvider<Counter>.value(//1
notifier: Counter(1),//2
child: MyApp(),
));
}
3.使用Provider獲取Counter的值
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Home"),
actions: <Widget>[
FlatButton(
child: Text("下一頁"),
onPressed: () =>
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondPage();
})),
),
],
),
body: Center(
child: Text("${Provider.of<Counter>(context).count}"),//1
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context).add();//2
},
child: Icon(Icons.add),
),
);
}
}
//第二個(gè)頁面也獲取到Count
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text(Provider.of<String>(context)),
),
body: Center(
child: Text("${Provider.of<Counter>(context).count}"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context).add();
},
child: Icon(Icons.add),
),
);
}
}
放一個(gè)之前自己學(xué)習(xí)時(shí)寫的demo,希望可以幫助新入門的老鐵們宋距,有好的建議可以提一下轴踱,我們一起進(jìn)步,奧利給O绺铩?苌摊腋!
https://github.com/Baffin-HSL/Flutter_UI