Flutter key:LocalKey、GlobalKey

发布于 1 年前 作者 phonegap100 360 次浏览 最后一次编辑是 1 年前 来自 分享

Flutter key:LocalKey、GlobalKey对应教程 https://www.bilibili.com/video/BV1S4411E7LY/?p=45

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

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

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

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

20220808102537

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      color: Colors.blue,
    ),
    Box(
      color: Colors.red,
    ),
    Box(
      color: Colors.orange,
    )
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          
          setState(() {
            list.shuffle();  //打乱list的顺序           
          });          
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  Color color;
  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 SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

运行后我们发现改变list Widget顺序后,Widget颜色会变化,但是每个Widget里面的文本内容并没有变化,为什么会这样呢?当我们List重新排序后Flutter检测到了Widget的顺序变化,所以重新绘制List Widget,但是Flutter 发现List Widget 里面的元素没有变化,所以就没有改变Widget里面的内容。

把List 里面的Box的颜色改成一样,这个时候您重新对list进行排序,就很容易理解了。重新排序后虽然执行了setState,但是代码和以前是一样的,所以Flutter不会重构List Widget里面的内容, 也就是Flutter没法通过Box里面传入的参数来识别Box是否改变。如果要让FLutter能识别到List Widget子元素的改变,就需要给每个Box指定一个key。

   List<Widget> list = [
    Box(
      color: Colors.blue,
    ),
    Box(
      color: Colors.blue,
    ),
    Box(
      color: Colors.blue,
    )
  ];

二、Flutter key:LocalKey、GlobalKey

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

Flutter key子类包含LocalKeyGlobalKey

  • 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
  • 全局键(GlobalKey): GlobalKey、GlobalObjectKey

20220808102537

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

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

2.1 LocalKey改造上面的例子

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      key: const ValueKey(1),
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Box(color: Colors.red)),
      color: Colors.red,
    ),
    Box(
      key:UniqueKey(), //程序自动生成一个key
      color: Colors.orange,
    )
  ];
  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {          
          setState(() {
            list.shuffle();  //打乱list的顺序           
          });          
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  Color color;
  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 SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

2.2 GlobalKey的使用

如果把LocalKey比作局部变量, GlobalKey就类似于全局变量

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

20220808102537

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      key: const ValueKey(1),
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Box(color: Colors.red)),
      color: Colors.red,
    ),
    Box(
      key:UniqueKey(), //程序自动生成一个key
      color: Colors.orange,
    )
  ];
  @override
  Widget build(BuildContext context) {
    print(MediaQuery.of(context).orientation);
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {          
          setState(() {
            list.shuffle();  //打乱list的顺序           
          });          
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      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 {
  Color color;
  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 SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

在前面我们介绍过一个Widget状态的保存主要是通过判断组件的类型或者key值是否一致。LocalKey只在当前的组件树有效,所以把Colum换成了Row的时候Widget的状态就丢失了。为了解决这个问题我们就可以使用GlobalKey。

GlobalKey优化,把LocalKey换成GlobalKey,如下:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [];
  final GlobalKey _key1 = GlobalKey();
  final GlobalKey _key2 = GlobalKey();
  final GlobalKey _key3 = GlobalKey();
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    list = [
      Box(
        key: _key1,
        color: Colors.blue,
      ),
      Box(
        key: _key2,
        color: Colors.red,
      ),
      Box(
        key: _key3, //程序自动生成一个key
        color: Colors.orange,
      )
    ];
  }

  @override
  Widget build(BuildContext context) {
    print(MediaQuery.of(context).orientation);
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //打乱list的顺序
          });
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      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 {
  Color color;
  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 SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

三、 GlobalKey 获取子组件

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

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final GlobalKey _globalKey = GlobalKey();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){    
          //1、获取子组件的状态 调用子组件的属性 
          var state=(_globalKey.currentState as _BoxState);
          setState(() {
            state._count++;
          });

          //2、获取子组件的属性
           var box=(_globalKey.currentWidget as Box);
           print(box.color);     

           //3、获取子组件渲染的属性
          var renderBox= (_globalKey.currentContext!.findRenderObject() as RenderBox);
          print(renderBox.size);    

        },
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      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;
  run(){
    print("run");
  }
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}


四、 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。

回到顶部