Flutter - Key的原理

作者: Lcr111 | 来源:发表于2021-12-28 16:47 被阅读0次

    前言

    上篇文章我们简单探索了Flutter的渲染原理---Flutter初探渲染原理初探,这篇文章我们继续来看看上篇文章中提到的ElementcanUpdate方法里面oldWidget.key == newWidget.key的,帮我我们更深入的理解Flutter底层的渲染原理。

    Key的种类

    • Key 本身是一个抽象类,又一个工厂构造方法,创建ValueKey
    • 直接子类主要有: LocalKeyGlobalKey
      • 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),不是应该最前面的一块不见了嘛!

    暂未使用Key2
    此时,只剩下一块,颜色和第一块一样,但是文字却是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还是正常展示的。

    Widget和Elemwnt演变过程

    接下来我们将方块换回为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的信息。

    相关文章

      网友评论

        本文标题:Flutter - Key的原理

        本文链接:https://www.haomeiwen.com/subject/quhpqrtx.html