preface
When I was getting started with some of the Flutter applications, I always came across the GlobalKey class. I only knew from the syntax of the code that it must be used to bind something, but what is the key? Why bind? What happens if you don’t bind? Why do some Widget implementations require binding and others do not? I don’t know any of that.
So while there was time on The Dragon Boat Festival, I read the official document carefully and found that the official document was very detailed (provided that you have a certain understanding of the control tree of the Flutter). Most of the questions above were answered. Unfortunately, the official document was explained by YouTube, which is not easy to read and read when you forget. So I put together this blog post to reinforce my impression.
- The official doc: API. Flutter. Dev/flutter/fou…
- If you don’t know about the control tree of Flutter: juejin.cn/post/684490…
- This article demo code: github.com/mimajiushi/…
- It is highly recommended that you first understand the three trees of Flutter, otherwise some of the logic may be confusing to you.
What is the key
The key is used to control whether widgets in the Weidget tree are replaced.
If the runtimeType and key attributes of the two Weidget are equal (compare with ==), then the element that originally pointed to the old Weidge will have its pointer to the new widget (via the element.update method). If not, the old element is removed from the tree, the new element is rebuilt based on the current new widget, and added to the tree to point to the new widget.
Let’s take a look at the code: Element.update
@mustCallSuper
void update(covariant Widget newWidget) {
// This code is hot when hot reloading, so we try to
// only call _AssertionError._evaluateAssertion once.
assert(_lifecycleState == _ElementLifecycle.active && widget ! =null&& newWidget ! =null&& newWidget ! = widget && depth ! =null
&& Widget.canUpdate(widget, newWidget));
// This Element was told to update and we can now release all the global key
// reservations of forgotten children. We cannot do this earlier because the
// forgotten children still represent global key duplications if the element
// never updates (the forgotten children are not removed from the tree
// until the call to update happens)
assert(() {
_debugForgottenChildrenWithGlobalKey.forEach(_debugRemoveGlobalKeyReservation);
_debugForgottenChildrenWithGlobalKey.clear();
return true; } ()); _widget = newWidget; }Copy the code
Go to widget.canupdate above
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Copy the code
As you can see, the logic is pretty much the same as in the document, except that the Widget itself does not call widget. canUpdate. Element calls this method, which means that it is the Element’s responsibility to decide whether or not the Widget can be updated
But talk is cheap show me the code, so how can we prove that this theory is true? A code demo is shown below.
When do we use keys
Let’s build a demo
For an example that does not require a key, the code logic is that when the order of the elements in the collection changes, the control changes as follows:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: PositionedTiles()));
}
class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles;
@override
void initState() {
super.initState();
tiles = [
// StatefulColorfulTile(),
// StatefulColorfulTile(),
// StatefulColorfulTile(key: UniqueKey()),
// StatefulColorfulTile(key: UniqueKey()),
StatelessColorfulTile(),
StatelessColorfulTile(),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied),
// child: Icon(Icons.sentiment_very_dissatisfied),
onPressed: swapTiles,
),
);
}
void swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0)); }); }}// ignore: must_be_immutable
class StatelessColorfulTile extends StatelessWidget {
Color color = ColorUtil.randomColor();
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Padding(padding: EdgeInsets.all(70.0))); }}class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => StatefulColorfulTileState();
}
class StatefulColorfulTileState extends State<StatefulColorfulTile> {
Color color;
@override
void initState() {
super.initState();
color = ColorUtil.randomColor();
}
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Padding(padding: EdgeInsets.all(70.0))); }}class ColorUtil {
static Color randomColor() {
var red = Random.secure().nextInt(255);
var greed = Random.secure().nextInt(255);
var blue = Random.secure().nextInt(255);
return Color.fromARGB(255, red, greed, blue); }}Copy the code
The above code looks like this, as you can see usingStatelessColorfulTile
, the two color blocks after clicking the button can be exchanged successfully:
Next, let’s change the code to cook like this and restart:
@override
void initState(a) {
super.initState();
tiles = [
StatefulColorfulTile(),
StatefulColorfulTile(),
// StatefulColorfulTile(key: UniqueKey()),
// StatefulColorfulTile(key: UniqueKey()),
// StatelessColorfulTile(),
// StatelessColorfulTile(),
];
}
Copy the code
An amazing thing happened. After clicking the button, the color blocks no longer exchange:
With StatefulColorfulTile, how do you make the color blocks swap when you click the button again? I’m sure you’ve already thought of this: set the key property, which means change the code to look like this:
@override
void initState(a) {
super.initState();
tiles = [
// StatefulColorfulTile(),
// StatefulColorfulTile(),
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
// StatelessColorfulTile(),
// StatelessColorfulTile(),
];
}
Copy the code
The effect is as follows:
The next step is to illustrate the causes of these effects.
whyStatelessColorfulTile
Can exchange
Let’s take a look at what happens when StatelessColorfulTile is swapped.
3. After the exchange:
When the code calls PositionedTiles. SetState to exchange two widgets, the flutter will compare each node in the Widget tree and Element tree from top to bottom if the runtimeType and key of the node match (there is no key here, So only compare runtimeType), then the Element is still considered valid and available for reuse, and it can be reused directly by simply changing the Element’s pointer.
Because the StatefulColorfulTile color information is stored in the widget:
class StatelessColorfulTile extends StatelessWidget { Color color = ColorUtil.randomColor(); . (a)}Copy the code
So even though the color block Widget does not need to be updated because of the widget. canUpdate return and has no internal callback to setState logic, the exchange will be successful.
The Element saves the Widget, which describes the control style, and the RenderObject, which is the layout rendering control. When the Element only updates the Widget, the next rendering will have the effect of the new Widget.
whyStatefulColorfulTile
You have to add a key to swap
One important difference between StatefulColorfulTile and StatelessColorfulTile is that the Color attribute is placed differently.
The Color property of the StatelessColorfulTile is placed directly under the Widget:
class StatelessColorfulTile extends StatelessWidget { Color color = ColorUtil.randomColor(); . (a)}Copy the code
whileStatefulColorfulTile
The Color attribute is placed under State:
As an additional basic knowledge, the State attribute is eventually managed by Element, so you can follow up with a few snippet of the source code.
Take a look first atStateFulWidget
Abstract method:
With the Flutter tree concept in mind, we should understand that each Widget will eventually create its Element, which is created by the createElement method above, which calls the StatefulElement constructor.
Then follow upStatefulElement()
Function, we can see it clearlyStatefulElement
Management of theState
And do all sorts of things with it:
With the premise that the State attribute is ultimately managed by Element, it’s easy to move on.
Let’s seeStatefulColorfulTile
What happens when we call the exchange function without the keyExchange before:
3. After the exchange:
First, after the Widget is updated, the flutter compares the Widget based on the runtimeType and key to determine whether the Element needs to be rebuilt. The key is empty and only the runtimeType is compared. The comparison result must be equal. So Element is reused directly.
When the StatefulColorfulTile is rerendered, the Color property is not retrieved from the Widget object itself, but from the Element’s State. The Element has not changed at all, so the Color is not changed. The colors are constant, so visually the two colors are not exchanged.
And then after the key, before the swap:
After swapping, find both sideskey
No, so try to matchElement
Is there another id that’s the same? If there is, rearrange itElement
For the samekey
Matching:
If there is no key on the Element side that matches the new Widget, the old Element will fail and an Element will be rebuilt based on the new Widget.
After rebuild, the Element has changed, and you visually see the two color blocks switch positions:
Those familiar with the principle of three trees know that the Element is equivalent to the actual control on the device. Since the position of the Element changes, the control on the final screen will also change, and the final exchange after re-rendering is visually the two color blocks exchanged.
That’s the end of this blog post. This is just a brief introduction to the use of keys in widgets, but there are many different ways to implement keys, and they are used in different ways. This is not relevant to the purpose of this post, so I will leave it to you.