Flutter key

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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章