23.Key
Key 的作用
我们创建这样一个示例:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
//key的作用就非常大了!!
List<Widget> items = [
ColorItem('第1个'),
ColorItem('第2个'),
ColorItem('第3个'),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Key的作用'),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: items,
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
setState(() {
items.removeAt(0);
});
},
),
);
}
}
class ColorItem extends StatefulWidget {
final String title;
ColorItem(this.title, {Key? key}) : super(key: key);
@override
_ColorItemState createState() => _ColorItemState();
}
class _ColorItemState extends State<ColorItem> {
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(widget.title),
color: color,
);
}
}
运行发现了上面的现象,作为有 iOS 开发经验的我们,很容易联想到这应该是和复用有关的现象。
复用问题
这里我们需要理解, ColorItem 作为一个 StatefulWidget 它是由两部分组成的: Widget 和 State 。这里就是 Widget 被移除后, State 还在内存中。所以出现了复用异常的问题。
那么将数据放在 Widget 中按照这里分析的理论来说应该就没有这个现象了吧?我们试试:

验证成功!那么具体原因是什么呢?
增量渲染与 canUpdate
我们来到 Widget 的实现中, canUpdate 方法决定了一个 Widget 的 Element 是否会被更新,而 Element 的更新,又直接关系到了增量渲染。
同时满足两个条件:
新旧
Widget的类型相同新旧
Widget的Key相同如果
Key是空的,只通过类型判断,就算他们的子Widget完全不同
这里就很好的解释了一开始发生的异常现象。
图解
Widget 和 Element 是一一对应的,而 State 是在 Element 中的。

移除一个
Widget开始检查第一个
Element检查到第二个Widget调用
canUpdate类型相同
Key为空return
ture
于是就用
第一个Element更新第二个Widget
依次类推,就发生了前面的异常现象

既然这里提到了 Key ,那么加上 Key 是否就可以上面这个问题呢?
Key 的使用
这里为每个 ColorItem 加上 Key , 并将 Color 属性放回 State 中:

有效!
Key
Key 本身是一个抽象类,有一个工厂方法:
它有两个子类
LocalKey同一个
父Element内唯一的
GlobalKey整个
App内唯一
刚才用的
ValueKey是LocalKey的子类。
LocalKey
区别哪个 Element 要保留,哪个 Element 要删除。
ValueKey以值作为参数(数字、字符串等)
ObjectKey以对象作为参数
UniqueKey创建唯一标识
GlobalKey
在整个 App 中 唯一的Key。 GlobalKey 可以唯一标识一个元素, 比如访问一个 BuildContext 或者一个 Widget。对于 StatefulWidget 而言, GlobalKey 也可以访问 State 。
当有 GlobalKey 的 Widget 被移动到 Widget树 中新的位置的话,会重新渲染他们的子树。
为了渲染子树,一个 Widget 必须在同一个动画帧内完成在树中,从旧位置移动到新位置。
重新渲染一个使用 GlobalKey 的 Element 是很消耗性能的,因为会触发调用所有相关的 State 的 deactivate 方法,然后让所有依赖 InheritedWidget 的 Widget 重建。
所以如果你不需要达到上面的效果,那么建议使用其他的 Key 。
注意点
两个在同一个树中的
Widget不能同时有相同的GlobalKey。尝试这样做的话在运行时会触发异常。GlobalKeys不应该再每次build的时候被重新创建。他们应该是长期被一个 State 所持有的。例如:
每次
build的时候都创建一个新的GlobalKey会丢弃和旧Key相关的子树,并为新Key创建一个新树。除了会损害性能,这种操作还有可能会对子树造成未知的影响。例如子树中的
GestureDetector将无法继续追踪正在进行中的手势,因为它会在每次build的时候被重新创建
比较好的做法是:
让一个
State持有这个GlobalKey,并且在Build方法外初始化它比如在
State.initState中
GlobalKey简单示例
Last updated
Was this helpful?