在开发中会发现每逢咱们创立一个小部件时,都有一个参数 key,这个key究竟有什么效果呢?下面就来介绍下key是什么,有什么效果以及怎么运用。

key是什么

在 Flutter 开发中咱们常常与要状况打交道。咱们知道 WidgetStatefulStateless 两种。Key 能够协助开发者在 Widget 树中保存状况,在一般的状况下,咱们并不需求运用 Key。那究竟什么时分应该运用 Key 呢?

下面举个比如来阐明:

  • 创立了一个100*100的盒子,色彩是随机生成的,显现的文字是外面传进来的
class StfulItem extends StatefulWidget {
  final String title;
  const StfulItem(this.title, {Key? key}) : super(key: key);
  @override
  _StfulItemState createState() => _StfulItemState();
}
class _StfulItemState extends State<StfulItem> {
  //随机生成色彩
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: color,
      child: Text(widget.title),
    );
  }
}
  • 创立了三个上面创立的盒子,然后点击按钮时删去第一个盒子
class _KeyDemoState extends State<KeyDemo> {
  List<Widget> items = [
    const StfulItem('1111'),
    const StfulItem('2222'),
    const StfulItem('3333'),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("KeyDemo"),),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: items,
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.delete),
        onPressed: (){
          setState(() {
            items.removeAt(0);
          });
        },
      ),
    );
  }
}

Flutter-认识各种Key及使用

把 color 生成不放到 state 里边

class StfulItem extends StatefulWidget {
  final String title;
  StfulItem(this.title, {Key? key}) : super(key: key);
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  _StfulItemState createState() => _StfulItemState();
}

Flutter-认识各种Key及使用

发现效果也是一个个删去,并没有发现色彩复用,由于这个是更新了 widget 导致的。

把上面 StatefulWidget 改成 StatelessWidget,看下效果会是什么样?

class StlItem extends StatelessWidget {
  final String title;
  StlItem(this.title, {Key? key}) : super(key: key);
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      child: Text(title),
      color: color,
    );
  }
}

发现这个时分删去后是正常的,没有复用前一个的色彩。

Flutter-认识各种Key及使用

  • 针对第一种状况,会复用之前的色彩,假设运用 value Key 会是什么效果呢?
List<Widget> items = [
  StfulItem('1111', key: const ValueKey('1111')),
  StfulItem('2222', key: const ValueKey('2222')),
  StfulItem('3333', key: const ValueKey('3333')),
];

发现确实达到了效果,没有在复用了,这儿我就不贴运转的额效果了。

为什么 Stateful Widget 无法正常删去,加上了 Key 之后就能够了,在这之中究竟产生了什么? 为了弄明白这个问题,咱们会涉及 Widget 的 diff 更新机制。先介绍下 Flutter 的烘托原理

Flutter-认识各种Key及使用

Flutter 的烘托原理

并不是一切的Widget都会被独立烘托!只要承继RenderObjectWidget的才会创立RenderObject目标。在Flutter烘托的流程中,有三颗重要的树!Flutter引擎是针对Render树进行烘托!

Widget:Widget里边存储了一个视图的装备信息,包含布局、特点等。它是一份轻量的数据结构,在构建时是结构树,它不参加直接的绘制。

Element:Element是Widget的抽象,当一个Widget初次被创立的时分,那么这个Widget会经过Widget.createElement,创立一个element,挂载到Element Tree遍历视图树。在attachRootWidget函数中,把 widget交给了 RenderObjectToWidgetAdapter这座桥梁,Element创立的一起还持有 Widget和 RenderObject的引用。构建体系经过遍历Element Tree来创立RenderObject,每一个Element都具有一个仅有的key,当触发视图更新时,只会更新符号的需改变的Element。相似react中setState后虚拟dom树的更新。

RenderObject:在 RenderObject树中会产生 Layout、Paint的绘制事情,大部分绘图性能优化产生在这儿,RenderObject Tree构建为Canvas的所需描绘数据,加入到Layer Tree中,最终在Flutter Engine中进行视图组成并光栅化交给GPU。

Widget 更新机制

static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

其实知道 Flutter 的烘托原理后,咱们知道 Widget 仅仅一个装备且无法修正,而 Element 才是真实被运用的目标,并能够修正。当新的 Widget 到来时将会调用 canUpdate 办法,来确定这个 Element是否需求更新。canUpdate 对两个(新老) Widget 的 runtimeType 和 key 进行比较,从而判别出当前的 Element 是否需求更新。若 canUpdate 办法回来 true 阐明不需求替换 Element,直接更新 Widget 就能够了。

Stateless 的比较

咱们在运用 StatelessWidget 时没有传入 key ,而且 runtimeType 也是共同的,所以 canUpdate 回来 true。这个时分就需求 Widget 的 build 办法从头构建,由于 color 和 title 都在这个里边,所以就会看到正常的删去效果。

Stateful 的比较

我界说的 color 是在 state 里边的,Widget 并不保存 State,真实 hold State 的引用的是 Stateful Element。 咱们在运用 StatefulWidget 时没有传人 key ,而且 runtimeType 也是共同的,所以 canUpdate 回来 true,更新 widget,StatefulWidget 的 Element 将复用上一个的。

当咱们传入一个 value key 之后,canUpdate 回来为 false。Flutter 的将会认为这个 Element 需求被替换。然后从头生成一个新的 Element 目标装载到 Element 树上替换掉之前的 Element。

