0.在Windows上搭建Flutter開發(fā)環(huán)境
1.從零開始學Flutter--Widget
[2.從零開始學Flutter--State 狀態(tài)管理]
State狀態(tài) 定義
Flutter是基于聲明式框架,我們以前開發(fā)Android或者IOS都是基于命令式框架祖很,如果是以前有過React/Vue的開發(fā)經(jīng)驗呢撞,了解Flutter狀態(tài)管理會更加簡單炸宵,如果是從原生的Android和IOS轉(zhuǎn)到Flutter,理解起來可能會有難度啄寡,不過不急拾弃,我寫這篇文章的時候也半懂不懂的讯檐,所以如果理解有出入可以留言交流羡疗,后面我有新的理解也會及時更新。
直接說人話别洪,在Flutter中我們怎么去理解State叨恨,我們在原生移動開發(fā)的時候,如果需要改變一個控件的狀態(tài)挖垛,直接調(diào)用那個控件暴露的函數(shù)就可以直接改變控件的狀態(tài)痒钝,就算是自定義控件的時候,SDK也提供了很多基礎的API讓我們操作這個View的各種狀態(tài)痢毒,但是在Flutter中送矩,好像并沒有一個控件或者View或者UI元素直接讓我們?nèi)ゲ僮鳎懊嫖覀冋f過闸准,Widget只是一個UI元素的描述,而且并不是所有的Widget都能改變狀態(tài)梢灭,所以他的子類分為StatelessWidget 和 StatefulWidget夷家,其中 StatelessWidget 的狀態(tài)是不可改變的,StatefulWidget狀態(tài)可以變化敏释,需要通過實現(xiàn)createState函數(shù)库快,接受一個State去改變狀態(tài)。
我們先不探究原理钥顽,先大致了解在Fluter怎么去管理State
Widget管理自己的狀態(tài)义屏。
在上一章介紹Widget里面,我們有寫一個簡單的例子
class PageOne extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new TextSatte();
}
}
class TextState extends State {
var _count = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: Text("舉個例子"),
),
body: new Stack(
children: <Widget>[
new Align(
child: Text("我繼承StatefulWidget$_count"),
),
new Align(
alignment: new FractionalOffset(0.5, 1),
child: new MaterialButton(
onPressed: () {
setState(() {
_count++;
});
},
child: Text("改變狀態(tài)"),
),
)
],
),
);
}
}
可以看到蜂大,如果 PageOne 需要自己管理狀態(tài)闽铐,那繼承 StatefulWidget,然后會需要實現(xiàn)createState函數(shù)奶浦,我們直接通過State 的setState函數(shù)就可以更改我們PageOne的狀態(tài)兄墅,比較簡單的使用
Widget管理子Widget狀態(tài)
既然是父管理子類,那父類Wiget肯定是可以改變狀態(tài)的
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
建一個ParentWidget 繼承 StatefulWidget澳叉,實現(xiàn)createState方法隙咸,返回一個State
_ParentWidgetState類繼承State沐悦,提供一個_handleTapboxChanged函數(shù),參數(shù)bool類型五督,通過setState用來改變_active的值
實現(xiàn)build方法藏否,在build里面,Container的child的對象是TapboxB充包,構(gòu)造函數(shù)傳遞兩個參數(shù)副签,_active和_handleTapboxChanged,下面來看看TapboxB里面的實現(xiàn)
TapboxB類
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
TapboxB 繼承 StatelessWidget误证,構(gòu)造函數(shù)是兩個參數(shù)active和onChanged继薛,然后提供一個_handleTap函數(shù),這個函數(shù)就是直接調(diào)用傳過來的onChanged(onChanged就是一個方法)愈捅,然后參數(shù)傳!active遏考,有代碼經(jīng)驗的應該都知道是什么意思
然后在build方法里面,設置Text元素的點擊事件為_handleTap蓝谨,這樣就可以達到改變自身狀態(tài)(子widget)的效果了
總體流程就是父類提供一個方法改變狀態(tài)灌具,然后把這個方法通過子類構(gòu)造函數(shù)傳遞到子類,然后子類需要的時候直接調(diào)用這個函數(shù)就可以到達修改狀態(tài)的效果
混合狀態(tài)管理
混合狀態(tài)管理就是子widget和父widget共同去處理state譬巫,結(jié)合上面兩種方法就可以了
ParentWidget不需要改變咖楣,TapboxB 類繼承 StatefulWidget,在事件里面調(diào)用setState函數(shù)就可以了
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
bool _highlight = false;
@override
_TapboxC createState() => _TapboxC();
}
class _TapboxC extends State<TapboxC> {
void _handleTap() {
widget.onChanged(!widget.active);
}
void _handleTapDown(TapDownDetails details) {
setState(() {
widget._highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
widget._highlight = false;
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new GestureDetector(
onTapDown: _handleTapDown, // 處理按下事件
onTapUp: _handleTapUp, // 處理抬起事件
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
widget.active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: widget._highlight
? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
新建一個TapboxC類芦昔,繼承StatefulWidget诱贿,還是跟上面一樣,通過構(gòu)造函數(shù)咕缎,接受兩個參數(shù)珠十,用來調(diào)用父類函數(shù),改變狀態(tài)
自定義一個state凭豪,加入一個變量_highlight焙蹭,提供兩個方法_handleTapDown和_handleTapUp(方法里面通過setState函數(shù)來改變變量狀態(tài)),用來相應按下和抬起事件的處理
然后設置onTapDown和onTapUp事件嫂伞,border屬性靠_highlight變量改變孔厉,這樣就達到了混合處理狀態(tài)的模式,是不是很簡單
全局State的管理
//訂閱者回調(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;
//保存事件訂閱者隊列帖努,key:事件名(id)撰豺,value: 對應事件的訂閱者隊列
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ā)后該事件所有訂閱者會被調(diào)用
void emit(eventName, [arg]) {
var list = _emap[eventName];
if (list == null) return;
int len = list.length - 1;
//反向遍歷拼余,防止訂閱者在回調(diào)中移除自身帶來的下標錯位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
}
//定義一個top-level(全局)變量郑趁,頁面引入該文件后可以直接使用bus
var bus = new EventBus();
看代碼,簡單的邏輯應該沒問題姿搜,開始設計單例模式寡润,然后定義一個_emap變量來保存任務隊列捆憎,提供 on 和 off 函數(shù)來添加和移除任務隊列,兩個函數(shù)都要傳一個EventCallback的回調(diào)梭纹,進行事件的監(jiān)聽回調(diào)躲惰,然后提供emit函數(shù)來觸發(fā)事件,觸發(fā)函數(shù)被調(diào)用之后变抽,實際就是遍歷循環(huán)整個_emap任務列表础拨。
其實這就是一個簡單的監(jiān)聽者模式,在需要監(jiān)聽事件的地方調(diào)用on來注冊監(jiān)聽绍载,在需要改變某個狀態(tài)的地方诡宗,調(diào)用emit函數(shù)來觸發(fā)事件,這樣在監(jiān)聽的地方就可以收到回調(diào)击儡,進行處理塔沃。
//頁面A中
//監(jiān)聽登錄事件
bus.on("login", (arg) {
// do something
});
//登錄頁B中
//登錄成功后觸發(fā)登錄事件,頁面A中訂閱者會被調(diào)用
bus.emit("login", userInfo);
總結(jié)
Flutter的狀態(tài)管理和原生移動開發(fā)的不同阳谍,基本就是如果需要改變一個UI元素的狀態(tài)蛀柴,就需要在一個State里面進行處理,不能直接調(diào)用某個"控件"的函數(shù)進行改變矫夯,其中有的元素不需要進行狀態(tài)的改變鸽疾,我們就繼承StatelessWidget,如果需要改變就繼承StatefulWidget训貌,在StatefulWidget中自己控制自己的狀態(tài)可以調(diào)用State的setState函數(shù)就可以直接改變了制肮,父類要改變子類的狀態(tài),只需要把父類改變狀態(tài)的函數(shù)傳遞給子類递沪,讓子類去調(diào)用函數(shù)就可以了豺鼻,全局的狀態(tài)管理我們寫了一個監(jiān)聽者模式的事件總線,F(xiàn)lutter中有很多第三方的框架可以實現(xiàn)全局狀態(tài)的管理区拳,后面用到再學習拘领,如果Flutter原生的功能能滿足需求意乓,也沒必要過度的引入第三方的庫樱调,增加代碼的復雜性。