Flutter -- 15.Key

一.引入key的概念

  • 這里有一個小demo
  • 每次點擊按鈕沥阱,刪除第一個Widget

1.使用StatefulWidget

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {

  final List<Widget> _widgets = [const StateFulTest('1111'), const  StateFulTest('2222'), const StateFulTest('33333')];

  void _onPressed() {
    setState(() {
      _widgets.removeAt(0);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('Key Demo'),),
        floatingActionButton: FloatingActionButton(
          onPressed: _onPressed,
          child: const Icon(Icons.add),
        ),
        body: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: _widgets,
        )
    );
  }
}

class StateFulTest extends StatefulWidget {
  const StateFulTest(this.title ,{Key? key}) : super(key: key);

  final String title;

  @override
  _StateFulTestState createState() => _StateFulTestState();
}

class _StateFulTestState extends State<StateFulTest> {
  Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      width: 100,
      color: color,
      child: Center(
        child: Text(widget.title),
      ),
    );
  }
}


class StatelessTest extends StatelessWidget {
  StatelessTest(this.title, {Key? key}) : super(key: key);

  final String title;

  Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      width: 100,
      color: color,
      child: Center(
        child: Text(title),
      ),
    );
  }
}
key_colors_bug.gif
  • 文字顯示正常膀息,但是Widget的顏色卻是不正常的
  • 看起來就像刪除的第三個Widget

2.使用StatelessWidget

  • 那這里可否懷疑是Stateful導致的問題芹扭?
  • _widgets中的StateFulTest更換為StatelessDemo
  • 發(fā)現居然正常了

3.使用StatelessWidget芯肤,將State中的color放到Widget

  • 經過測試正常

4.問題排查思路

  • 出現該問題是因為state沒有被刷新或者重置
  • 通過渲染原理可知state的創(chuàng)建是在StatefulElement的構造方法中问拘,Element與state是綁定
  • 出現的問題是因為刪除后的第一個Widget綁定了之前刪掉的Element遍略,導致state不會被刷新,出現顏色不會變化的bug
  • 針對Widget刷新時骤坐,綁定了之前一個Element導致的bug绪杏。查看Widget源碼是否有關于Widget與Element綁定的方法
  • 在Widget類中,有一種非常重要的方法canUpdate
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  • 進入這個方法的前提是2個Widget的父Element是相同的
  • 這個函數的注釋寫得非常清楚纽绍,新的Wdiget是否可以更新到老的Widget的Elment
  • 簡答來說蕾久,是否可以復用Element
  • 還有一點我們也需要了解,新老Widget的子部件不同也不會影響到是否可以update

  • runtimeType為對象的運行時類型的形式
  • 通過這個方法我們可以很明確的分析到拌夏,很明顯新老Widget是一種類型僧著,并且runtimeType肯定是一致的。key沒有傳值為nil障簿,因此該方法必定返回true盹愚。
  • 將Element1更新到Widget2中
  • 至此,問題問題排查清楚

  • 下面用一張圖來簡單的分析一下


    statefulWidget_Element_Error.png

5.當我們移除一個Widget時站故,同時再添加一個Widget皆怕,此時的Element是否會復用移除的?

  final List<Widget> _widgets = [const StateFulTest('1111'), const  StateFulTest('2222'), const StateFulTest('33333')];
  
  void _onPressed() {
    setState(() {
      _widgets.removeAt(0);
      _widgets.add(const StateFulTest('4444'));
    });
  }
1.添加一個不帶key的Widget

按上圖邏輯,Element3會被釋放
那么現在斷點調試西篓,查看一下Flutter在這塊是怎么優(yōu)化的
此時斷點斷在createElement()

  StatefulElement createElement() => StatefulElement(this);

結果:并沒有進入斷點愈腾,添加新的Widget(不含key)沒有執(zhí)行createElement

2.添加一個帶key的Widget
  void _onPressed() {
    setState(() {
      _widgets.removeAt(0);
      _widgets.add(const StateFulTest('4444', key: ValueKey(4),));
    });
  }

結果:進入斷點,添加新的Widget(含key)執(zhí)行createElement

3.總結
  • 在setState方法中岂津,當我們移除一個Widget時虱黄,同時再添加一個Widget,此時的Element會先去復用移除的(canUpdate判斷是否能復用)

二.Key

  • 抽象類寸爆,一般使用它的派生類LocalKeyGlobalKey
abstract class Key {
  /// Construct a [ValueKey<String>] with the given [String].
  ///
  /// This is the simplest way to create keys.
  const factory Key(String value) = ValueKey<String>;

  /// Default constructor, used by subclasses.
  ///
  /// Useful so that subclasses can call us, because the [new Key] factory
  /// constructor shadows the implicit constructor.
  @protected
  const Key.empty();
}
  • 默認Key的工程構造方法也是使用ValueKey實現礁鲁,ValueKey為LocalKey的派生類

  • 在之前的代碼修改_widgets盐欺,加入key
