在本節(jié)中,您將創(chuàng)建一個自定義有狀態(tài)的widget。 您將使用一個自定義有狀態(tài)widget來替換兩個無狀態(tài)widget - 紅色實心星形圖標(biāo)和其旁邊的數(shù)字計數(shù) - 該widget用兩個子widget管理一行:IconButton和Text。
實現(xiàn)一個自定義的有狀態(tài)widget需要創(chuàng)建兩個類:
本節(jié)展示如何為Lakes應(yīng)用程序構(gòu)建一個名為FavoriteWidget的StatefulWidget。第一步是選擇如何管理FavoriteWidget的狀態(tài)。
Widget的狀態(tài)可以通過多種方式進行管理,但在我們的示例中,widget本身(FavoriteWidget)將管理自己的狀態(tài)。 在這個例子中,切換星形圖標(biāo)是一個獨立的操作,不會影響父窗口widget或其他用戶界面,因此該widget可以在內(nèi)部處理它自己的狀態(tài)。
FavoriteWidget類管理自己的狀態(tài),因此它重寫createState()來創(chuàng)建狀態(tài)對象。 框架會在構(gòu)建widget時調(diào)用createState()。在這個例子中,createState()創(chuàng)建_FavoriteWidgetState的實例,您將在下一步中實現(xiàn)該實例。
class FavoriteWidget extends StatefulWidget {
@override
_FavoriteWidgetState createState() => new _FavoriteWidgetState();
}
注意: 以下劃線(_)開頭的成員或類是私有的。有關(guān)更多信息,請參閱Dart語言參考中的庫和可見性部分 。
自定義State類存儲可變信息 - 可以在widget的生命周期內(nèi)改變邏輯和內(nèi)部狀態(tài)。 當(dāng)應(yīng)用第一次啟動時,用戶界面顯示一個紅色實心的星星形圖標(biāo),表明該湖已經(jīng)被收藏,并有41個“喜歡”。狀態(tài)對象存儲這些信息在_isFavorited和_favoriteCount變量。
狀態(tài)對象也定義了build方法。此build方法創(chuàng)建一個包含紅色IconButton和Text的行。 該widget使用IconButton(而不是Icon), 因為它具有一個onPressed屬性,該屬性定義了處理點擊的回調(diào)方法。IconButton也有一個icon的屬性,持有Icon。
按下IconButton時會調(diào)用_toggleFavorite()方法,然后它會調(diào)用setState()。 調(diào)用setState()是至關(guān)重要的,因為這告訴框架,widget的狀態(tài)已經(jīng)改變,應(yīng)該重繪。 _toggleFavorite在: 1)實心的星形圖標(biāo)和數(shù)字“41” 和 2)虛心的星形圖標(biāo)和數(shù)字“40”之間切換UI。
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
int _favoriteCount = 41;
void _toggleFavorite() {
setState(() {
// If the lake is currently favorited, unfavorite it.
if (_isFavorited) {
_favoriteCount -= 1;
_isFavorited = false;
// Otherwise, favorite it.
} else {
_favoriteCount += 1;
_isFavorited = true;
}
});
}
@override
Widget build(BuildContext context) {
return new Row(
mainAxisSize: MainAxisSize.min,
children: [
new Container(
padding: new EdgeInsets.all(0.0),
child: new IconButton(
icon: (_isFavorited
? new Icon(Icons.star)
: new Icon(Icons.star_border)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
),
new SizedBox(
width: 18.0,
child: new Container(
child: new Text('$_favoriteCount'),
),
),
],
);
}
}
提示: 當(dāng)文本在40和41之間變化時,將文本放在SizedBox中并設(shè)置其寬度可防止出現(xiàn)明顯的“跳躍” ,因為這些值具有不同的寬度。
將您自定義stateful widget在build方法中添加到widget樹中。首先,找到創(chuàng)建圖標(biāo)和文本的代碼,并刪除它:
// ...
new Icon(
Icons.star,
color: Colors.red[500],
),
new Text('41')
// ...
在相同的位置創(chuàng)建stateful widget:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = new Container(
// ...
child: new Row(
children: [
new Expanded(
child: new Column(
// ...
),
new FavoriteWidget(),
],
),
);
return new MaterialApp(
// ...
);
}
}
誰管理著stateful widget的狀態(tài)?widget本身?父widget?都會?另一個對象?答案是……這取決于實際情況。 有幾種有效的方法可以給你的widget添加互動。作為小部件設(shè)計師。以下是管理狀態(tài)的最常見的方法:
如何決定使用哪種管理方法?以下原則可以幫助您決定:
如果有疑問,首選是在父widget中管理狀態(tài)
我們將通過創(chuàng)建三個簡單示例來舉例說明管理狀態(tài)的不同方式:TapboxA、TapboxB和TapboxC。 這些例子功能是相似的 - 每創(chuàng)建一個容器,當(dāng)點擊時,在綠色或灰色框之間切換。 _active確定顏色:綠色為true,灰色為false。
有時,widget在內(nèi)部管理其狀態(tài)是最好的。例如, 當(dāng)ListView的內(nèi)容超過渲染框時, ListView自動滾動。大多數(shù)使用ListView的開發(fā)人員不想管理ListView的滾動行為,因此ListView本身管理其滾動偏移量。
_TapboxAState 類:
// TapboxA 管理自身狀態(tài).
//------------------------- 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],
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Flutter Demo'),
),
body: new Center(
child: new TapboxA(),
),
),
);
}
}
對于父widget來說,管理狀態(tài)并告訴其子widget何時更新通常是最有意義的。 例如,IconButton允許您將圖標(biāo)視為可點按的按鈕。 IconButton是一個無狀態(tài)的小部件,因為我們認(rèn)為父widget需要知道該按鈕是否被點擊來采取相應(yīng)的處理。
在以下示例中,TapboxB通過回調(diào)將其狀態(tài)導(dǎo)出到其父項。由于TapboxB不管理任何狀態(tài),因此它的父類為StatelessWidget。
ParentWidgetState 類:
TapboxB 類:
// ParentWidget 為 TapboxB 管理狀態(tài).
//------------------------ 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],
),
),
);
}
}
提示: 在創(chuàng)建API時,請考慮使用@required為代碼所依賴的任何參數(shù)使用注解。
'package: flutter/foundation.dart';
對于一些widget來說,混搭管理的方法最有意義的。在這種情況下,有狀態(tài)widget管理一些狀態(tài),并且父widget管理其他狀態(tài)。
在TapboxC示例中,點擊時,盒子的周圍會出現(xiàn)一個深綠色的邊框。點擊時,邊框消失,盒子的顏色改變。 TapboxC將其_active狀態(tài)導(dǎo)出到其父widget中,但在內(nèi)部管理其_highlight狀態(tài)。這個例子有兩個狀態(tài)對象_ParentWidgetState和_TapboxCState。
_ParentWidgetState 對象:
_TapboxCState 對象:
//---------------------------- 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 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;
_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);
}
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return new GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
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,
),
),
);
}
}
另一種實現(xiàn)可能會將高亮狀態(tài)導(dǎo)出到父widget,同時保持_active狀態(tài)為內(nèi)部,但如果您要求某人使用該TapBox,他們可能會抱怨說沒有多大意義。 開發(fā)人員只會關(guān)心該框是否處于活動狀態(tài)。開發(fā)人員可能不在乎高亮顯示是如何管理的,并且傾向于讓TapBox處理這些細(xì)節(jié)。
Flutter提供各種按鈕和類似的交互式widget。這些widget中的大多數(shù)實現(xiàn)了Material Design 指南, 它們定義了一組具有質(zhì)感的UI組件。
如果你愿意,你可以使用GestureDetector來給任何自定義widget添加交互性。 您可以在管理狀態(tài)和Flutter Gallery中找到GestureDetector的示例。
注意: Futter還提供了一組名為Cupertino的iOS風(fēng)格的小部件 。
When you need interactivity, it’s easiest to use one of the prefabricated widgets. Here’s a partial list: 當(dāng)你需要交互性時,最容易的是使用預(yù)制的widget。這是預(yù)置widget部分列表:
更多建議: