上一節咱們熟悉了初始化後的flutter的界面。這一節,咱們就來重點了解一下這部分的內容。html
StatelessWidget
和StatefulWidget
。顧名思義,咱們只要若是是不須要根據狀態變化的組件,咱們能夠直接繼承StatelessWidget
.若是和狀態有關係的組件就必須繼承StatefulWidget
。Widget
都是不可變的狀態。 可是實際上,總要根據對應的狀態,視圖發生變化,因此就有了state
。用它來保持咱們的狀態。 這樣,一個Stateful Widget,其實是兩個類:狀態對象state
和Widget
組成的。 以下代碼class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
複製代碼
setState
& build
_MyHomePageState
繼承於State
.一方面須要管理本身的狀態_counter
,一方面須要build
來構造組件。 改變狀態後,須要經過setState
來從新構建widget
,就是會從新調用build
方法,來獲得狀態同步。接着先看看一些經常使用的組件,這些是隨時可用的小部件,開箱即用,你會很是滿意:算法
Text
- 用於簡單地在屏幕上顯示文本的小部件。Image
- 用於顯示圖像。Icon
- 用於顯示Flutter的內置Material和Cupertino圖標。Container
- 在Flutter中,至關於div
。容許在其中進行添加填充,對齊,背景,力大小以及其餘東西的加載。空的時候也會佔用0px的空間,這很方便。Row
, Column
- 這些小部件顯示水平或垂直方向的子項列表。Stack
- 堆棧顯示一個孩子的列表。這個功能很像CSS中的'position'屬性。Scaffold
- 爲應用提供基本的佈局結構。它能夠輕鬆實現底部導航,appBars,後退按鈕等。更多的能夠看目錄。segmentfault
**注意:**若是您熟悉基於組件的框架(如React或Vue),則可能不須要閱讀此內容。Widget
就是組件。bash
這樣的話,實際開發中,也是經過不斷對組件的封裝,來提升工做效率。 好比簡單的封裝一個原型的圖片組件(實際上,應該這個width和height均可以封裝進去的。)網絡
class CircleImage extends StatelessWidget {
final String renderUrl;
CircleImage(this.renderUrl);
@override
Widget build(BuildContext context) {
return Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: new DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(renderUrl ?? ''),
),
),
);
}
}
//直接使用
new CircleImage('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533638174553&di=6913961a358faf638b6233e5d3dcc2b2&imgtype=0&src=http%3A%2F%2Fimage.9game.cn%2F2015%2F3%2F5%2F10301938.png')
複製代碼
運行效果(中間皮卡丘)app
如今讓咱們深刻一點, 先來思考一下 - 爲何Stateful Widget會將State
和Widget
分開呢?框架
State
管理着狀態,它是常駐的。然而,Widget
是不可變的,當配置發生變化,它會立馬發生重建。因此這樣的重建的成本是極低的。 由於State
在每次重建時都沒有拋棄,因此能夠維護它而且沒必要每次重建某些東西時都要進行昂貴的計算以得到狀態屬性。State
沒有丟棄,它能夠不斷重建它的Widget以響應數據變化。當建立一個StatefulWidget
時。當即調用。一般都是以下,這樣簡單的操做。less
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
複製代碼
當這個Widget
調用createState
後, 會將buildContext
傳入。 BuildContext
內有本身在widget tree
上相關的信息。異步
全部的widgets
都有 bool this.mounted
這個屬性. 當BuildContext
傳入時,它將會被標記成 true。若是這個屬性不是true的話,調用setState
會報錯。ide
注意:你能夠在調用setState
前,檢查一下這個變量。
if (mounted) {...` to make sure the State exists before calling `setState()
複製代碼
這個方法只會調用一次,在這個Widget
被建立以後。它必須調用super.initState()
. 在這裏能夠作:
BuildContext
的狀態Streams
ChangeNotifiers
或者其餘會改變的數據的監聽。@override
initState() {
super.initState();
// Add listeners to this class
cartItemStream.listen((data) {
_updateWidget(data);
});
}
複製代碼
它是在initState
方法後,就會調用。 當Widget
依賴的一些數據(好比說是InheritedWidget
,後面會介紹)更新時,它會當即被調用。 同時build
方法,會自動調用。 須要注意的是,你須要經過調用BuildContext.inheritFromWidgetOfExactType
,手動去註冊InheritedWidget
的監聽後,這個方法纔會起做用。
文檔還建議,當InheritedWidget更新時,若是須要進行網絡調用(或任何其餘昂貴的操做),它可能會頗有用。
這個方法會常常被調用。
若是父組件發生變化,並且必須去重建widget時,並且被相同的runtimeType
重建時,這個方法會被調用。
由於Flutter是複用state
的。因此,你可能須要從新初始化狀態。 若是你的Widget
是須要根據監聽的數據,發生變化的,那麼你就須要從舊的對象中反註冊,而後註冊新的對象。
注意:若是您但願重建與此狀態關聯的Widget,則此方法基本上是'initState'的替代!
這個方法,會自動調用build
,因此不須要去調用setState
@override
void didUpdateWidget(Widget oldWidget) {
if (oldWidget.importantProperty != widget.importantProperty) {
_init();
}
}
複製代碼
這個方法會被framework
和開發者不斷調用。用來通知組件刷新。
這個方法的不能有異步的回調。其餘,就能夠隨便使用。
void updateProfile(String name) {
setState(() => this.name = name);
}
複製代碼
(這個狀態暫時不是很理解) State
從樹中刪除時會調用Deactivate
,但可能會在當前幀更改完成以前從新插入。此方法的存在主要是由於State
對象能夠從樹中的一個點移動到另外一個點。
這不多使用。
State刪除對象時調用Dispose ,這是永久性的。 在此方法取消訂閱並取消全部動畫,流等
state
對象被移除了,若是調用setState
,會拋出的錯誤。
widget
都有本身的context
。這個context是父組件經過build方法給他返回的。首先,先看下面代碼。咱們將在四個地方打印context的hashCode,來看看有什麼不一樣
//...
_MyHomePageState() {
//1. constructor
print('constructor context hashcode = ${context.hashCode}');
}
void _incrementCounter() {
//2. member method
print('_incrementCounter context hashcode = ${context.hashCode}');
setState(() {
_counter++;
});
}
@override
void initState() {
super.initState();
//3. initState
print('initState context hashcode = ${context.hashCode}');
}
@override
Widget build(BuildContext context) {
return new Scaffold(
//...
floatingActionButton: new FloatingActionButton(
onPressed: () {
//4.floattingbutton
print(
'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
_incrementCounter();
},
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
複製代碼
很明顯能夠看到,咱們在initState
方法時,已經分配拿到了父組件的BuildContext.接下來的直接使用context,也都是同一個。
咱們知道能夠經過Scaffold的context來彈出一個SnackBar
。這裏想經過點擊彈出這個。 修改代碼以下:
//...
floatingActionButton: new FloatingActionButton(
onPressed: () {
print(
'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('I am context from Scaffold'),
));
},
tooltip: 'Increment',
child: new Icon(Icons.add),
),
複製代碼
運行,可是運行報錯信息以下:
很明顯。經過上面的測試,咱們知道這裏的context,確實不是Scaffold。那咱們要如何在這裏拿到Scaffold的context呢?
修改代碼以下,經過Builder方法,獲得這個context
.
//...
floatingActionButton: new Builder(
builder: (context) {
return new FloatingActionButton(
onPressed: () {
print(
'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('I am context from Scaffold'),
));
_incrementCounter();
},
tooltip: 'Increment',
child: new Icon(Icons.add),
);
},
)
複製代碼
運行結果
咱們能夠看到,咱們確實拿到了Scaffold
分配的Context
,並且彈出了SnackBar
.
後續過程當中,必定要注意這個Context的使用。
注意:這裏其實還有另一個方法,來獲得這個BuildContext
。就是將FloatingActionButton分離出來,寫成另一個組件,就能經過build
方法拿到了。
方法以下:
class ScaffoldButton extends StatelessWidget {
ScaffoldButton({this.onPressedButton});
final VoidCallback onPressedButton;
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
print(
'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('I am context from Scaffold')));
onPressedButton();
},
tooltip: 'Increment',
child: new Icon(Icons.add),
);
}
}
複製代碼
再將floatingActionButton
修改爲這個類
//...
floatingActionButton: ScaffoldButton(
onPressedButton: () {
_incrementCounter();
},
));
複製代碼
隨意點開一個Widget
,就會發現,能夠傳遞一個參數Key
.那這個Key究竟是幹啥子,有什麼用呢?
Flutter是受React啓發的,因此Virtual Dom的diff算法也參考過來了(應該是略有修改),在diff的過程當中若是節點有Key來比較的話,可以最大程度重用已有的節點(特別在列表的場景),除了這一點這個Key也用在不少其餘的地方這個之後會總結一下。總之,這裏咱們能夠知道key可以提升性能,因此每一個Widget都會構建方法都會有一個key的參數可選,貫穿着整個框架。
一般狀況下,咱們不須要去傳遞這個Key
。由於framework
會在內部自處理它,來區分不一樣的widgets
下面有幾種狀況,咱們可使用它
ObjectKey
和ValueKey
來對組件進行區分。能夠看PageStorageKey, 和另一個例子,這個例子是deletion: flutter.io/cookbook/ge….
簡單的來講,當咱們使用Row
或者Column
時,想要執行一個remove
的動畫
new AnimatedList(
children: [
new Card(child: new Text("foo")),
new Card(child: new Text("bar")),
new Card(child: new Text("42")),
]
)
複製代碼
當咱們移除"bar"後
new AnimatedList(
children: [
new Card(child: new Text("foo")),
new Card(child: new Text("42")),
]
)
複製代碼
由於咱們沒有定義Key,因此可能flutter並不知道,咱們那個item發生了改變,因此可能發生在位置1上的動畫,可能發生在其餘位置。 正確的修改以下:
new AnimatedList(
children: [
new Card(key: new ObjectKey("foo"), child: new Text("foo")),
new Card(key: new ObjectKey("bar"), child: new Text("bar")),
new Card(key: new ObjectKey("42"), child: new Text("42")),
]
)
複製代碼
這樣當咱們移除"bar"的時候,flutter就能準確的區別到正確的位置上。 Key
雖然不是Index
,可是對於每個元素來講,是獨一無二的。
GlobalKey
GlobalKey
的場景是,從父控件和跨子Widget
來傳遞狀態時。 須要注意的是:不要濫用GlobalKey,若是有更好的方式的,請使用其餘方式來傳遞狀態。這裏有一個例子是 經過給Scaffold添加GolbalKey。而後通widget.GolbalKey.state來調用showSnackBar
class _MyHomePageState extends State<MyHomePage> {
final globalKey =
new GlobalKey<ScaffoldState>();
void _incrementCounter() {
globalKey.currentState
.showSnackBar(SnackBar(content: Text('I am context from Scaffold')));
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: globalKey,
//...
)
}
}
複製代碼
這樣就能夠直接從父控件調用子Widget
的狀態。
這邊文章,咱們對StateFulWidget有了升入的認識。
下一遍文章:咱們將更加深刻的對Flutter的界面開發的一些原理
Flutter中的Key,LocalKey,GlobalKey... And More
what-are-keys-used-for-in-flutter-framework