本篇主要经过一些实例来深刻分析Flutter中的keyhtml
经过key.dart中的注释能够看到相关说明算法
Flutter中的内部重建机制,有时候须要配合Key的使用才能触发真正的“重建”,key一般在widget的构造函数中,当widget在widget树中移动时,Keys存储对应的state,在实际中,这将能帮助咱们存储用户滑动的位置,修改widget集合等等markdown
大多数时候咱们并不须要key,可是当咱们须要对具备某些状态且相同类型的组件 进行 添加、移除、或者重排序时,那就须要使用key,不然就会遇到一些古怪的问题,看下例子,
点击界面上的一个按钮,而后交换行中的两个色块
**less
使用 StatelessWidget
(StatelessColorfulTile
) 作 child
(tiles
):ide
class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
StatelessColorfulTile(),
StatelessColorfulTile(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}
swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatelessColorfulTile extends StatelessWidget {
Color myColor = UniqueColorGenerator.getColor();
@override
Widget build(BuildContext context) {
return Container(
color: myColor, child: Padding(padding: EdgeInsets.all(70.0)));
}
}
复制代码
使用 StatefulWidget
(StatefulColorfulTile
) 作 child
(tiles
):函数
List<Widget> tiles = [
StatefulColorfulTile(),
StatefulColorfulTile(),
];
...
class StatefulColorfulTile extends StatefulWidget {
@override
ColorfulTileState createState() => ColorfulTileState();
}
class ColorfulTileState extends State<ColorfulTile> {
Color myColor;
@override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}
@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}
复制代码
结果点击切换颜色按钮,没有反应了
性能
为了解决这个问题,咱们在StatefulColorfulTile widget构造时传入一个UniqueKeyui
class _ScreenState extends State<Screen> {
List<Widget> widgets = [
StatefulContainer(key: UniqueKey(),),
StatefulContainer(key: UniqueKey(),),
];
···
复制代码
而后点击切换按钮,又能够愉快地交换颜色了。this
为何StatelessWidget正常更新,StatefullWidget就更新失效,加了key以后又能够了呢?为了弄清楚这其中发生了什么,咱们须要再次弄清楚Flutter中widget的更新原理
在framework.dart中能够看到 关于widget的代码spa
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
···
/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// If the widgets have no key (their key is null), then they are considered a
/// match if they have the same type, even if their children are completely
/// different.
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 是否须要更新**。
咱们并无传入key,因此只比较两个runtimeType,咱们将color定义在widget中,这将使得他们具备不一样的runtimeType,所以可以更新element 显示出交换位置的效果
改为stateful以后 咱们将color的定义放在在State中,Widget并不保存State,真正hold State的引用是Stateful Element,在咱们没有给widget设置key以前,将只会比较这两个widget的runtimeType,因为两个widget的属性和方法都相同,canUpdate方法将返回false,在Flutter看来,没有发生变化,所以点击按钮 色块并无交换,当咱们给widget一个key之后,canUpdate方法将会比较两个widget的runtimeType以及key,返回true(这里runtimeType相同,key不一样),这样就能够正确感知两个widget交换顺序,可是这种比较也是有范围的tu
为了提高性能,Flutter的diff算法是有范围的,会对某一个层级的widget进行比较而不是一个个比较,咱们把上面的ok的例子再改动一下,将带key的 StatefulContainer 包裹上 Padding 组件,而后点击交换按钮,会发生下面奇怪的现象。点击以后不是交换widget,而是从新建立了!
@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和Element树以下
Elemetn to Widget
匹配算法将一次只检查树的一个层级:,
(2)因而开始第二层的对比,此时发现元素与组件的Key并不匹配,因而把它设置成不可用状态,可是这里的key是本地key,(Local Key),Flutter并不能找到另外一层里面的Key(另一个Padding Widget中的key),所以flutter就建立了一个新的
class _ScreenState extends State<Screen> {
List<Widget> widgets = [
Padding(
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulContainer(),
),
Padding(
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulContainer(),
),
];
复制代码