更改父母时如何使用“ GlobalKey”维护小部件的状态?

use*_*613 4 flutter

在Emily Fortuna的文章(和视频)中,她提到:

GlobalKeys有两个用途:它们允许小部件在您应用程序的任何位置更改父级而不丢失状态,或者可以用于访问小部件树的完全不同部分中有关另一个小部件的信息。第一种情况的示例可能是,如果您想在两个不同的屏幕上显示相同的小部件,但又保持所有相同的状态,则需要使用GlobalKey。

她的文章包括名为“使用GlobalKey到ReuseWidget”的应用程序的gif演示,但未提供源代码(可能是因为它太琐碎了)。您还可以在此处从8:30标记开始观看快速视频演示:https : //youtu.be/kn0EOS-ZiIc?t=510

如何实施她的演示?我在哪里定义GlobalKey变量以及如何/在哪里使用它?举例来说,基本上,我想显示一个每秒计数的计数器,并将其放在许多不同的屏幕上。那是GlobalKey可以帮助我的吗?

use*_*613 19

我不建议将 GlobalKey 用于此任务。

您应该传递数据,而不是小部件,而不是小部件状态。例如,如果您想在演示中使用aSwitch和 a Sliderlike,最好只传递这两个小部件的实际booleandouble后面。对于更复杂的数据,你应该看看ProviderInheritedWidget还是一样。

自该视频发布以来,情况发生了变化。Saed 的回答(我奖励了 50 点赏金)可能是视频中的做法,但它在最近的 Flutter 版本中不再有效。基本上现在还没有使用 GlobalKey 轻松实现演示的好方法

但...

如果你能保证这两个小部件永远不会同时出现在屏幕上,或者更准确地说,它们永远不会同时插入到同一帧的小部件树中,那么你可以尝试使用GlobalKey相同的小部件在布局的不同部分。

请注意,这是一个非常严格的限制。例如,当滑动到另一个屏幕时,通常会出现两个屏幕同时渲染的过渡动画。那不行。所以对于这个演示,我插入了一个“空白页”以防止在滑动时出现这种情况。

全局键演示

如何:

所以,如果你想要相同的小部件,出现在非常不同的屏幕上(希望彼此远离),你可以使用 aGlobalKey来做到这一点,基本上只需要 3 行代码。

首先,声明一个可以从两个屏幕访问的变量:

final _key = GlobalKey();
Run Code Online (Sandbox Code Playgroud)

然后,在您的小部件中,有一个构造函数,它接受一个键并将其传递给父类:

Foo(key) : super(key: key);
Run Code Online (Sandbox Code Playgroud)

最后,每当您使用小部件时,将相同的键变量传递给它:

return Container(
  color: Colors.green[100],
  child: Foo(_key),
);
Run Code Online (Sandbox Code Playgroud)

完整来源:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatelessWidget {
  final _key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Global Key Demo")),
      body: PageView.builder(
        itemCount: 3,
        itemBuilder: (context, index) {
          switch (index) {
            case 0:
              return Container(
                color: Colors.green[100],
                child: Foo(_key),
              );
              break;
            case 1:
              return Container(
                color: Colors.blue[100],
                child: Text("Blank Page"),
              );
              break;
            case 2:
              return Container(
                color: Colors.red[100],
                child: Foo(_key),
              );
              break;
            default:
              throw "404";
          }
        },
      ),
    );
  }
}

class Foo extends StatefulWidget {
  @override
  _FooState createState() => _FooState();

  Foo(key) : super(key: key);
}

class _FooState extends State<Foo> {
  bool _switchValue = false;
  double _sliderValue = 0.5;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Switch(
          value: _switchValue,
          onChanged: (v) {
            setState(() => _switchValue = v);
          },
        ),
        Slider(
          value: _sliderValue,
          onChanged: (v) {
            setState(() => _sliderValue = v);
          },
        )
      ],
    );
  }
}
Run Code Online (Sandbox Code Playgroud)


Sae*_*bil 17

更新:这是一种解决状态管理的旧方法,不再推荐,请参阅我对此答案的评论,并在下面 查看user1032613 的答案

全局键可用于从小部件树中的任何位置访问有状态小部件的状态

在此处输入图片说明

import 'package:flutter/material.dart';

main() {
  runApp(MaterialApp(
    theme: ThemeData(
      primarySwatch: Colors.indigo,
    ),
    home: App(),
  ));
}

class App extends StatefulWidget {
  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  GlobalKey<_CounterState> _counterState;

  @override
  void initState() {
    super.initState();
    _counterState = GlobalKey();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
        children: <Widget>[
          Counter(
            key: _counterState,
          ),
        ],
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.navigate_next),
        onPressed: () {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return Page1(_counterState);
            }),
          );
        },
      ),
    );
  }
}

class Counter extends StatefulWidget {
  const Counter({
    Key key,
  }) : super(key: key);

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count;

  @override
  void initState() {
    super.initState();
    count = 0;
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            setState(() {
              count++;
            });
          },
        ),
        Text(count.toString()),
      ],
    );
  }
}
class Page1 extends StatefulWidget {
  final GlobalKey<_CounterState> counterKey;
  Page1( this.counterKey);
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Row(
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.add),
              onPressed: () {
                setState(() {
                  widget.counterKey.currentState.count++;
                  print(widget.counterKey.currentState.count);
                });
              },
            ),
            Text(
              widget.counterKey.currentState.count.toString(),
              style: TextStyle(fontSize: 50),
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)


Rém*_*let 6

用于GlobalKey在树上移动小部件的最常见用例是有条件地将“子级”包装到另一个小部件中时,如下所示:

Widget build(context) {
  if (foo) {
    return Foo(child: child);
  }
  return child;
}
Run Code Online (Sandbox Code Playgroud)

使用这样的代码,您会很快注意到,如果child是有状态的,则切换foo将使其child失去状态,这通常是意外的。

为了解决这个问题,我们将使小部件成为有状态的,创建一个GlobalKey,然后包装child成一个KeyedSubtree

这是一个例子:

class Example extends StatefulWidget {
  const Example({Key key, this.foo, this.child}) : super(key: key);

  final Widget child;
  final bool foo;

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    final child = KeyedSubtree(key: key, child: widget.child);

    if (widget.foo) {
      return Foo(child: child);
    }
    return child;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么Example是一个StatefulWidget。如果'key'成员被设置为final,它不是一个无状态的widget吗? (3认同)