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

时间:2019-07-05 00:23:32

标签: flutter

在艾米丽·福图纳(Emily Fortuna)的文章(和视频)中,她提到:

  

GlobalKeys有两个用途:它们允许小部件更改父级   在应用中的任何位置而不会丢失状态,或者它们可以用来   在完全不同的部分中访问有关另一个小部件的信息   小部件树的名称。如果您使用第一个方案的示例,   想要在两个不同的屏幕上显示相同的小部件,但是按住   都处于相同状态,则需要使用GlobalKey。

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

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

4 个答案:

答案 0 :(得分:6)

我不建议为此任务使用GlobalKey。

您应该传递数据,而不是小部件,而不是小部件状态。例如,如果您想要演示中的SwitchSlider,最好在这两个小部件后面传递实际的booleandouble。对于更复杂的数据,您应该查看ProviderInheritedWidget等。

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

但是...

如果可以保证,这两个小部件将从不同时显示在屏幕上,或更准确地说,它们将永远不会同时插入同一帧的小部件树中,然后您可以尝试使用GlobalKey在布局的不同部分使用相同的小部件。

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

Global key demo

方法:

因此,如果您希望使用相同的小部件,使其出现在截然不同的屏幕上(希望彼此之间相距很远),则可以使用GlobalKey来完成,基本上只需三行代码。 >

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

final _key = GlobalKey();

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

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

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

return Container(
  color: Colors.green[100],
  child: Foo(_key),
);

完整来源:

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);
          },
        )
      ],
    );
  }
}

答案 1 :(得分:5)

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

Widget build(context) {
  if (foo) {
    return Foo(child: child);
  }
  return child;
}

使用这样的代码,您会很快注意到,如果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;
  }
}

答案 2 :(得分:0)

首先

何时应使用 GlobalKey

正如docs所指,

GlobalKey是整个应用程序中唯一的键。

Global keys唯一标识元素。

Global keys提供对与元素关联的其他对象的访问,例如BuildContext,对于StatefulWidgets,则为State

具有全局键的窗口小部件将子树从树中的一个位置移动到树中的另一个位置时,它们的子树成为父树。为了重设其子树,小部件必须在与从树中的旧位置删除的同一动画帧中到达树中的新位置。

全局密钥相对昂贵。如果不需要上面列出的任何功能,请考虑使用密钥,ValueKeyObjectKeyUniqueKey

您不能在树中同时包含具有相同全局密钥的两个小部件。尝试这样做将在运行时声明。

第二

我将考虑以下示例以加深理解

在下一个应用程序中,主支架具有2个主要小部件,父级拥有该应用程序状态,子级通过其UI反映应用程序状态。每次父小部件更改值和setState时,子小部件都会从父级获取最新状态以更新UI并触发动画以突出显示更改。

以下GIF显示了最终结果:

GlobalKeyUseCase

所以我们要做的是使用GlobalKey从父级显式触发动画

父母

class _MainWidgetState extends State<MainWidget> {
  int counter = 0;
  final GlobalKey<AnimatedTextState> animatedStateKey = GlobalKey<AnimatedTextState>();
//...
Widget build(BuildContext context) {
//...      
            child: AnimatedText(
              key: animatedStateKey,
              text: "Number $counter ",
            )
//...
//...
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            ++counter;
            animatedStateKey.currentState.updateTextWithAnimation("Number $counter");
          });
        },
      ),
//...

现在我们可以从GlobalKey获取子状态

孩子

//...
  void updateTextWithAnimation(String text) {
    setState(() {
      this.text = text;
    });
    _controller.reset();
    _controller.forward();
  }
//...

随时查看GlobalKeyExample

答案 3 :(得分:0)

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

enter image description here

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),
            ),
          ],
        ),
      ),
    );
  }
}