1 目标: 实现如下效果

class HomePageState extends State<HomePage> {List<String> names = ["aaa", "bbb", "ccc"];@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Test Key"),),body: ListView(children: names.map((name) {// 待会儿我们会修改返回的ListItem为ListItemLess或者ListItemFulreturn 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);@overrideWidget 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));@overrideWidget 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();@overrideWidget 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";@overrideWidget build(BuildContext context) {return Container();}}
