问题
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