什么是key
Key
能够帮助开发者在 Widget tree
中保存状态。
Flutter | 深入浅出Key 中使用 key
来解决widget交换的问题?
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
···
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
我们知道 Widget
只是一个配置且无法修改,而 Element
才是真正被使用的对象,并可以修改。
当新的 Widget
到来时将会调用 canUpdate
方法,来确定这个 Element
是否需要更新。
canUpdate
对两个(新老) Widget
的 runtimeType
和 key
进行比较,从而判断出当前的 Element
是否需要更新。若 canUpdate
方法返回 true
说明不需要替换 Element
,直接更新 Widget
就可以了。
StatelessWidget 内部中如何交换 Widget
只比较它们的 runtimeType
。这里 runtimeType
一致,canUpdate
方法返回 true
,两个 Widget 被交换了位置,StatelessElement 调用新持有 Widget 的 build 方法重新构建,在屏幕上两个 Widget 便被正确的交换了顺序。
- 交换流程:
-
Row Widget
为它的子Widget
提供了一组有序的插槽。对于每一个Widget,Flutter
都会构建一个对应的Element
。构建的这个Element Tree
相当简单,仅保存有关每个Widget
类型的信息以及对子Widget
的引用。你可以将这个Element Tree
当做就像你的Flutter App
的骨架。它展示了App
的结构,但其他信息需要通过引用原始Widget
来查找。
交换色块时,Flutter
遍历Widget
树。它从Row Widget
开始,然后移动到它的子 Widget
,Element
树检查 Widget
是否与旧Widget
是相同类型和 Key
。
canUpdate()
它会更新对新 widget
的引用。这里,Widget
没有设置Key
,所以Flutter
只是检查类型。它对第二个孩子做同样的事情。所以 Element
树将根据 Widget
树进行对应的更新。
StatefulWidget 中如何交换
color
的定义放在了State
中,Widget
并不保存State
,真正 hold State
的引用的是 Stateful Element。
(1) 交换控件的次序,Flutter
将遍历 Element
树,检查 Widget
树中 Row
控件并且更新Element
树中的引用,然后第一个 Tile
控件检查它是相同类型,说明不需要更新 Element
,Element
会根据当前 State
展示内容。所以颜色没有发生交换
StatefullWidget 结合 Key
添加 Key
之后的结构
(1) 当现在执行 swap
时, Element
数中 StatafulWidget
控件除了比较类型外,还会比较 key
是否相等:
只有类型和 key
都匹配时,才算找到对应的 Widget
。于是在 Widget Tree
发生交换后,Element Tree
中子控件和原始控件对应关系就被打乱了,所以Flutter
会重建 Element Tree
,直到控件们正确对应上。
Element
位置被正确更新了
Where: 在哪设置 Key
如果把 Key
设置到内部会发生什么
@override
void initState() {
super.initState();
tiles = [
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(key: UniqueKey()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(key: UniqueKey()),
),
];
}
当点击按钮发生交换之后,可以看到两个色块的颜色会随机改变,但是我的预期是两个固定的颜色彼此交换。
为什么
当Widget 树中两个 Padding 发生了交换,它们包裹的色块也就发生了交换:
然后 Flutter 将进行检查,以便对 Element 树进行对应的更新: Flutter 的 Elemetn to Widget 匹配算法将一次只检查树的一个层级:
在第一级,Padding Widget 都正确匹配。
在第二级,Flutter
注意到 Tile 控件的
Key` 不匹配,就停用该 Tile Element,删除 Widget 和 Element 之间的连接
解决问题的方法,将 Key
添加到 Padding
处
总结
-
canUpdate()
方法根据(新老)Widget
的runtimeType
和key
来更新 Widget && Element。 -
LocalKey
的意思是: 当Widget
与Element
匹配时,Flutter
只在树中特定级别内查找匹配的Key
。因此Flutter
无法在同级中找到具有该Key
的Tile Widget
,所以它会创建一个新Element
并初始化一个新State
。 就是这个原因,造成色块颜色发生随机改变,每次交换相当于生成了两个新的Widget
。