Flutter 之 Key 詳解

1、Key 作用

在 Flutter 中,Key 是不能重復(fù)使用的,所以 Key 一般用來做唯一標(biāo)識(shí)。組件在更新的時(shí)候,其狀態(tài)的保存主要是通過判斷組件的類型或者 key 值是否一致。因此,當(dāng)各組件的類型不同的時(shí)候,類型已經(jīng)足夠用來區(qū)分不同的組件了,此時(shí)我們可以不必使用 key。但是如果同時(shí)存在多個(gè)同一類型的控件的時(shí)候,此時(shí)類型已經(jīng)無法作為區(qū)分的條件了,我們就需要使用到 key。

2、示例

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> list = [
    Box(
      color: Colors.blue,
    ),
    Box(
      color: Colors.red,
    ),
    Box(
      color: Colors.orange,
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //打亂list的順序
          });
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

// ignore: must_be_immutable
class Box extends StatefulWidget {
  Color color;
  Box({super.key, required this.color});
  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

運(yùn)行后發(fā)現(xiàn)改變 list Widget 順序后,Widget 顏色會(huì)變化,但是每個(gè) Widget 里面的文本內(nèi)容并沒有變化?
當(dāng) List 重新排序后 Flutter 檢測(cè)到了 Widget 的順序變化,所以重新繪制 ListWidget,但是 Flutter 發(fā)現(xiàn) List Widget 里面的元素沒有變化,所以就沒有改變 Widget 里面的內(nèi)容。
把 List 里面的 Box 的顏色改成一樣,這個(gè)時(shí)候重新對(duì) list 進(jìn)行排序,就很容易理解了。重新排序后雖然執(zhí)行了setState,但是代碼和以前是一樣的,所以 Flutter 不會(huì)重構(gòu) List Widget 里面的內(nèi)容, 也就是 Flutter 沒法通過 Box 里面?zhèn)魅氲膮?shù)來識(shí)別 Box 是否改變。如果要讓 FLutter 能識(shí)別到 List Widget 子元素的改變,就需要給每個(gè) Box 指定一個(gè) key。

3、LocalKey、GlobalKey

1、 Flutter key 子類包含 LocalKey 和 GlobalKey 。

局部鍵(LocalKey):ValueKey、ObjectKey、UniqueKey。
全局鍵(GlobalKey): GlobalKey、GlobalObjectKey。
ValueKey (值key):把一個(gè)值作為 key 。
UniqueKey(唯一key):程序生成唯一的 Key,當(dāng)我們不知道如何指定 ValueKey 的時(shí)候就可以使用 UniqueKey。
ObjectKey(對(duì)象 key):把一個(gè)對(duì)象實(shí)例作為 key。
GlobalKey:每個(gè) Widget 都對(duì)應(yīng)一個(gè) Element ,我們可以直接對(duì) Widget 進(jìn)行操作,但是無法直接操作 Widget 對(duì)應(yīng)的 Element 。而 GlobalKey 就是那把直接訪問 Element 的鑰匙。通過 GlobalKey可以獲取到 Widget 對(duì)應(yīng)的 Element 。

2、key 的使用

LocalKey 使用

//替換上述示例中 list 數(shù)據(jù)
  // List<Widget> list = [
  //   Box(
  //     color: Colors.blue,
  //   ),
  //   Box(
  //     color: Colors.red,
  //   ),
  //   Box(
  //     color: Colors.orange,
  //   )
  // ];
  List<Widget> list = [
    Box(
      key: const ValueKey(1),
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Box(color: Colors.red)),
      color: Colors.red,
    ),
    Box(
      key: UniqueKey(), //程序自動(dòng)生成一個(gè)key
      color: Colors.orange,
    )
  ];

GlobalKey 的使用
如果把 LocalKey 比作局部變量, GlobalKey 就類似于全局變量。在使用了 LocalKey 之后,在上述示例中,當(dāng)屏幕狀態(tài)改變的時(shí)候把 Colum 換成了 Row,Box 的狀態(tài)就會(huì)丟失。

