Hi π
- Wechat: RyukieW
- π¦ Archive of technical articles
- π making
My personal project | Minesweeper Elic Endless Ladder | Dream of books | Privacy Access Record |
---|---|---|---|
type | The game | financial | tool |
AppStore | Elic | Umemi | Privacy Access Record |
More columns:
The independent development of Lawliet
Lawliet’s iOS garden party
Lawliet’s underlying iOS lab
Lawliet’s iOS Reverse lab
Lawliet’s brush book
Lawliet’s Flutter Lab
The Key role
Let’s create an example like this to see what happens:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePage(), ); }}class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// the key is very important.
List<Widget> items = [
ColorItem('The first'),
ColorItem('Second'),
ColorItem('Third')];@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Key's role '),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: items,
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
setState(() {
items.removeAt(0); }); },),); }}class ColorItem extends StatefulWidget {
final String title;
ColorItem(this.title, {Key? key}) : super(key: key);
@override
_ColorItemState createState() => _ColorItemState();
}
class _ColorItemState extends State<ColorItem> {
final color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100, child: Text(widget.title), color: color, ); }}Copy the code
The runtime found the above phenomenon, and as those of us with iOS development experience, it is easy to imagine that this phenomenon should be related to reuse.
Reuse problem
Here we need to understand that the ColorItem as a StatefulWidget is made up of two parts: Widget and State. State is still in memory after the Widget is removed. So there is the problem of reuse exceptions.
So putting data in widgets shouldn’t be the case according to the theory analyzed here, right? Let’s try:
class ColorItem extends StatefulWidget {
final String title;
// Move to Widget
final color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
ColorItem(this.title, {Key? key}) : super(key: key);
@override
_ColorItemState createState() => _ColorItemState();
}
class _ColorItemState extends State<ColorItem> {
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100, child: Text(widget.title), color: widget.color, ); }}Copy the code
Verification successful! So what are the specific reasons?
Incremental rendering with canUpdate
In Widget implementation, the canUpdate method determines whether a Widget’s Element will be updated, which in turn is directly related to incremental rendering.
Both conditions are satisfied:
- The old and new
Widget
Of the same type - The old and new
Widget
ηKey
The same- if
Key
They are empty, judged by type only, even if they are childrenWidget
Completely different
- if
This is a good explanation of what happened in the first place.
/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// If the widgets have no key (their key is null), then they are considered a
/// match if they have the same type, even if their children are completely
/// different.
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Copy the code
The illustration
Widgets and Elements correspond one to one, and State is in the Element.
- To remove a
Widget
Start to check - The first one
Element
Check the second oneWidget
- call
canUpdate
- The same type
Key
Is empty- return
ture
- And then use
The first Element
updateThe second Widget
- call
- By analogy, the previous anomaly occurs
Since the Key is mentioned here, can the above problem be solved by adding Key?
The use of the Key
Here we add a Key to each ColorItem and put the Color attribute back into State:
.class _HomePageState extends State<HomePage> {
// the key is very important.
List<Widget> items = [
ColorItem(
'The first',
key: ValueKey(1),
),
ColorItem(
'Second',
key: ValueKey(2),
),
ColorItem(
'Third',
key: ValueKey(3),)];@overrideWidget build(BuildContext context) { ... }}class ColorItem extends StatefulWidget {
final Stringtitle; . }class _ColorItemState extends State<ColorItem> {
final color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0); . }Copy the code
Effective!
Key
Key itself is an abstract class with a factory method:
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also:
///
/// * [Widget.key], which discusses how widgets use keys.
@immutable
abstract class Key {
/// Construct a [ValueKey<String>] with the given [String].
///
/// This is the simplest way to create keys.
const factory Key(String value) = ValueKey<String>;
/// Default constructor, used by subclasses.
///
/// Useful so that subclasses can call us, because the [new Key] factory
/// constructor shadows the implicit constructor.
@protected
const Key.empty();
}
Copy the code
- It has two subclasses
LocalKey
- The same
The parent Element
The only
- The same
GlobalKey
- The whole
App
The only
- The whole
The ValueKey we just used is a subclass of LocalKey.
LocalKey
Distinguish which Element to keep and which Element to remove.
/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also:
///
/// * [Widget.key], which discusses how widgets use keys.
abstract class LocalKey extends Key {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const LocalKey() : super.empty();
}
Copy the code
ValueKey
- Take values as arguments (numbers, strings, etc.)
ObjectKey
- Take an object as a parameter
UniqueKey
- Create a Unique identifier
GlobalKey
Unique Key in the entire App. GlobalKey can uniquely identify an element, such as accessing a BuildContext or Widget. For StatefulWidget, GlobalKey also has access to State.
When widgets with GlobalKey are moved to a new location in the Widget tree, their subtrees are re-rendered.
To render a subtree, a Widget must be completed in the tree within the same animation frame, moving from the old position to the new position.
Rerendering an Element that uses GlobalKey is costly because it triggers calls to all the associated State deactivate methods and then rebuilds all the inheritedWidgets that depend on them.
So if you don’t need to achieve the above results, use a different Key.
Pay attention to the point
- Both of them are in the same tree
Widget
You can’t have the sameGlobalKey
. Attempting to do so will raise an exception at run time. GlobalKeys
Not every timebuild
Is recreated. They should be held by a State for a long time.- Such as:
- Every time
build
Create a new oneGlobalKey
Discards andThe old Key
Associated subtree, and isA new Key
Create a new tree. In addition to hurting performance, this operation may have unknown effects on subtrees. - For example, in a subtree
GestureDetector
Will not be able to continue tracking the gesture in progress as it will each timebuild
Is recreated
- Every time
- Such as:
- It would be better to:
- To make a
State
Holding theGlobalKey
And, inBuild
Initialize it outside the method - For example, in
State.initState
δΈ
- To make a
A simple GlobalKey example
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: GlobalKeyDemo(), ); }}class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_GKeyItemState> _gKey = GlobalKey();
GlobalKeyDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Key's role '),
),
body: Center(
child: GKeyItem(key: _gKey,),
),
floatingActionButton: FloatingActionButton(
child: constIcon(Icons.add), onPressed: () { _gKey.currentState? .setState(() { _gKey.currentState? .count +=1; }); },),); }}class GKeyItem extends StatefulWidget {
const GKeyItem({Key? key}) : super(key: key);
@override
_GKeyItemState createState() => _GKeyItemState();
}
class _GKeyItemState extends State<GKeyItem> {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Text('$count')); }}Copy the code