Flutter-->key应该放在哪?

1. 实例展示
在文章Flutter-->何时需要使用到key?中提到的例子,有状态的控件的key通过该控件本身的构造函数进行了传递。下面我们在Flutter-->何时需要使用到key?中提到的例子的基础上做一下修改,在方块外层添加Padding控件,如下代码:
@override
void initState() {
super.initState();
widgets = [
Padding(
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(
key: UniqueKey(),
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(
key: UniqueKey(),
),
)
];
}
效果图:

通过上面的效果图,我们发现两个方块的颜色每次交换都会发生变化,与我们预期的效果不一样,这种情况就涉及到了我们上述代码中涉及到到key应该放在哪个控件上。
接下来我们将方块控件中的key放到Padding里,如下代码:
@override
void initState() {
super.initState();
widgets = [
Padding(
key: UniqueKey(),
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(),
),
Padding(
key: UniqueKey(),
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(),
)
];
}
效果图:

通过上面效果图我们发现改变key的位置,就可以达到我们预期的效果。
2. 图解分析
上面实例中涉及到两种情况,下面我们通过图解的方式一一分析。
(1)直接添加Padding控件作为方块的父控件






图1:该图展示里(1)情况对应的控件树及其元素树。
图2:该图展示了交换Row的两个子控件。
图3:Flutter的元素到控件的匹配算法是一次查看树的一个级别,于是先将子控件的子控件变成灰色,
图4:由于Padding控件没有key,所以只比较新旧控件的类型是否匹配,比较发现新旧控件的类型相同,所以旧控件的元素可以引用到新控件。
图5:比较完Padding层接下来是Padding的子控件,也就是方块控件。 比较发现新旧方块控件的key不一样,所以新控件会停用旧元素,删除之前的链接。
图6:由于这个例子中使用的key是本地key,这意味这当控件与元素匹配时,Flutter仅查找在元素树中特定级别内匹配的key。所以方块控件无法在该级别找到具有相同key的元素,因此会创建一个新的,并初始化一个新状态,也就是方块控件的颜色值会有一个新值。所以才会出现上面的效果图的情况。
(2)将方块控件中的key移动到父控件Padding



图7:该图展示里(2)情况对应的控件树及其元素树,从上图发现Padding中有了key。
图8:根据Flutter的元素到控件的匹配算法,会先看Padding层级,比较发现新旧控件的key不一样,新空间会停用旧元素,删除之前的连接。
图9:比较Padding层级删除掉之前的连接后,Flutter会在与Padding控件同一层级的Row元素下面查找是否有相同key的元素,查找发现有,Flutter就会正确更新连接。达到我们上面实例中的效果。
总结
通过上面的例子和分析,我们发现key应该放在控件子树的顶部。也就是说如果上面实例中的Padding再被Padding包裹,那么key应该放在最外层的Padding中,如下代码:
@override
void initState() {
super.initState();
widgets = [
Padding(
key: UniqueKey(),
padding: EdgeInsets.all(8.0),
child: Padding(
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(),
),
),
Padding(
key: UniqueKey(),
padding: EdgeInsets.all(8.0),
child: Padding(
padding: EdgeInsets.all(8.0),
child: StatefulColorContainer(),
),
),
];
}