1 目标: 实现如下效果
class HomePageState extends State<HomePage> {
List<String> names = ["aaa", "bbb", "ccc"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test Key"),
),
body: ListView(
children: names.map((name) {
// 待会儿我们会修改返回的ListItem为ListItemLess或者ListItemFul
return ListItemLess(name);
}).toList(),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.delete),
onPressed: () {
setState(() {
names.removeAt(0);
});
}
),
);
}
}
(1) StatelessWidget实现
class ListItemLess extends StatelessWidget {
finalString name;
final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
ListItemLess(this.name);
@override
Widget build(BuildContext context) {
return Container(
height: 60,
child: Text(name),
color: randomColor,
);
}
}
它的实现效果是: 每删除一个,所有的颜色都会发现一次变化, 这不是我们想要的
原因:
删除之后调用setState,会重新build,
新的StatelessWidget会重新生成一个新的随机颜色
(2) StatefulWidget实现(没有key)
class ListItemFul extends StatefulWidget {
finalString name;
ListItemFul(this.name): super();
@override
_ListItemFulState createState() => _ListItemFulState();
}
class _ListItemFulState extends State<ListItemFul> {
final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
@override
Widget build(BuildContext context) {
return Container(
height: 60,
child: Text(widget.name),
color: randomColor,
);
}
}
这次不会变颜色了, 但还是有问题, 每次删除的是最后一个
原因:
- 这是因为在删除第一条数据的时候,Widget对应的Element并没有改变;
- 而Element中对应的State引用也没有发生改变;
- 在更新Widget的时候,Widget使用了没有改变的Element中的State;
所以前面的元素都不会有变动, 由于长度变小了, 最后一个就删掉了
(3) StatefulWidget的实现(随机key)
class ListItemFul extends StatefulWidget {
finalString name;
ListItemFul(this.name, {Key key}): super(key: key);
@override
_ListItemFulState createState() => _ListItemFulState();
}
HomePageState代码改为:
body: ListView(
children: names.map((name) {
return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),);
}).toList(),
),
这一次我们发现,每次删除都会出现随机颜色的现象:
原因:
修改了key之后,Element会强制刷新,那么对应的State也会重新创建// Widget类中的代码
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
(4) StatefulWidget的实现(name为key)
HomePageState代码改为:
body: ListView(
children: names.map((name) {
return ListItemFul(name, key: ValueKey(name));
}).toList(),
),
这次达到了我们理想中的效果:
原因:在更新widget的过程中根据key进行了diff算法
在前后进行对比时,发现bbb对应的Element和ccc对应的Element会继续使用,那么就会删除之前aaa对应的Element,而不是直接删除最后一个Element
2 Key的分类
key本身是一个抽象,不过它也有一个工厂构造器,创建出来一个ValueKey
直接子类主要有:LocalKey和GlobalKeyLocalKey: 它应用于具有相同父Element的Widget进行比较,也是diff算法的核心所在;
GlobalKey: 通常我们会使用GlobalKey某个Widget对应的Widget或State或Element
(1) LocalKey
LocalKey有三个子类
ValueKey:
ValueKey是当我们以特定的值作为key时使用,比如一个字符串、数字等等
ObjectKey:
如果两个学生,他们的名字一样,使用name作为他们的key就不合适了 我们可以创建出一个学生对象,使用对象来作为key
UniqueKey
如果我们要确保key的唯一性,可以使用UniqueKey; 比如我们之前使用随机数来保证key的不同,这里我们就可以换成UniqueKey;
(2) GlobalKey
GlobalKey可以帮助我们访问某个Widget的信息,包括Widget或State或Element等对象
比如下面的例子:我希望可以在HomePage中直接访问HomeContent中的内容
class HomePage extends StatelessWidget {
final GlobalKey<_HYHomeContentState> homeKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("列表测试"),
),
body: HomeContent(key: homeKey),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.data_usage),
onPressed: () {
print("${homeKey.currentState.value}");
print("${homeKey.currentState.widget.name}");
print("${homeKey.currentContext}");
},
),
);
}
}
class HomeContent extends StatefulWidget {
finalString name = "123";
HYHomeContent({Key key}): super(key: key);
@override
_HYHomeContentState createState() => _HYHomeContentState();
}
class _HomeContentState extends State<HYHomeContent> {
finalString value = "abc";
@override
Widget build(BuildContext context) {
return Container();
}
}