在〈第一個 Flutter 專案〉中看到,範例在繼承 StatefulWidget
後,定義了 key
,在該專案中其實沒用處,那麼 key
用在哪呢?
要先說結論的話,Flutter 的 Widget
會有結構相對應的 Element
樹,若希望 Widget
樹節點調整位置後,Element
樹也跟著調整對應的節點位置,就要使用 key
。
來個簡單範例好了,假設你想開發一個便利貼之類的 App,首先你開發了個原型,可以點選按鈕後,將目前管理的便利貼輪流顯示出來:
import 'package:flutter/material.dart';
void main() => runApp(MyNote());
class MyNote extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Note',
home: MyHomePage(title: 'My Note Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({this.title});
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class Note extends StatelessWidget {
Color color;
double width;
double height;
Note({this.color, this.width, this.height});
@override
Widget build(BuildContext context) {
return Container(
color: color,
width: width,
height: height,
);
}
}
class _MyHomePageState extends State<MyHomePage> {
final notes = [
Note(
color: Colors.red,
width: 200,
height: 200,
),
Note(
color: Colors.blue,
width: 200,
height: 200,
)
];
void _round_robin() {
setState(() {
notes.add(notes.removeAt(0));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: notes,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _round_robin,
tooltip: 'round robin',
child: Icon(Icons.swap_vert),
),
);
}
}
因為目前是原型,Note
暫且先繼承 StatelessWidget
定義,目前 _MyHomePageState
有兩個 Note
,執行結果如下:
Stack
可以管理一組元件,以堆疊方式來繪製畫面,每次按下按鈕,調整的是 notes
中的元件順序,這個順序就是堆疊的順序,因為目前只有兩個 Note
,結果就是兩個顏色輪流出現。
在首次執行時,Widget
樹與 Element
樹是這樣的:
以上只顯示了 Stack
子樹的部份,Element
樹與 Widget
樹結構是對應的,StatelessWidget
的 createElement
建立的,是 Element
的子類 StatelessElement
實例,虛線表示 Element
實例參考至 Widget
實例,Note
對應的 Element
是從 Note
的 createElement
得來,使用對應顏色的外框表示從哪個 Note
建構而來。
接著你按下按鈕後,notes
的順序調整後重新 build
, Widget
樹與 Element
樹一開始會是如下:
接著 Flutter 會以目前 Widget
樹結構與既有的 Element
樹結構比對,看看對應位置的 Widget
是否可用來更新 Element
,這是由 Widget
的 canUpdate
方法決定,其原始碼為:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Element
目前參考的 Widget
是 oldWidget
,對應位置的 Widget
是 newWidget
,可否用來更新 Element
的意思是,Element
是否參考至 newWidget
。
Element
會以參考的 Widget
組態來建立畫面,從原始碼中可以看到,根據的是 Widget
的 runtimeType
與 key
來決定傳回 true
或 false
,傳回 true
的話,Element
就會參考至 newWidget
。
在沒有設置 key
的情況下,key
是 null
,也就是只要 runtimeType
相同,也就是 Widget
只要是同一型態,就可以更新 Element
,就目前的範例來說就是如此,接下來兩棵樹會長這樣:
也就是說,Element
樹結構並沒有調整,只不過由藍色 Note
建立的 Element
,現在參考至紅色的 Note
,而紅色 Note
建立的 Element
,現在參考至藍色的 Note
,因為 Element
參考的 Widget
組態會被用來建立畫面,因此還是看到了顏色的變化。
對這個範例來說,因為 Note
是 StatelessWidget
,Element
沒有保留狀態的問題,Element
只是以新的 Widget
組態來呈現畫面就沒有問題。
如果真的想令 Element
樹也調整,可以設置 Widget
有不同的 key
,例如:
import 'package:flutter/material.dart';
void main() => runApp(MyNote());
...略
class Note extends StatelessWidget {
Color color;
double width;
double height;
// 設置 key
Note({Key key, this.color, this.width, this.height}) : super(key: key);
@override
StatelessElement createElement() {
return super.createElement();
}
@override
Widget build(BuildContext context) {
return Container(
color: color,
width: width,
height: height,
);
}
}
class _MyHomePageState extends State<MyHomePage> {
final notes = [
Note(
key: UniqueKey(), // 設置 key
color: Colors.red,
width: 200,
height: 200,
),
Note(
key: UniqueKey(), // 設置 key
color: Colors.blue,
width: 200,
height: 200,
)
];
...略
}
在以上的範例中,兩個 Note
的 key
會不同,在調整了 Widget
樹前,本來是這樣:
接著按下按鈕後,Widget
樹變化了:
接著 Flutter 會以目前 Widget
樹結構與既有的 Element
樹結構比對,這時因為 canUpdate
傳回 false
,不能直接以 newWidget
來更新 Element
:
因此 Flutter 會根據 key
查找 Element
,將 Element
樹重新調整為符合目前 Widget
樹的結構:
設定 key
後的執行結果,就畫面效果而言相同,基本上,對於 StatelessWidget
不用設置 key
,不過,藉由以上可以知道,key
的設置會影響 Element
樹與 Widget
之間的調整方式,而這個調整方式對於 StatefulWidget
就會有影響了,這下一篇文件再來談。