final List<Widget> _widgets = [const StateFulTest('1111', key: Key('1111'),), const  StateFulTest('2222', key: Key('2222')), const StateFulTest('33333', key: Key('33333'))];
key_colors_ferfect.gif
  • 結果顯示正常
  • 至此關于構造方法中Key的作用相信大家應該比較明白了

三.LocalKey

  • 一般用于相同父Element小部件的比較。也就是在Widget中update方法使用
1.ValueKey
  • 指的是通過一個值來創(chuàng)建的key仅醇。其中傳入的值類型是泛型冗美,任意類型
  • 使用場景,通過value值來對比
2.ObjectKey
  • 指的是通過一個對象來創(chuàng)建的key
  • 使用場景析二,通過Object指針地址來對比
3.UniqueKey
  • 指的是創(chuàng)建了一個唯一的key粉洼。通過該對象生成一個唯一的hash碼
  • 使用場景,每次構建時key都是不同的叶摄,因此Element永遠不會復用
keyDemo() {

  //創(chuàng)建測試對象
  TestKeyClass testK = TestKeyClass();

  //ValueKey
  ValueKey key1 = ValueKey(testK);
  ValueKey key2 = ValueKey(testK);
  ValueKey key3 = const ValueKey(3);
  print(key1 == key2); //true
  print(key1 == key3); //false

  //ObjectKey
  ObjectKey objectKey1 = ObjectKey(testK);
  ObjectKey objectKey2 = ObjectKey(testK);
  ObjectKey objectKey3 = ObjectKey(TestKeyClass());
  print(objectKey1 == objectKey2); //true
  print(objectKey1 == objectKey3); //false

  //UniqueKey
  print(UniqueKey() == UniqueKey()); //false
}

四.GlobalKey

  • 一般通過使用GlobalKey來保存/獲取某一部件的Widget属韧、State、Element
  • 概念類似于iOS中的tag

  • 這里介紹一個簡單的使用場景蛤吓,在StatelessWidget中刷新StatefulWidget的狀態(tài)
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  final GlobalKey _globalKey = GlobalKey();

  void _onPressed() {
    _GlobalKeyTestState state = _globalKey.currentState as _GlobalKeyTestState ;

    /*
    * 下面寫法會報出警告
    * The member 'setState' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'.
    * 大致意思是setState這個方法應該只能在state方法里面調用
    * 因此這里寫了一個refreshState方法中轉一下來消除警告
    * */
    // state.setState(() {
    //   state.count ++;
    // });

    state.count ++;
    state.refreshState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Global Key Demo'),),
        body: GlobalKeyTest(key: _globalKey,),
        floatingActionButton: FloatingActionButton(
          onPressed: _onPressed,
          child: const Icon(Icons.add),
        ),
      ),
    );
  }

}

class GlobalKeyTest extends StatefulWidget {
  const GlobalKeyTest({Key? key}) : super(key: key);

  @override
  _GlobalKeyTestState createState() => _GlobalKeyTestState();
}

class _GlobalKeyTestState extends State<GlobalKeyTest> {

  var count = 0;

  refreshState() {
    setState(() {
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$count'),
    );
  }
}
globalKey.gif
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末宵喂,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子会傲,更是在濱河造成了極大的恐慌锅棕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淌山,死亡現場離奇詭異裸燎,居然都是意外死亡,警方通過查閱死者的電腦和手機泼疑,發(fā)現死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門德绿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人退渗,你說我怎么就攤上這事移稳。” “怎么了氓辣?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵秒裕,是天一觀的道長袱蚓。 經常有香客問我钞啸,道長,這世上最難降的妖魔是什么喇潘? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任体斩,我火速辦了婚禮,結果婚禮上颖低,老公的妹妹穿的比我還像新娘絮吵。我一直安慰自己,他們只是感情好忱屑,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布蹬敲。 她就那樣靜靜地躺著暇昂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伴嗡。 梳的紋絲不亂的頭發(fā)上急波,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音瘪校,去河邊找鬼澄暮。 笑死,一個胖子當著我的面吹牛阱扬,可吹牛的內容都是我干的泣懊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼麻惶,長吁一口氣:“原來是場噩夢啊……” “哼馍刮!你這毒婦竟也來了?” 一聲冷哼從身側響起窃蹋,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤渠退,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脐彩,有當地人在樹林里發(fā)現了一具尸體碎乃,經...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年惠奸,在試婚紗的時候發(fā)現自己被綠了梅誓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡佛南,死狀恐怖梗掰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情嗅回,我是刑警寧澤及穗,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站绵载,受9級特大地震影響埂陆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜娃豹,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一焚虱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懂版,春花似錦鹃栽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薇芝。三九已至,卻和暖如春丰嘉,著一層夾襖步出監(jiān)牢的瞬間恩掷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工供嚎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留黄娘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓克滴,卻偏偏與公主長得像逼争,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子劝赔,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內容