Key 的原理和使用
没有 Key 会发生什么 Flutter 需要 Widget 的 key 作为每个组件的唯一标识 假设现在有一列相同 Widget 组成的页面,如果没有 Key,在页面 Widget 数量变化或者上下顺序变化时,页面并不会按照我们看到的逻辑跟顺序进行变化,因为在 Flutter 看来每个 Widget 都是一样的,无论
没有 Key 会发生什么
Flutter 需要 Widget 的 key 作为每个组件的唯一标识
假设现在有一列相同 Widget 组成的页面,如果没有 Key,在页面 Widget 数量变化或者上下顺序变化时,页面并不会按照我们看到的逻辑跟顺序进行变化,因为在 Flutter 看来每个 Widget 都是一样的,无论怎么调整页面中相同 Widget 的数量和顺序,在 Flutter 看来都是没有变化的,所以页面也就不会有变化
Widget和Element的对应关系
在开发时,一个重要问题就是:如何结构化的组织视图数据,提供给渲染引擎,最终完成界面的显示。虽然不太的 UI 框架有不同的实现方式,但是基本都会使用视图树(View Tree)的概念
Flutter 将视图树进行的拓展,把视图数据的组织和渲染分成了三部分,即 Widget、Element 和 RenderObject
三者关系如下:

Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同时也是数据的映射,是 Flutter框架中最基本的概念
Flutter 的核心设计思想就是一切皆 Widget
Flutter 将 Widget 设计为不可变的,所以当视图渲染的配置信息发生变化时,Flutter 会选择重建 Widget 树的方式进行数据更新,用数据驱动 UI 构建的方式是简单高效的
但是这样也有缺点,因为大量对象的销毁和重建会对垃圾回收造成很大压力,不过 Widget 本身不设计实际的渲染位图,只是一份轻量的数据结构,重建的成本还是比较低的
Element 是 Widget 的一个实例化对象,它承载了视图构建的上下文数据,是链接结构化配置信息到最终完成渲染的桥梁
Flutter 的渲染流程是这样的:
-
根据 Widget 树生成 Element 树,树中的节点都继承自 Element 类
-
根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自 RenderObject 类并关联到 Element.renderObject 属性上
-
根据渲染树生成 Layer 树,然后在屏幕上显示,Layer 树中的节点都继承自 Layer 类
其实在写 StatefulWidget 的时候就会发现,继承了 StatefulWidget 类需要重写 createState 方法,需要返回一个继承了 State 类的对象
返回的这个 State 对象其实就是 Element 树中的元素,而重写 createState 就是实现了创建元素的方法
当 Widget Tree 有变化的时候(比如,交换两个 Widget 的顺序,修改属性等),Element Tree 其实是不想有变化的,因为如果 Widget Tree 一有变化就触发 Element Tree 重建的话,重建后的 Element Tree 中的状态会全部丢失,更别说实现动画效果和热重载了
所以当 Widget Tree 发生变化时,Flutter 会尽量将变化后的 Widget 与当前 Element Tree 对应起来
Flutter 根据下面两点判断某个 Widget和某个 Element 是否可以关联起来:
-
类型:对比 Widget 和 Element 是否是同一类型(比如,Column 对应 ColumnElement)
-
Key:当 Widget 传了 Key,触发重建时,Flutter 会先对比类型,然后对比 Key;如果 Key 跟之前的 Element 不想同,Flutter 会自动搜索跟当前 Widget 同一级别的其他 Widget是否有跟 Element 相同的 Key,如果有就可以对应上
局部键LocalKey的三种类型
LocalKey 要求必须在局部(同一级别)具有唯一性
ValueKey
可以传入一个 Value 作为 key,当 value 相等的时候,ValueKey 也就相等
ObjectKey
ObjectKey 也是传入一个值,但是对比的时候不是对比传入的值是否相同,而是对比是否是同一个 instance(类似指针的概念),也就是在内存中是否是同一个 Object
UniqueKey
UniqueKey 只有和自己相同,每个 UniqueKey 都是独一无二的
全局键GlobalKey的用法
当同一层级的 Widget 变化顺序的时候,可以使用 LocalKey 来帮助 Flutter 实现 Widget 和 Element 一一对应,但 LocalKey 只会在同一层级生效,如果某个 Widget 变化的层级,那么 LocalKey 就找不到了,同时对应的 Element 状态也会丢失,要解决这个问题可以使用 GlobalKey
GlobalKey 是全局唯一的 Key 不像 LocalKey 那样局限在同一层级中,不管套了多少层 Widget,GlobalKey 依然能够找到对应的 Widget 跟 Element
所以如果有变化 Widget 层级但又不想让状态丢失的话,可以给 Widget 一个 GlobalKey
GlobalKey 还可以用于获取 Widget 的详细信息跟状态,类似前端中的 document.getElementById
假设现在有一个 GlobalKey,并且赋值给了某一个 StatefulWidget
final _globalKey = GlobalKey();
现在可以通过 GlobalKey 来获取和修改这个 StatefulWidget 的属性跟状态
final state = (_globalKey.currentState as xxxState);
print(state._count);
state.setState((){
state._count++;
});
除此之外还能拿到 GlobalKey 对应的 Widget、Element 等,能做到的东西有很多