How to use `GlobalKey` to maintain widgets' states when changing parents?

守給你的承諾、 提交于 2019-12-10 08:53:57

问题


In Emily Fortuna's article (and video) she mentions:

GlobalKeys have two uses: they allow widgets to change parents anywhere in your app without losing state, or they can be used to access information about another widget in a completely different part of the widget tree. An example of the first scenario might if you wanted to show the same widget on two different screens, but holding all the same state, you’d want to use a GlobalKey.

Her article includes a gif demo of an app called "Using GlobalKey to ReuseWidget" but does not provide source code (probably because it's too trivial). You can also see a quick video demo here, starting at 8:30 mark: https://youtu.be/kn0EOS-ZiIc?t=510

How do I implement her demo? Where do I define the GlobalKey variable and how/where do I use it? Basically for example, I want to display a counter that counts up every second, and have it on many different screens. Is that something GlobalKey can help me with?


回答1:


The most common use-case of using GlobalKey to move a widget around the tree is when conditionally wrapping a "child" into another widget like so:

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

With such code, you'll quickly notice that if child is stateful, toggling foo will make child lose its state, which is usually unexpected.

To solve this, we'd make our widget stateful, create a GlobalKey, and wrap child into a KeyedSubtree

Here's an example:

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:


Global keys can be used to access the state of a statefull widget from anywhere in the widget tree

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



回答3:


Firstly

When should I use GlobalKey ?

As docs refers ,

GlobalKey is a key that is unique across the entire app.

Global keys uniquely identify elements.

Global keys provide access to other objects that are associated with elements, such as the a BuildContext and, for StatefulWidgets, a State.

Widgets that have global keys reparent their subtrees when they are moved from one location in the tree to another location in the tree. In order to reparent its subtree, a widget must arrive at its new location in the tree in the same animation frame in which it was removed from its old location in the tree.

Global keys are relatively expensive. If you don't need any of the features listed above, consider using a Key, ValueKey, ObjectKey, or UniqueKey instead.

You cannot simultaneously include two widgets in the tree with the same global key. Attempting to do so will assert at runtime.

Secondly

I will consider the following example for deeper understanding

In the next application the main scaffold has 2 main widgets, the parent who own that app state, and the child reflect the app state through their UI. Every times parent widget change value and setState, child widget will get the newest state from parent to update UI and trigger animation to high light the change.

The following GIF shows the final result:

So what we will do is triggering Animation explicitly from parent with GlobalKey

Parent

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");
          });
        },
      ),
//...

Now we can get child state from GlobalKey

Child

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

Feel free to see GlobalKeyExample.



来源:https://stackoverflow.com/questions/56895273/how-to-use-globalkey-to-maintain-widgets-states-when-changing-parents

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!