The Key of Widget in Flutter
當我們剛開始使用Flutter,我們在繼承StatelessWidget和StatefulWidget時,不會關注Key。整個界面的渲染通常也不會有什麼問題。但當我們想要複用Widget,想要優化我們的渲染性能時,Key就成為了一個繞不過的話題。
一個問題
首先,我們需要明白Flutter的界面渲染原理。深入了解Flutter界面開發這一篇部落格講得非常清楚。當我們需要更新界面時,我們會通過setState的方式,讓StatefulWidget周遊Widget樹進行重建。
有時候,我們會發現,即使我們調用了setState,Widget也沒有進行重建。
它的原因
原來,當我們用一個StatefulWidget渲染界面,其狀态可能被我們儲存在其
State
中。當我們修改Widget樹中ChildWidget的位置,如果修改完成後,整個樹的每一個Widget沒有發生改變,Flutter也不會更新整個樹。
上面一段話,非常繞且難懂。但我們很容易想象,Element樹決定了頁面最終展示到螢幕上的樣子,Flutter會從上到下逐一對比Widget樹和Element樹中的每個節點,如果左右節點的類型一緻,那麼就認為該element仍然是有效的,可用複用。
一個粟子
我們用這樣一個
StatefulWidget
,将其color儲存在State中。
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => StatefulColorfulTileState();
}
class StatefulColorfulTileState extends State<StatefulColorfulTile> {
// 将 Color 儲存在 StatefulColorfulTile 的 State StatefulColorfulTileState 中.
Color color;
@override
void initState() {
super.initState();
color = UniqueColorGenaretor.getColor();
}
@override
Widget build(BuildContext context) => buildColorfulTile(color);
}
複制
class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles;
@override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(),
StatefulColorfulTile(),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: tiles))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles));
}
void swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
複制
當我們調用
tiles.insert(1, tiles.removeAt(0));
時,界面是不會更新的。因為對于Flutter來說,我們交換了兩個一模一樣的Widget,交換完成後,Widget樹并沒有變化。如果我們把ChildWidget換成
StatelessWidget
,且color資訊儲存在Widget中,界面就會更新,因為Flutter會認為這是兩個完全不同的Widget。
那麼,如果我們一定要用
StatefulWidget
來做ChildWidget時,我們應該怎麼做呢?
很簡單:
void initState() {
super.initState();
tiles = [
// 使用 UniqueKey
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
];
}
複制
我們給每一個
StatefulWidget
一個唯一的key。這樣,當我們交換Widget時,Flutter就不會認為這兩個Widget是一模一樣的。
用在哪裡
簡而言之,當我們在一個容器下,放了多個相同的Widget對象時,我們就應該思考,這些Widget它們是否應該有個id,還是說它們本質上沒有差別。
根據業務不同,它們的id,可能是字元(ValueKey),可能是一個複雜的資料結構(ObjectKey)。或者,我們隻是想讓它們彼此唯一(UniqueKey)。
此外,我們知道Element是和State綁定的。是以,當你修改了相同類型的Widget節點位置,又想要保留它的狀态時,你需要使用Key。
GlobalKey
官方文檔
作用
- 允許widget在應用程式中的任何位置更改其parent而不丢失其狀态。應用場景:在兩個不同的螢幕上顯示相同的widget,并保持狀态相同。
- 可以在任何地方通過
擷取其狀态,甚至可以調用key.currentState
改變它的狀态。是以,我們可以認為,用上GlobalKey的Widget,是需要伴随App整個生命周期的。像一個靜态的Widget一樣。changeState()
結語
對于key的使用,需要根據我們業務的具體情況,根據Flutter的渲染機制,靈活掌握。其核心就是Element與Widget的綁定關系,是類型綁定,還是key+類型的綁定。