這是我參與8月更文挑戰的23天,活動詳情檢視:8月更文挑戰
Flutter Key
當widget
在widget tree
中移動時,key
可以保留它的狀態.
key
可用於保留使用者的滾動位置,或在修改集合時保持狀態.
什麼時候需要Key
如果你還沒有用過key
,說明你可能還不需要使用key
.
事實也正是如此,大多數時候,我們不需要使用key
.
但是如果你發現自己需要新增,刪除或重新排序處於某種狀態的相同型別的widget
的集合的時候,可能就是需要用到key
的時候了.
我們先用一個官方的例子來看一下key
的作用 , 交換兩個只有顏色不同的widget
class KeyStudy extends StatefulWidget {
@override
_KeyStudyState createState() => _KeyStudyState();
}
class _KeyStudyState extends State<KeyStudy> {
List<Widget> tiles;
@override
void initState() {
super.initState();
tiles = [StatelessColorfulWidget(), StatelessColorfulWidget()];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: tiles,
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}),
);
}
}
class StatelessColorfulWidget extends StatelessWidget {
static List<Color> colorList = [Colors.blue, Colors.yellow, Colors.red];
final defaultColor = colorList[new Random().nextInt(3)];
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: defaultColor,
);
}
}
複製程式碼
程式碼不復雜,我們先定義了一個無狀態的widget
:StatelessColorfulWidget
,
然後在頁面上並列放置2個該widget
.
通過點選按鈕來調換這兩個widget
的位置.
看起來很正常 , 操作也符合我們的預期 , 此時我們將StatelessColorfulWidget
從StatelessWidget
換成StatefulWidget
,並將顏色儲存在state
中 ,
class StatefulColorfulWidget extends StatefulWidget {
StatefulColorfulWidget({Key key}) : super(key: key);
@override
_StatefulColorfulWidgetState createState() => _StatefulColorfulWidgetState();
}
class _StatefulColorfulWidgetState extends State<StatefulColorfulWidget> {
Color defaultColor;
@override
void initState() {
super.initState();
defaultColor = StatelessColorfulWidget.colorList[new Random().nextInt(3)];
}
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: defaultColor,
);
}
}
複製程式碼
程式碼依舊不復雜 , 然後用StatefulColorfulWidget
替代tiles
中的StatelessColorfulWidget
,來看一下效果:
我點了幾下按鈕,一點反應都沒有,但是此時如果加個key呢?
像這樣:
@override
void initState() {
super.initState();
tiles = [
StatefulColorfulWidget(key: UniqueKey()),
StatefulColorfulWidget(key: UniqueKey())
];
}
複製程式碼
之後我們看看效果:
可以看到,加了key
之後,又能正常交換了.
注意 : 如果你在寫以上程式碼來看效果的時候,記得每次修改重新執行app
,因為我們的tiles
是定義在initState()
中的,
熱過載不會執行initState()
方法 , 所以可能效果與預期不符.
如果集合中的整個widget
子樹是無狀態的, 則不需要key
.
說到無狀態,就想起了cool
, 確實, 這個key
確實很cool
.
為什麼會這樣
在無狀態的widget
中, Row
為其子widget提供了一組有序插槽.
對於每一個widget
,Flutter都會構建一個相應的Element
.
Element tree
只儲存有關每個widget
的型別,以及對子元素的引用資訊,
我們可以將 Element tree
看做是Flutter應用程式的骨架,它顯示了應用程式的結構, 但可以用過引用原始widget
來查詢所有其他資訊.
當我們交換行中widget
的順序時,flutter會遍歷Element Tree
來檢視骨架的結構是否相同.
從父元素,也就是這裡的Row
開始,然後檢查它的子元素,Element Tree
會檢查新的widget
和舊的widget
的runTimeType
和key
是否相同.
如果是,就會更新對新的widget
的引用, 在第一種情況下,我們使用的是StatelessWidget
,因為這個widget
沒有key
,所以flutter只檢查它的型別.
此時,我們再對StatefulWidget
的情況進行同樣的分析:
基本與之前一樣,有同樣的Widget Tree
和Element Tree
,但是現在也有一對state
物件,並將顏色的資訊儲存到了這裡.
而不是像StatelessWidget
一樣儲存在Widget
自身中.
所以此時當我們交換2個wdiget
的順序時,Flutter遍歷Element Tree
,檢查widget
的型別和key
,並更新引用.
Flutter使用Element Tree
及其相應的狀態來確定實際顯示的內容 , 所以看起來widget
沒有正確交換.
而當我們使用了key
之後,Flutter檢查會發現key
不匹配 , 所以Flutter 會 deactive這些element, 並查詢與這個key
相同的element
,找到後會更新其對widget
的引用.
此處,交換之後,對第一個StatefulTileWidget
進行檢查的時候,發現右側對應的StatefulileElement
的key
不匹配,會為它尋找匹配的element
第二個也是如此 , 現在就可以正常更換widget
了.
因為如果要修改集合中的widget
的數量或者順序,那麼key
是很有用的.
此處我們舉例僅使用了顏色,但實際上我們在state
中會儲存更加複雜的東西, 比如播放動畫,顯示使用者輸入的資料,滾動位置等等,這些都涉及到狀態.
所以,現在我們有一個場景,需要使用key
,這個key
應該放在哪裡呢?
將Key放在Widget樹的哪個位置
我們應該在widget
樹的頂部指定一個需要保留的key
.
至於為神馬是在頂部,我們通過下面一個例子來看一下原因:
我很偷懶的用快捷方式為這兩個StatefulColorfulWidget
新增了Padding
,然後我們看一下效果:
看起來好像不是交換啊?點選按鈕時,widget
變成了隨機的顏色,為什麼只是將widget
用Padding
包裹之後就變成了這樣呢?
(有人可能會發現widget的大小改變了,那是因為之前的widget寬度是200,加了padding之後超出螢幕範圍了,我偷偷改小了)
下圖是新增了Padding
之後的Widget Tree
和Element Tree
的樣子
當我們交換子項的位置時,Flutter的element
到widget
的匹配演算法是一次檢視樹的一個級別.
在第一級子項,一起都匹配正確,
但是在第二級時,Flutter會注意到element
和widget
的key
不匹配,因此會停用這個element
,去掉它們之間的關聯,
因為我們此處使用的UniqueKey
是LocalKey
,這意味著當將widget
和element
匹配時,Flutter僅查詢在樹中特定級別內相匹配的key
.
因為無法在該級別找到具有相同key
的element
,所以會建立一個新的,並初始化一個新的狀態 ,
在這種情況下,widget
會有一個新的顏色.
當然,另外一個也會出現同樣的問題.
那如果我們在Padding
新增key
呢?Flutter就會找到正確的key
來重新建立連線,就像前面的示例一樣.
所以,我們現在知道什麼時候使用key
,以及將key
放在哪裡了.
但是如果你去Flutter的文件裡檢視key
,會發現有幾種不同的key
,那麼我們該使用哪一個呢?
應該使用哪個Key
Key的種類
首先我們肯定要知道的是,key究竟有哪幾種?
GlobalKey有兩種用途,它允許widget
在App的任何位置更改父級而不會丟失狀態,或者可以使用它們在Widget Tree
完全不同的部分中訪問有關另一個widget
的資訊.
比如要在兩個不同的螢幕顯示相同的widget
,並保持所有相同的狀態, 此時可以使用GlobalKey.
而LocalKey
又有以下幾個子類:
- ObjectKey : 從物件中獲取其標識的鍵用作其值。
- UniqueKey : 僅等於自身的Key。
- ValueKey : 使用特定型別的值標識自己的鍵。
- PageStorageKey : ValueKey的子類, 它定義了PageStorage值的儲存位置。
PageStorageKey是用來儲存使用者滾動位置的專用key,因此APP可以保留它以供後面使用.
我們要注意3點:
1.在具有相同父元素的Elements中,鍵必須唯一。相比之下,GlobalKeys在整個應用程式中必須唯一。
2.Key的子類應該是LocalKey或GlobalKey的子類.
3.GlobalKey更為昂貴,因此如果沒有必要,請使用ValueKey, ObjectKey, 或者 UniqueKey.
使用場景
在代辦事項列表應用程式中 , 我們可能希望待辦事項的文字是恆定且獨特的,這種情況,ValueKey
是一個很好的選擇,因為它的文字是value
.
如果每個Widget
有更復雜的資料組合,任何單個欄位都有可能與另一個條目相同,但組合起來是唯一的,這時可以用ObjectKey
.
如果集合中有多個具有相同值的widget
,或者我們想確保每個widget
與其他的widget
不同,則可以使用UniqueKey
.
我們的示例就是用的UniqueKey
,因為我們沒有任何其他常量資料儲存在我們的元件上,而且在構建widget
之前,我們還不知道顏色是什麼.
小結
當你想要跨widget
樹保留狀態時 , 應該使用key
.
當修改相同型別的widget
集合時, 要將key
放在要保留的widget
的樹的頂部.
並且要根據widget中儲存的資料型別選擇對應的key
.