Flutter Key详解

我们平时一定接触过很多的 Widget,比如 Container、Row、Column 等,它们在我们绘制界面的过程 中挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同 的参数,它们通常在参数列表的第一个,那就是 Key。

在Flutter中,Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。

没有 Key 会发生什么奇怪现象

如下面例: 定义了一个StatefulWidget的Box,点击Box的时候可以改变Box里面的数字,当我们重新 对Box排序的时候Flutter就无法识别到Box的变化了, 这是什么原因呢?

这段代码list.shuffle()打乱数组 重新排序 但是数字没有变化 为什么? 因为没有key

import 'package:flutter/material.dart';

import 'tools/pageViewKeepAlive.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  List<Widget> list = [
    const Box(
      color: Colors.red,
    ),
    const Box(
      color: Colors.blue,
    ),
    const Box(
      color: Colors.green,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            list.shuffle(); // 打乱数组
          });
        },
        child: const Icon(Icons.refresh),
      ),
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: list,
      )),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({super.key, required this.color});

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 100,
        height: 100,
        color: Colors.red,
        child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: const TextStyle(fontSize: 20),
          ),
        ));
  }
}

Flutter key:LocalKey、GlobalKey、UniqueKey

Flutter key子类包含LocalKey 和GlobalKey 。

  1. 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
  2. 全局键(GlobalKey): GlobalKey、GlobalObjectKey

key

ValueKey (值key)把一个值作为key ,UniqueKey(唯一key)程序生成唯一的Key,当我们不知道 如何指定ValueKey的时候就可以使用UniqueKey,ObjectKey(对象key)把一个对象实例作为key。

GlobalKey(全局key),GlobalObjectKey(全局Objec key,和ObjectKey有点类似)

LocalKey改造上面的例子

import 'package:flutter/material.dart';

import 'tools/pageViewKeepAlive.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  List<Widget> list = [
    const Box(
      key: ValueKey('1'),
      color: Colors.red,
    ),
    const Box(
      key: ValueKey('2'),
      color: Colors.blue,
    ),
    const Box(
      key: ValueKey('3'),
      color: Colors.green,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            list.shuffle(); // 打乱数组
          });
        },
        child: const Icon(Icons.refresh),
      ),
      body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: list,
          )
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 100,
        height: 100,
        color: Colors.red,
        child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: const TextStyle(fontSize: 20),
          ),
        ));
  }
}

GlobalKey的使用

GlobalKey是一个全局key,它可以跨组件获取State,它的使用方法如下:

import 'package:flutter/material.dart';

import 'tools/pageViewKeepAlive.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  List<Widget> list = [];
  final GlobalKey _globalKey1 = GlobalKey();
  final GlobalKey _globalKey2 = GlobalKey();
  final GlobalKey _globalKey3 = GlobalKey();


  @override
  void initState() {
    super.initState();
    list = [
      Box(
        key: _globalKey1,
        color: Colors.red,
      ),
      Box(
        key: _globalKey2,
        color: Colors.blue,
      ),
      Box(
        key: _globalKey3,
        color: Colors.green,
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            list.shuffle(); // 打乱数组
          });
        },
        child: const Icon(Icons.refresh),
      ),
      body: Center(
          child: MediaQuery.of(context).orientation == Orientation.portrait
              ? Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: list,
                )
              : Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: list,
                )
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 100,
        height: 100,
        color: Colors.red,
        child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: const TextStyle(fontSize: 20),
          ),
        ));
  }
}

GlobalKey 获取子组件

globalKey.currentState 可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget可以获 取子组件的属性,_globalKey.currentContext!.findRenderObject()可以获取渲染的属性。

import 'package:flutter/material.dart';

import 'tools/pageViewKeepAlive.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  // List<Widget> list = [];
  final GlobalKey _globalKey = GlobalKey();
  // final GlobalKey _globalKey2 = GlobalKey();
  // final GlobalKey _globalKey3 = GlobalKey();


  @override
  void initState() {
    super.initState();
    // list = [
    //   Box(
    //     key: _globalKey1,
    //     color: Colors.red,
    //   ),
    //   Box(
    //     key: _globalKey2,
    //     color: Colors.blue,
    //   ),
    //   Box(
    //     key: _globalKey3,
    //     color: Colors.green,
    //   ),
    // ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),

      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 获取子组件的属性
          var boxState = _globalKey.currentState as _BoxState;
          setState(() {
            boxState._count++;
            // 调用子组件的方法
          });
          boxState.run();
          // 获取子组件的Widget
          var box = _globalKey.currentWidget as Box;
          print(box.color);

          // 获取子组件的渲染对象
          var boxRender = _globalKey.currentContext!.findRenderObject() as RenderBox;
          print(boxRender);


        },
        child: const Icon(Icons.add),
      ),
      body:  Center(
          child: Box(
            key: _globalKey,
            color: Colors.red,
          ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  void run(){
    print('run');
  }
  @override
  Widget build(BuildContext context) {
    return Container(
        width: 100,
        height: 100,
        color: Colors.red,
        child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: const TextStyle(fontSize: 20),
          ),
        ));
  }
}

Widget Tree、Element Tree 和 RenderObject Tree

Flutter应用是由是Widget Tree、Element Tree 和 RenderObject Tree组成

Widget可以理解成一个类,Element可以理解成Widget的实例,Widget与Element的关系可以是一对 多,一份配置可以创造多个Element实例

属性 描述
Widget Widget就是一个类, 是Element 的配置信息。与Element的关系可以是一对多,一份配置可以创造多个Element实例
Element Widget 的实例化,内部持有Widget和RenderObject。
RenderObject 负责渲染绘制。

默认情况下面,当Flutter同一个 Widget的大小,顺序变化的时候,FLutter不会改变Widget的state。

下面使用了LocalKey,当屏幕状态改变的时候把 Colum换成了Row,Box的状态就会丢失。

results matching ""

    No results matching ""