Flutter key簡單介紹

__white發表於2021-08-23

這是我參與8月更文挑戰的23天,活動詳情檢視:8月更文挑戰

Flutter Key

widgetwidget 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的位置.

看起來很正常 , 操作也符合我們的預期 , 此時我們將StatelessColorfulWidgetStatelessWidget換成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())
    ];
  }
複製程式碼

之後我們看看效果:

c3

可以看到,加了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和舊的widgetrunTimeTypekey是否相同.

如果是,就會更新對新的widget的引用, 在第一種情況下,我們使用的是StatelessWidget,因為這個widget沒有key,所以flutter只檢查它的型別.

此時,我們再對StatefulWidget的情況進行同樣的分析:

 存下來直接上傳

基本與之前一樣,有同樣的Widget TreeElement 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進行檢查的時候,發現右側對應的StatefulileElementkey不匹配,會為它尋找匹配的element

第二個也是如此 , 現在就可以正常更換widget了.

因為如果要修改集合中的widget的數量或者順序,那麼key是很有用的.

此處我們舉例僅使用了顏色,但實際上我們在state中會儲存更加複雜的東西, 比如播放動畫,顯示使用者輸入的資料,滾動位置等等,這些都涉及到狀態.

所以,現在我們有一個場景,需要使用key,這個key應該放在哪裡呢?

將Key放在Widget樹的哪個位置

我們應該在widget樹的頂部指定一個需要保留的key.

至於為神馬是在頂部,我們通過下面一個例子來看一下原因:

image-20200622161650197

我很偷懶的用快捷方式為這兩個StatefulColorfulWidget新增了Padding,然後我們看一下效果:

 站可能

看起來好像不是交換啊?點選按鈕時,widget 變成了隨機的顏色,為什麼只是將widgetPadding包裹之後就變成了這樣呢?

(有人可能會發現widget的大小改變了,那是因為之前的widget寬度是200,加了padding之後超出螢幕範圍了,我偷偷改小了)

下圖是新增了Padding之後的Widget TreeElement Tree的樣子

image-20200622162128860 當我們交換子項的位置時,Flutter的elementwidget的匹配演算法是一次檢視樹的一個級別.

在第一級子項,一起都匹配正確,

但是在第二級時,Flutter會注意到elementwidgetkey不匹配,因此會停用這個element,去掉它們之間的關聯,

因為我們此處使用的UniqueKeyLocalKey,這意味著當將widgetelement匹配時,Flutter僅查詢在樹中特定級別內相匹配的key.

因為無法在該級別找到具有相同keyelement,所以會建立一個新的,並初始化一個新的狀態 ,

在這種情況下,widget會有一個新的顏色.

當然,另外一個也會出現同樣的問題.

那如果我們在Padding新增key呢?Flutter就會找到正確的key來重新建立連線,就像前面的示例一樣.

所以,我們現在知道什麼時候使用key,以及將key放在哪裡了.

但是如果你去Flutter的文件裡檢視key,會發現有幾種不同的key,那麼我們該使用哪一個呢?

應該使用哪個Key

Key的種類

首先我們肯定要知道的是,key究竟有哪幾種?

  • GlobalKey : 在整個APP中唯一的鍵
  • LocalKey : 在具有相同父元素的Elements中,鍵必須唯一。

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.