几种类型的 key

key 自身是一个抽象类,有一个工厂构造办法创立ValueKey

  • 直接子类主要有:LocalKey 和 GlobalKey

  • GlobalKey:协助咱们拜访某个Widget的信息

  • LocalKey:它用来差异哪个Element要保存,哪个Element要删去。diff 算法中心地点

    • ValueKey : 以值作为参数 (数字,字符串,任意类型)
    • ObjectKey : 以目标作为参数
    • LocalKey:(创立仅有标识)

LocalKey 的三种类型

LocalKey承继自 Key, 翻译过来便是局部键,LocalKey在具有相同父级的Element中有必要是惟一的。也便是说,LocalKey 在同一层级中有必要要有仅有性。

ValueKey

bool operator ==(Object other) {
  if (other.runtimeType != runtimeType)
    return false;
  return other is ValueKey<T>
      && other.value == value;
}

运用特定类型的值来标识自身的键,ValueKey 在最上面的比如中已经运用过了,他能够接收任何类型的一个目标来最为 key。

经过源码咱们能够看到它重写了 == 运算符,在判别是否持平的时分首要判别了类型是否持平,然后再去判别 value 是否持平;

ObjectKey

bool operator ==(Object other) {
  if (other.runtimeType != runtimeType)
    return false;
  return other is ObjectKey
      && identical(other.value, value);
}

ObjectKeyValueKey 最大的差异便是比较的算不一样,其中首要也是比较的类型,然后就调用 indentical 办法进行比较,其比较的便是内存地址,相当于 java 中直接运用 == 进行比较。而 LocalKey 则相当于 java 中的 equals 办法用来比较值的;

UniqueKey

bool operator ==(Object other) {
  if (other.runtimeType != runtimeType)
    return false;
  return other is ObjectKey
      && identical(other.value, value);
}

每次从头 build 的时分,UniqueKey 都是绝无仅有的,所以就会导致无法找到对应的 Element,状况就会丢掉。有一种做法便是把 UniqueKey 界说在 build 的外面,这样就不会呈现状况丢掉的问题了。

GlobalKey

GlobalKey承继自 Key,比较与 LocalKey,他的效果域是大局的,而 LocalKey 只效果于当前层级。 整个应用程序里都是仅有的,所以同一个 GlobalKey 只能效果在一个widget上。能够经过 GlobalKey拿到所对应的 state 和 element 或许 widget ,用以改变 state 的状况或许变量值。

代码示例:

经过点击悬浮按钮修正内容的值

class GlobalKeyDemo extends StatelessWidget {
  GlobalKeyDemo({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GlobalKeyDemo'),
      ),
      body: ChildPage(),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
        },
      ),
    );
  }
}
class ChildPage extends StatefulWidget {
  const ChildPage({Key? key}) : super(key: key);
  @override
  _ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
  int count = 0;
  String data = 'hello';
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(count.toString()),
          Text(data),
        ],
      ),
    );
  }
}

咱们会发现是不在同一个 widget 里边的,无法修正 ChildPage 中 text 文本的内容,这个时分就需求用到 GlobalKey 来完成

class GlobalKeyDemo extends StatelessWidget {
  //界说大局的key
  final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
  GlobalKeyDemo({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GlobalKeyDemo'),
      ),
      //把大局的key传到其他部件里边去
      body: ChildPage(key: _globalKey,),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          _globalKey.currentState!.setState(() {
            _globalKey.currentState!.data = 'old:' + _globalKey.currentState!.count.toString();
            _globalKey.currentState!.count++;
          });
        },
      ),
    );
  }
}
class ChildPage extends StatefulWidget {
  const ChildPage({Key? key}) : super(key: key);
  @override
  _ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
  int count = 0;
  String data = 'hello';
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(count.toString()),
          Text(data),
        ],
      ),
    );
  }
}

经过 Global Key 咱们获取内容有以下API:

  • currentContext: 能够找到包含renderBox在内的各种element有关的东西
  • currentWidget: 能够得到widget的特点
  • currentState: 能够得到state里边的变量.

Global Key的完成原理

在 GlobalKey 内部有一个静态的_registry Map调集,该调集以 GlobalKey 为 key,以 Element 为Value 。其供给的 currentSate 办法便是以 GlobalKey 目标为 key 获取对应的 Element 目标,然后就能够经过 Element.state 获取详细的值。

我不知道为什么多文章介绍 GlobalKey是十分贵重的,没有特别的复用需求,不主张运用它,经过打断点发现是会调用 unmount-> _unregisterGlobalKey进行销毁的,为什么还说贵重呢!

完成效果:

Flutter-认识各种Key及使用

小结

当你想要跨widget树保存状况时,应该运用key。当修正相同类型的widget调集时,,要将key放在要保存的widget的树的顶部。LocalKey 在同一层级中有必要要有仅有性,GlobalKey 的效果域是大局的。

  • GlobalKey:协助咱们拜访某个Widget的信息

  • LocalKey:它用来差异哪个Element要保存,哪个Element要删去。

    • ValueKey:以值作为参数 (数字,字符串,任意类型)
    • ObjectKey:以目标作为参数
    • LocalKey:(创立仅有标识)

参考文章:

Flutter的烘托原理:zhuanlan.zhihu.com/p/135969091

Flutter | 深入浅出Key:/post/684490…