Flutter 进阶之路重点记录

Flutter - Key的原理

2021-12-28  本文已影响0人  Lcr111

前言

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

Key的种类

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的信息。

上一篇 下一篇

猜你喜欢

热点阅读