在开发中会发现每逢咱们创立一个小部件时,都有一个参数 key
,这个key究竟有什么效果呢?下面就来介绍下key是什么,有什么效果以及怎么运用。
key是什么
在 Flutter 开发中咱们常常与要状况打交道。咱们知道 Widget 有 Stateful
和 Stateless
两种。Key 能够协助开发者在 Widget 树中保存状况,在一般的状况下,咱们并不需求运用 Key。那究竟什么时分应该运用 Key 呢?
下面举个比如来阐明:
- 创立了一个100*100的盒子,色彩是随机生成的,显现的文字是外面传进来的
class StfulItem extends StatefulWidget {
final String title;
const StfulItem(this.title, {Key? key}) : super(key: key);
@override
_StfulItemState createState() => _StfulItemState();
}
class _StfulItemState extends State<StfulItem> {
//随机生成色彩
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,
color: color,
child: Text(widget.title),
);
}
}
- 创立了三个上面创立的盒子,然后点击按钮时删去第一个盒子
class _KeyDemoState extends State<KeyDemo> {
List<Widget> items = [
const StfulItem('1111'),
const StfulItem('2222'),
const StfulItem('3333'),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("KeyDemo"),),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: items,
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.delete),
onPressed: (){
setState(() {
items.removeAt(0);
});
},
),
);
}
}
把 color 生成不放到 state
里边
class StfulItem extends StatefulWidget {
final String title;
StfulItem(this.title, {Key? key}) : super(key: key);
final color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
@override
_StfulItemState createState() => _StfulItemState();
}
发现效果也是一个个删去,并没有发现色彩复用,由于这个是更新了 widget 导致的。
把上面 StatefulWidget 改成 StatelessWidget,看下效果会是什么样?
class StlItem extends StatelessWidget {
final String title;
StlItem(this.title, {Key? key}) : super(key: key);
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(title),
color: color,
);
}
}
发现这个时分删去后是正常的,没有复用前一个的色彩。
- 针对第一种状况,会复用之前的色彩,假设运用
value Key
会是什么效果呢?
List<Widget> items = [
StfulItem('1111', key: const ValueKey('1111')),
StfulItem('2222', key: const ValueKey('2222')),
StfulItem('3333', key: const ValueKey('3333')),
];
发现确实达到了效果,没有在复用了,这儿我就不贴运转的额效果了。
为什么 Stateful Widget
无法正常删去,加上了 Key 之后就能够了,在这之中究竟产生了什么? 为了弄明白这个问题,咱们会涉及 Widget 的 diff
更新机制。先介绍下 Flutter 的烘托原理
Flutter 的烘托原理
并不是一切的Widget都会被独立烘托!只要承继RenderObjectWidget的才会创立RenderObject目标。在Flutter烘托的流程中,有三颗重要的树!Flutter引擎是针对Render树进行烘托!
Widget:Widget里边存储了一个视图的装备信息,包含布局、特点等
。它是一份轻量的数据结构,在构建时是结构树,它不参加直接的绘制。
Element:Element是Widget的抽象,当一个Widget初次被创立的时分,那么这个Widget会经过Widget.createElement
,创立一个element,挂载到Element Tree遍历视图树。在attachRootWidget函数中,把 widget交给了 RenderObjectToWidgetAdapter这座桥梁,Element创立的一起还持有 Widget和 RenderObject的引用。构建体系经过遍历Element Tree来创立RenderObject,每一个Element都具有一个仅有的key,当触发视图更新时,只会更新符号
的需改变的Element。相似react中setState后虚拟dom树的更新。
RenderObject:在 RenderObject树中会产生 Layout、Paint
的绘制事情,大部分绘图性能优化产生在这儿,RenderObject Tree构建为Canvas的所需描绘数据,加入到Layer Tree中,最终在Flutter Engine
中进行视图组成并光栅化交给GPU。
Widget 更新机制
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
其实知道 Flutter 的烘托原理后,咱们知道 Widget 仅仅一个装备且无法修正,而 Element 才是真实被运用的目标,并能够修正。当新的 Widget 到来时将会调用 canUpdate
办法,来确定这个 Element是否需求更新
。canUpdate 对两个(新老) Widget 的 runtimeType 和 key 进行比较,从而判别出当前的 Element 是否需求更新。若 canUpdate 办法回来 true 阐明不需求替换 Element,直接更新 Widget 就能够了。
Stateless 的比较
咱们在运用 StatelessWidget 时没有传入 key
,而且 runtimeType 也是共同的
,所以 canUpdate 回来 true。这个时分就需求 Widget 的 build 办法从头构建
,由于 color 和 title 都在这个里边,所以就会看到正常的删去效果。
Stateful 的比较
我界说的 color 是在 state
里边的,Widget 并不保存
State,真实 hold State 的引用的是 Stateful Element。
咱们在运用 StatefulWidget 时没有传人 key ,而且 runtimeType 也是共同的,所以 canUpdate 回来 true,更新 widget,StatefulWidget 的 Element 将复用上一个的。
当咱们传入一个 value key 之后,canUpdate 回来为 false。Flutter 的将会认为这个 Element 需求被替换。然后从头生成一个新的 Element 目标装载到 Element 树上替换掉之前的 Element。
几种类型的 key
key 自身是一个抽象类,有一个工厂构造办法创立ValueKey
-
直接子类主要有:LocalKey 和 GlobalKey
-
GlobalKey:协助咱们拜访某个Widget的信息
-
LocalKey:它用来差异哪个Element要保存,哪个Element要删去。diff 算法中心地点
- ValueKey : 以值作为参数 (数字,字符串,任意类型)
- ObjectKey : 以目标作为参数
- LocalKey:(创立仅有标识)
LocalKey 的三种类型
LocalKey
承继自 Key, 翻译过来便是局部键,LocalKey
在具有相同父级的Element
中有必要是惟一的。也便是说,LocalKey 在同一层级中有必要要有仅有性。
ValueKey
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ValueKey<T>
&& other.value == value;
}
运用特定类型的值来标识自身的键,ValueKey 在最上面的比如中已经运用过了,他能够接收任何类型的一个目标来最为 key。
经过源码咱们能够看到它重写了 == 运算符,在判别是否持平的时分首要判别了类型是否持平,然后再去判别 value 是否持平;
ObjectKey
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ObjectKey
&& identical(other.value, value);
}
ObjectKey 和 ValueKey 最大的差异便是比较的算不一样,其中首要也是比较的类型,然后就调用 indentical
办法进行比较,其比较的便是内存地址,相当于 java 中直接运用 == 进行比较。而 LocalKey 则相当于 java 中的 equals 办法用来比较值的;
UniqueKey
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ObjectKey
&& identical(other.value, value);
}
每次从头 build 的时分,UniqueKey 都是绝无仅有的,所以就会导致无法找到对应的 Element,状况就会丢掉。有一种做法便是把 UniqueKey 界说在 build 的外面,这样就不会呈现状况丢掉的问题了。
GlobalKey
GlobalKey
承继自 Key,比较与 LocalKey,他的效果域是大局的,而 LocalKey 只效果于当前层级
。
整个应用程序里都是仅有的,所以同一个 GlobalKey 只能效果在一个widget上。能够经过 GlobalKey拿到所对应的 state 和 element 或许 widget ,用以改变 state 的状况或许变量值。
代码示例:
经过点击悬浮按钮修正内容的值
class GlobalKeyDemo extends StatelessWidget {
GlobalKeyDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GlobalKeyDemo'),
),
body: ChildPage(),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
},
),
);
}
}
class ChildPage extends StatefulWidget {
const ChildPage({Key? key}) : super(key: key);
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(count.toString()),
Text(data),
],
),
);
}
}
咱们会发现是不在同一个 widget 里边的,无法修正 ChildPage 中 text 文本的内容,这个时分就需求用到 GlobalKey 来完成
class GlobalKeyDemo extends StatelessWidget {
//界说大局的key
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
GlobalKeyDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GlobalKeyDemo'),
),
//把大局的key传到其他部件里边去
body: ChildPage(key: _globalKey,),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
_globalKey.currentState!.setState(() {
_globalKey.currentState!.data = 'old:' + _globalKey.currentState!.count.toString();
_globalKey.currentState!.count++;
});
},
),
);
}
}
class ChildPage extends StatefulWidget {
const ChildPage({Key? key}) : super(key: key);
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(count.toString()),
Text(data),
],
),
);
}
}
经过 Global Key 咱们获取内容有以下API:
- currentContext: 能够找到包含renderBox在内的各种element有关的东西
- currentWidget: 能够得到widget的特点
- currentState: 能够得到state里边的变量.
Global Key的完成原理
在 GlobalKey 内部有一个静态的_registry Map调集,该调集以 GlobalKey 为 key,以 Element 为Value 。其供给的 currentSate 办法便是以 GlobalKey 目标为 key 获取对应的 Element 目标,然后就能够经过 Element.state 获取详细的值。
我不知道为什么多文章介绍 GlobalKey
是十分贵重的,没有特别的复用需求,不主张运用它,经过打断点发现是会调用 unmount
-> _unregisterGlobalKey
进行销毁的,为什么还说贵重呢!
完成效果:
小结
当你想要跨widget
树保存状况时,应该运用key
。当修正相同类型的widget
调集时,,要将key
放在要保存的widget
的树的顶部。LocalKey 在同一层级中有必要要有仅有性,GlobalKey 的效果域是大局的。
-
GlobalKey:协助咱们拜访某个Widget的信息
-
LocalKey:它用来差异哪个Element要保存,哪个Element要删去。
- ValueKey:以值作为参数 (数字,字符串,任意类型)
- ObjectKey:以目标作为参数
- LocalKey:(创立仅有标识)
参考文章:
Flutter的烘托原理:zhuanlan.zhihu.com/p/135969091
Flutter | 深入浅出Key:/post/684490…