banner
MiniKano

MiniKano

This is MiniKano's blog based on Blockchain technology. 新手,请多关照~
github

Flutter Key的使用

我们之前已经使用过很多组件了,像ContainerRow、或者ElevatedButton等 Widget,使用频率还是比较多的
但不知你有没有注意到,他们的构造函数中,都会有一个 Key,作为构造器的属性,它通常作为第一个属性存在。

在 flutter 中,Key 可以标识一个唯一的组件,正如同其名,Key 一半用来做唯一的标识

我们之前没有使用这个 Key,这时候就会有两种情况:

  1. 组件类型不同:比如ContainerSizedBox,此时 Flutter 内部可以通过组件的类型区分,此时无需使用key
  2. 组件类型相同,比如由多个Container组成的数组,此时 Flutter 无法通过组件类型区分多个组件,此时就需要使用Key

默认情况下,如果没有定义 key,flutter 默认并不会自动创建 key,而是使用 widget 的类型和 index 来调用。

我们可以通过一个案例来描述没有 key 造成的问题:

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

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

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [];
  @override
  void initState() {
    super.initState();
    list = [
      const Box(color: Colors.blue),
      const Box(color: Colors.yellow),
      const Box(color: Colors.red),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //shuffle可以随机打乱此列表的元素
          });
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

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.transparent,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
    );
  }
}

以上是一个 StatefulWidget 的例子,通过点击floatingActionButton打乱显示在屏幕上的三个 Box 的顺序,效果如下
(图片)

1[1].gif

可以看到,虽然点击重排按钮,box 表面上改变了顺序,但是里面的 State,也就是数字的显示,并没有随之改变,这是因为 Flutter 识别到这三个 Box 是一样的,只会做 stateless 层面的改变,而 State 中的东西是不会动的,所以才会造成这种现象(说的有点不太好,之后补充)

这时候就需要使用 key 来帮助我们了:

Key 作为唯一标识#

Key 可以分为两大类:GlobalKeyLocalKey,顾名思义,Global 是全局的,Local 是本地的(非全局)

//GlobalKey可以共享给全局使用
final GlobalKey _globalKey1 = GlobalKey();
final GlobalKey _globalKey2 = GlobalKey();
final GlobalKey _globalKey3 = GlobalKey();
//GlobalKey 是非常昂贵的,需要合理使用

//LocalKey
//ValueKey是LocalKey
const Box(color: Colors.blue, key: ValueKey(1)),
//ObjectKey也是LocalKey
const Box(color: Colors.red, key: ObjectKey(Text("key"))),
//UniqueKey也是LocalKey,作为独一无二的存在(生成一个具有唯一性的 hash 码)
Box(color: Colors.red, key: UniqueKey()),
//PageStorageKey可以当前保存页面的状态,例如列表的滚动状态
PageStorageKey()

LocalKeyGlobalKey的区别:

  • LocalKey应用于有相同父节点的比较情况,也就是一个父节点管理多个子节点,子节点最好使用LocalKey
  • GlobalKey能够跨 Widget 访问状态,应用于有多个父节点的比较情况,也就是子节点对应多个父节点,比如下面会说的的横竖屏切换功能,需要在RowColunm中切换,如果只是用LocalKey的话,会丢失State现有的状态。

以上是定义GlobalKeyLocalKey的方式。

现在让我们给上面实例的的 Box 加上 Key:

以下的案例会使用到MediaQuery中的orientation属性查询设备横竖屏状态

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

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

class _MyHomePageState extends State<MyHomePage> {
    //GlobalKey,可以共享给全局,这样在横竖屏切换的时候,切换组件就不会丢失状态了
    final GlobalKey _globalKey1 = GlobalKey();
    final GlobalKey _globalKey2 = GlobalKey();
    final GlobalKey _globalKey3 = GlobalKey();
    List<Widget> list = [];
    @override
    void initState() {
        super.initState();
        list = [
            Box(color: Colors.blue, key: _globalKey1),
            Box(color: Colors.yellow, key: _globalKey2),
            Box(color: Colors.red, key: _globalKey3),
        ];
    }

    //我们使用localKey(ValueKey)的话,会丢失状态
    // List<Widget> list = [
    //   const Box(color: Colors.blue, key: ValueKey(1)),
    //   const Box(color: Colors.yellow, key: ValueKey(2)),
    //   const Box(color: Colors.red, key: ValueKey(3)),
    // ];

    @override
    Widget build(BuildContext context) {
        //竖屏protrait横屏landscape
        print(MediaQuery.of(context).orientation);

        return Scaffold(
            appBar: AppBar(
                title: const Text('Title'),
            ),
            body: Center(
                //横屏横向显示,竖屏竖向显示
                //但是这样更改以后,状态就无法保存了,因为Column和Row的key还有组件本身就是不一样的
                child: MediaQuery.of(context).orientation == Orientation.portrait
                ? Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: list,
                )
                : Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: list,
                ),
            ),
            floatingActionButton: FloatingActionButton(
                onPressed: () {
                    setState(() {
                        list.shuffle(); //shuffle可以随机打乱此列表的元素
                    });
                },
                child: const Icon(Icons.refresh),
            ),
        );
    }
}

效果如下:
1-2[1].gif

注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。

使用 GlobalKey 操作子元素#

GlobalKey 有一个非常重要的特性:可以跨 Widget 访问 State

举个例子,如果想获取子 Widget(Box)内的 State,我们可以这样做

var boxState = _globalKey.currentState as _BoxState;

拿到了boxState之后,就可以直接调用BoxState中的方法、属性了

另外,我们还可以获取子 widget 节点

var boxWidget = _globalKey.currentWidget as Box;

这样可以拿到 box 中的各种属性了,比如背景颜色,宽高,字体大小等

完整演示:

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

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

class _MyHomePageState extends State<MyHomePage> {
  //GlobalKey,可以共享给全局
  final GlobalKey _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Box(color: Colors.blue, key: _globalKey),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          //父组件获取子元素的statewidget里的属性*
          var boxState = _globalKey.currentState as _BoxState;
          setState(() {
            boxState._count++;
          });
          print(boxState._count);
          //调用子Widget的方法
          boxState.run();

          //获取子widget
          var boxWidget = _globalKey.currentWidget as Box;
          print(boxWidget.color); //MaterialColor(primary value: Color(0xff2196f3))

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

          print(renderBox.size); // Size(100.0, 100.0)
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

//子widget
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("我是box的Run方法");
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: Colors.transparent,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
    );
  }
}
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。