当Widget在Widget Tree中移动时,Key会保留其状态。它们可用于保留用户的滚动位置等信息。
Key的种类
Key主要分为Local Keys和Global Keys;
Local Keys
- ValueKey : ValueKey('value')
- ObjectKey : ObjectKey(MutableRectangle(1,2,3,4))
- UniqueKey : UniqueKey()
- PageStorageKey : PageStorageKey(scrollLocation)
Global Keys
- GlobalKey : GlobalKey()
什么时候使用Key
大多数情况下,我们并不需要使用Key。但是如果你发现自己需要添加、删除或者重新排序处于某种状态的相同类型的Widget集合就会用到Key。
import 'package:flutter/material.dart';
class KeyDemoApp extends StatelessWidget{
@override
Widget build(BuildContext context) => MaterialApp(
home: _HomePage(),
);
}
class _HomePage extends StatefulWidget{
@override
State<StatefulWidget> createState() =>_HomePageState();
}
class _HomePageState extends State<_HomePage>{
List<Widget> _widgets;
@override
void initState() {
super.initState();
_widgets= [
RectStateless(Color.fromARGB(255, 0, 0, 0)),
RectStateless(Color.fromARGB(255, 255, 0, 0))
];
}
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Row(children: _widgets,),
),
floatingActionButton: FloatingActionButton(onPressed: swapWidget),
);
void swapWidget(){
setState(() {
_widgets.insert(1, _widgets.removeAt(0));
});
}
}
class RectStateless extends StatelessWidget{
final Color _bg;
RectStateless(this._bg,[Key key]):super(key:key);
@override
Widget build(BuildContext context) {
return Container(color:_bg,
width: 100,
height: 100,
);
}
}
在上面的代码中,如果点击按钮则两个Widget可以自由的更换位置。如果将两个Widget更换为StatefulWidget的Widget后:
class RectStateful extends StatefulWidget{
final Color _bg;
RectStateful(this._bg,[Key key]):super(key:key);
@override
State<StatefulWidget> createState() =>RectStatefulState(_bg);
}
class RectStatefulState extends State<RectStateful>{
final Color _bg;
RectStatefulState(this._bg);
@override
Widget build(BuildContext context) {
return Container(color:_bg,
width: 100,
height: 100,
);
}
}
当点击时两个色块都不会交换,当添加上Key后则又可以交换位置:
_widgets= [
RectStateful(Color.fromARGB(255, 0, 0, 0),UniqueKey()),
RectStateful(Color.fromARGB(255, 255, 0, 0),UniqueKey())
];
从上面的例子可以看出: 如果集合中的整个小部件子树是无状态的,则不需要使用Key。
Key的运作方式
在Stateless的例子中,Row为子Widget提供了一组有序的插槽。对于每个小部件,Flutter都会构建一个相应的Element,Element tree只会保存每个Widget类型以及对子Element的引用信息。可以将Element tree视为Flutter应用程序的骨架,它显示了应用程序的结构。当交换Row中的Widget时,Flutter会遍历Element tree来查看骨架结构是否相同,即检查新的Widget是否和旧的Widget的类型和Key是否相同,如果是一样的,它会更新对新Widget的引用,对于其他Widget也是对应的步骤。 在Stateless的例子中,Widget没有Key,Flutter只会检查其类型。
在Stateful没有Key例子中,每个Widget都会有一个State对象来保存相应的信息,这些信息而不是存储在小部件本身中。Flutter会检查Row小部件的类型,类型是一样的会更新引用,第二个Widget也如此。Flutter会遍历Element tree以及其对应的State来确定在设备上显示的实际内容。因此,看起来相应的Widget并没有正确的交换。
在有Key的Stateful例子中,向小部件添加来key的属性。在交换Row小部件时会像之前一样匹配。但是新的Widget的密钥和之前小部件Element对应的Key不匹配。因此,Flutter会停用这些Element,从第一个不匹配的Element开始,然后Flutter会查看不匹配的子项,查找具有相同密钥的Element,它会找到匹配项,并更新对相应Widget的引用。然后Flutter会为后续的子项做相同的事情。