前言
上篇文章我们简单探索了Flutter的渲染原理---Flutter初探渲染原理初探,这篇文章我们继续来看看上篇文章中提到的Element
的canUpdate
方法里面oldWidget.key == newWidget.key
的,帮我我们更深入的理解Flutter底层的渲染原理。
Key的种类
- Key 本身是一个抽象类,又一个工厂构造方法,创建
ValueKey
- 直接子类主要有:
LocalKey
和GlobalKey
-
GlobalKey
:帮助我们访问某个Widget的信息 -
LocalKey
:它用来区别哪个Element要保留,哪个Element 要删除
1.ValueKey
::以值作为参数(数字、字符串)
2.ObjectKey
:以对象作为参数
3.UniqueKey
:创建唯一标识(保证不会被复用)
-
LocalKey
下面我们通过一个例子来看看Key 的具体用法:
class StateKeyDemo extends StatefulWidget {
const StateKeyDemo({Key? key}) : super(key: key);
@override
_StateKeyDemoState createState() => _StateKeyDemoState();
}
class _StateKeyDemoState extends State<StateKeyDemo> {
List<Widget> items = [
statefulItem(
title: '111',
),
statefulItem(
title: '222',
),
statefulItem(
title: '333',
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('demoStl'), backgroundColor: Colors.blue),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: items,
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
setState(() {
items.removeAt(0);
});
},
),
);
}
}
class statefulItem extends StatefulWidget {
final String? title;
const statefulItem({this.title, Key? key}) : super(key: key);
@override
_statefulItemState createState() => _statefulItemState();
}
class _statefulItemState extends State<statefulItem> {
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!),
);
}
}
暂未使用Key1
当我们点击下方按钮时候,发现第三块不见了,再次点击,发现第二块不见了,但是我们代码里写的是items.removeAt(0)
,不是应该最前面的一块不见了嘛!
此时,只剩下一块,颜色和第一块一样,但是文字却是
333
。如果我们将这几个方块换成StatelessWidget:
class StatlessItem extends StatelessWidget {
final String? title;
StatlessItem({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,
color: color,
child: Text(title!),
);
}
}
我们发现运行结果就是我们想要的结果,方块从前往后一次被删除。
这是为啥呢???
分析
StatlessItem 里面的 color
是widget的一个变量,所以当我们点击按钮,删除方块时,删除的是一个个widget
,但是statefulItem 中的color
属性是属于state
的,state
又是保存在Elemnet
中的,当删除第一个时,删除的是widget,根据增量渲染的原理,相应的Element树会调用canUpdate
方法来判断是否需要更新,依次从左到右,判断每一个Element是否留下还是删除:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
所以,当前Element树中第一个Element会和Widget树中的第二个widget进行对比,类型一样,key一样(都没有key就是都一样null)
,发现当前Element可以保留下来进行复用,相应的state保存下来
复用,所以color
属性就被继续使用了,但是title
还是正常展示的。
接下来我们将方块换回为StatefullWidget,并且给每个方块添加属性key
:
List<Widget> items = [
statefulItem(
title: '111',
key: const ValueKey(111),
),
statefulItem(
title: '222',
key: const ValueKey(222),
),
statefulItem(
title: '333',
key: const ValueKey(333),
)
];
点击按钮操作,发现这次的结果是我们想要的结果,方块依次从左到右删除了。
结论
添加key
属性,就是用来标记widget
的,用来区别每一个widget
的。
GlobalKey的使用
import 'package:flutter/material.dart';
class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
GlobalKeyDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GlobalKeyDemo'),
),
body: ChildPage(
key: _globalKey,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_globalKey.currentState!.setState(() {
_globalKey.currentState!.data =
'old:' + _globalKey.currentState!.count.toString();
_globalKey.currentState!.count++;
});
},
child: const Icon(Icons.add),
),
);
}
}
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(
children: [
Text(count.toString()),
Text(data),
],
),
);
}
}
GlobalKey使用
通过使用GlobalKey生成的key,可以实现StatelessWidget 访问StatefulWidget中的数据信息。验证了前文所说的GlobalKey能帮助我们访问某个Widget的信息。
网友评论