//示例中要使用添加過 LocalKey 之后的 list
//更改 Scaffold 中 body 里面的內(nèi)容
     // body: Center(
     //   child: Column(
     //     mainAxisAlignment: MainAxisAlignment.center,
     //     children: list,
     //   ),
     // ),
     body: Center(
       child: MediaQuery.of(context).orientation == Orientation.portrait
           ? Column(
               mainAxisAlignment: MainAxisAlignment.center,
               children: list,
             )
           : Row(
               mainAxisAlignment: MainAxisAlignment.center,
               children: list,
             ),
     ),

分析:由于一個(gè) Widget 狀態(tài)的保存主要是通過判斷組件的類型或者 key 值是否一致。LocalKey 只在當(dāng)前的組件樹有效,所以把 Colum 換成了 Row 的時(shí)候 Widget 的狀態(tài)就丟失了。為了解決這個(gè)問題我們就可以使用 GlobalKey。

//在以上代碼的基礎(chǔ)上更改 list 數(shù)據(jù)
///1.不適用 key
 // List<Widget> list = [
 //   Box(
 //     color: Colors.blue,
 //   ),
 //   Box(
 //     color: Colors.red,
 //   ),
 //   Box(
 //     color: Colors.orange,
 //   )
 // ];

 ///2.使用 LocalKey
 // List<Widget> list = [
 //   Box(
 //     key: const ValueKey(1),
 //     color: Colors.blue,
 //   ),
 //   Box(
 //     key: ObjectKey(Box(color: Colors.red)),
 //     color: Colors.red,
 //   ),
 //   Box(
 //     key: UniqueKey(), //程序自動(dòng)生成一個(gè)key
 //     color: Colors.orange,
 //   )
 // ];
 
 ///3.GlobalKey
 List<Widget> list = [];
 final GlobalKey _key1 = GlobalKey();
 final GlobalKey _key2 = GlobalKey();
 final GlobalKey _key3 = GlobalKey();
 @override
 void initState() {
   super.initState();
   list = [
     Box(
       key: _key1,
       color: Colors.blue,
     ),
     Box(
       key: _key2,
       color: Colors.red,
     ),
     Box(
       key: _key3,
       color: Colors.orange,
     )
   ];
 }
3、 GlobalKey 獲取子組件

globalKey.currentState 可以獲取子組件的狀態(tài),執(zhí)行子組件的方法。
globalKey.currentWidget 可以獲取子組件的屬性。
_globalKey.currentContext!.findRenderObject() 可以獲取渲染的屬性。

class _HomePageState extends State<HomePage> {
 final GlobalKey _globalKey = GlobalKey();
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     floatingActionButton: FloatingActionButton(
       child: const Icon(Icons.add),
       onPressed: () {
         //1、獲取子組件的狀態(tài) 調(diào)用子組件的屬性
         var state = (_globalKey.currentState as _BoxState);
         setState(() {
           state._count++;
         });

         //2、獲取子組件的屬性
         var box = (_globalKey.currentWidget as Box);
         print(box.color);

         //3、獲取子組件渲染的屬性
         var renderBox =
             (_globalKey.currentContext!.findRenderObject() as RenderBox);
         print(renderBox.size);
       },
     ),
     appBar: AppBar(
       title: const Text('Title'),
     ),
     body: Center(
       child: Box(
         key: _globalKey,
         color: Colors.red,
       ),
     ),
   );
 }
}

4、 Widget Tree、Element Tree 、 RenderObject Tree

Flutter 應(yīng)用是由是 Widget Tree、Element Tree 和 RenderObject Tree 組成。
Widget:Widget 就是一個(gè)類, 是 Element 的配置信息。與 Element 的關(guān)系可以是一對(duì)多,一份配置可以創(chuàng)造多個(gè) Element 實(shí)例。
Element:Widget 的實(shí)例化,內(nèi)部持有 Widget 和 RenderObject。
RenderObject:負(fù)責(zé)渲染繪制。
默認(rèn)情況下面,當(dāng) Flutter 同一個(gè) Widget 的大小,順序變化的時(shí)候,F(xiàn)Lutter 不會(huì)改變 Widget 的 state。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容