1 目标: 实现如下效果

image.png

  1. class HomePageState extends State<HomePage> {
  2. List<String> names = ["aaa", "bbb", "ccc"];
  3. @override
  4. Widget build(BuildContext context) {
  5. return Scaffold(
  6. appBar: AppBar(
  7. title: Text("Test Key"),
  8. ),
  9. body: ListView(
  10. children: names.map((name) {
  11. // 待会儿我们会修改返回的ListItem为ListItemLess或者ListItemFul
  12. return ListItemLess(name);
  13. }).toList(),
  14. ),
  15. floatingActionButton: FloatingActionButton(
  16. child: Icon(Icons.delete),
  17. onPressed: () {
  18. setState(() {
  19. names.removeAt(0);
  20. });
  21. }
  22. ),
  23. );
  24. }
  25. }

(1) StatelessWidget实现

  1. class ListItemLess extends StatelessWidget {
  2. finalString name;
  3. final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
  4. ListItemLess(this.name);
  5. @override
  6. Widget build(BuildContext context) {
  7. return Container(
  8. height: 60,
  9. child: Text(name),
  10. color: randomColor,
  11. );
  12. }
  13. }

它的实现效果是: 每删除一个,所有的颜色都会发现一次变化, 这不是我们想要的
image.png
原因:
删除之后调用setState,会重新build,
新的StatelessWidget会重新生成一个新的随机颜色

(2) StatefulWidget实现(没有key)

  1. class ListItemFul extends StatefulWidget {
  2. finalString name;
  3. ListItemFul(this.name): super();
  4. @override
  5. _ListItemFulState createState() => _ListItemFulState();
  6. }
  7. class _ListItemFulState extends State<ListItemFul> {
  8. final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
  9. @override
  10. Widget build(BuildContext context) {
  11. return Container(
  12. height: 60,
  13. child: Text(widget.name),
  14. color: randomColor,
  15. );
  16. }
  17. }

这次不会变颜色了, 但还是有问题, 每次删除的是最后一个
image.png
原因:

  • 这是因为在删除第一条数据的时候,Widget对应的Element并没有改变;
  • 而Element中对应的State引用也没有发生改变;
  • 在更新Widget的时候,Widget使用了没有改变的Element中的State;
  • 所以前面的元素都不会有变动, 由于长度变小了, 最后一个就删掉了

    (3) StatefulWidget的实现(随机key)

    1. class ListItemFul extends StatefulWidget {
    2. finalString name;
    3. ListItemFul(this.name, {Key key}): super(key: key);
    4. @override
    5. _ListItemFulState createState() => _ListItemFulState();
    6. }

    HomePageState代码改为:

    1. body: ListView(
    2. children: names.map((name) {
    3. return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),);
    4. }).toList(),
    5. ),

    这一次我们发现,每次删除都会出现随机颜色的现象:
    image.png
    原因:
    修改了key之后,Element会强制刷新,那么对应的State也会重新创建

    1. // Widget类中的代码
    2. static bool canUpdate(Widget oldWidget, Widget newWidget) {
    3. return oldWidget.runtimeType == newWidget.runtimeType
    4. && oldWidget.key == newWidget.key;
    5. }

    (4) StatefulWidget的实现(name为key)

    HomePageState代码改为:

    1. body: ListView(
    2. children: names.map((name) {
    3. return ListItemFul(name, key: ValueKey(name));
    4. }).toList(),
    5. ),

    这次达到了我们理想中的效果:
    image.png
    原因:

  • 在更新widget的过程中根据key进行了diff算法

  • 在前后进行对比时,发现bbb对应的Element和ccc对应的Element会继续使用,那么就会删除之前aaa对应的Element,而不是直接删除最后一个Element

    2 Key的分类

    key本身是一个抽象,不过它也有一个工厂构造器,创建出来一个ValueKey
    直接子类主要有:LocalKey和GlobalKey

  • LocalKey: 它应用于具有相同父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中的内容

  1. class HomePage extends StatelessWidget {
  2. final GlobalKey<_HYHomeContentState> homeKey = GlobalKey();
  3. @override
  4. Widget build(BuildContext context) {
  5. return Scaffold(
  6. appBar: AppBar(
  7. title: Text("列表测试"),
  8. ),
  9. body: HomeContent(key: homeKey),
  10. floatingActionButton: FloatingActionButton(
  11. child: Icon(Icons.data_usage),
  12. onPressed: () {
  13. print("${homeKey.currentState.value}");
  14. print("${homeKey.currentState.widget.name}");
  15. print("${homeKey.currentContext}");
  16. },
  17. ),
  18. );
  19. }
  20. }
  21. class HomeContent extends StatefulWidget {
  22. finalString name = "123";
  23. HYHomeContent({Key key}): super(key: key);
  24. @override
  25. _HYHomeContentState createState() => _HYHomeContentState();
  26. }
  27. class _HomeContentState extends State<HYHomeContent> {
  28. finalString value = "abc";
  29. @override
  30. Widget build(BuildContext context) {
  31. return Container();
  32. }
  33. }