響應式的編程框架中都會有一個永恆的主題——「狀態管理」,不管是React/Vue(二者都是支持響應式編程的Web開發框架)仍是Flutter,討論的問題和解決的思想都是一致的。因此,若是你對React/Vue的狀態管理有了解,能夠跳過本節。言歸正傳,咱們想一個問題,Stateful Widget的狀態應該被誰管理?是Widget自己?是父Widget?仍是都會?亦或是另外一個對象?答案是:取決於實際狀況!編程
如下是管理狀態的最多見的方式:redux
如下原則能夠幫助你決定如何決定使用哪一種管理方式?bash
在Widget內部管理狀態封裝性會好一些,而在父Widget中管理會比較靈活。有些時候,若是不肯定到底該由誰來管理狀態,那麼首選由父Widget來管理(由於靈活會顯得更重要一些)。框架
接下來,咱們將經過建立三個簡單示例TapboxA、TapboxB和TapboxC來講明管理狀態的不一樣方式。這些例子的功能是類似的——建立一個盒子,當點擊它時,盒子背景會在綠色與灰色之間切換,狀態_active
肯定顏色:綠色爲true
,灰色爲false
。less
下面的例子將使用GestureDetector
來識別點擊事件,關於該GestureDetector
的詳細內容咱們將在後續進行講解。ide
_TapboxAState
類:函數
_active
:肯定盒子的當前顏色的布爾值;_handleTap()
函數,該函數在點擊該盒子時更新_active
,並調用setState()
更新UI;// TapboxA 管理自身狀態.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key);
@override
_TapboxAState createState() => new _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_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],
),
),
);
}
}
複製代碼
對於父Widget來講,管理狀態並告訴其子Widget什麼時候更新一般是比較好的方式。例如,IconButton是一個圖片按鈕,但它是一個無狀態的Widget,由於咱們認爲父Widget須要知道該按鈕是否被點擊從而採起相應的處理。動畫
在如下示例中,TapboxB經過回調將其狀態導出到其父項。因爲TapboxB無論理任何狀態,所以它的父類爲StatefulWidget,TapboxB爲StatelessWidget。ui
ParentWidgetState
類:this
_active
狀態;_handleTapboxChanged()
,當盒子被點擊時調用的方法;setState()
更新UI。TapboxB
類:
StatelessWidget
類,由於全部狀態都由其父Widget處理;// ParentWidget 爲 TapboxB 管理狀態.
//------------------------ ParentWidget --------------------------------
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,
),
);
}
}
//------------------------- 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],
),
),
);
}
}
複製代碼
對於一些Widget來講,混合管理的方式很是有用,在這種狀況下,Widget自身管理一些內部狀態,而父Widget管理一些其它外部狀態。
在下面TapboxC示例中,按下時,盒子的周圍會出現一個深綠色的邊框,擡起時,邊框消失,點擊生效,盒子的顏色改變。TapboxC將其_active
狀態導出到其父Widget中,但在內部管理其_highlight
狀態。這個例子有兩個狀態對象_ParentWidgetCState
和_TapboxCState
。
_ParentWidgetCState
對象:
_active
狀態;_handleTapboxChanged()
,當盒子被點擊時調用;_active
狀態改變時調用setState()
方法更新UI。_TapboxCState
對象:
_highlight
狀態;GestureDetector
監聽全部Tap事件,當用戶按下時,添加高亮(深綠色邊框),當用戶擡起(釋放按下)時,會移除高亮;_highlight
狀態,調用setState()
方法更新UI;//---------------------------- ParentWidget ----------------------------
class ParentWidgetC extends StatefulWidget {
@override
_ParentWidgetCState createState() => new _ParentWidgetCState();
}
class _ParentWidgetCState extends State<ParentWidgetC> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
@override
_TapboxCState createState() => new _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
// 在按下時添加綠色邊框,當擡起時,取消高亮
return new GestureDetector(
onTapDown: _handleTapDown, // 處理按下事件
onTapUp: _handleTapUp, // 處理擡起事件
onTap: _handleTap,
onTapCancel: _handleTapCancel,
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: _highlight
? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
複製代碼
當應用中包括一些跨Widget(甚至跨路由)的狀態須要同步時,上面介紹的方法就很難勝任了。好比,有一個設置頁,裏面能夠設置應用語言,可是爲了讓設置實時生效,咱們指望在語言狀態發生改變時,APP Widget可以從新構建(build
),可是APP Widget和設置頁並不在一塊兒,那怎麼辦呢?正確的作法是經過一個全局狀態管理器來處理這種「相距較遠」的Widget之間的通訊。
目前只要有兩種方法:
initState()
方法中訂閱語言狀態改變的事件,當用戶在設置頁切換語言狀態後,觸發語言狀態改變事件,而後APP Widget就會收到通知,接着從新構建(build
)一下便可。關於全局事件總線的實現,咱們後續進行。