Flutter 中的key、LocalKey、GlobalKey
  xx2YH4ad7R0N 2023年11月02日 56 0


key之间的关系

key

参考:https://api.flutter.dev/flutter/widgets/Widget/key.html

控制一个widget该如何替换掉树中的另一个widget:如果两个widget的runtimeType 和 key都相等(==),那么会让旧widget所对应的旧element转而指向新widget(即调用element.update);否则,旧element会被从树中移除,然后根据新widget来生成一个新element,并将该新element插入到树中。

@immutable
abstract class Key {
const factory Key(String value) = ValueKey<String>;

@protected
const Key.empty();
}

默认创建 Key 将会通过工厂方法根据传入的 value 创建一个 ValueKey。

Key 派生出两种不同用途的 Key:LocalKey 和 GlobalKey。

Key的用途

参考:https://api.flutter.dev/flutter/foundation/Key-class.html

大多数时候并不需要使用key。

当需要在一个StatefulWidget集合中进行添加、删除、重排序等操作时,才可能需要使用到 key。

下面的例子来自 Flutter 的官方视频: ​​何时使用密钥 - Flutter小部件 101 第四集​

为了说明在修改 widget 集合的时候为什么需要 key,我编写了一个非常简单的应用程序,其中有两个随机背景色的 widget,点击按钮时它们会交换位置:

Flutter 中的key、LocalKey、GlobalKey_ico

在无状态的版本里,一个​​Row​​中放置两个带有随机背景色的无状态 (stateless) 的​​StatelessColorfulTile​​​,使用继承自​​StatefulWidget​​​的​​PositionedTiles​​​存储这些 tile 的位置。当点击底部的​​FloatingActionButton​​时,可以正确地交换 tile 在列表中的位置:

void main() => runApp(new MaterialApp(home: PositionedTiles()));

class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
StatelessColorfulTile(),
StatelessColorfulTile(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}

swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}

class StatelessColorfulTile extends StatelessWidget {
Color myColor = UniqueColorGenerator.getColor();
@override
Widget build(BuildContext context) {
return Container(
color: myColor, child: Padding(padding: EdgeInsets.all(70.0)));
}
}

但是,当把 tile 替换成有状态 (stateful) 的​​StatefulColorfulTile​​​并将颜色存储在​​State​​中时,点击按钮,界面看上去并没有任何变化。

Flutter 中的key、LocalKey、GlobalKey_flutter_02

List<Widget> tiles = [
StatefulColorfulTile(),
StatefulColorfulTile(),
];

...
class StatefulColorfulTile extends StatefulWidget {
@override
ColorfulTileState createState() => ColorfulTileState();
}

class ColorfulTileState extends State<ColorfulTile> {
Color myColor;

@override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}

@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}

提醒一下,上面显示的代码是有问题的。因为当用户按下“交换”按钮时,色块并没有交换。解决方法是向有状态 (stateful) 的 widgets 添加一个 key 参数。然后,widgets 开始按我们的预期正确交换位置:

Flutter 中的key、LocalKey、GlobalKey_flutter_03

List<Widget> tiles = [
StatefulColorfulTile(key: UniqueKey()), // Keys added here
StatefulColorfulTile(key: UniqueKey()),
];

...
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR

@override
ColorfulTileState createState() => ColorfulTileState();
}

class ColorfulTileState extends State<ColorfulTile> {
Color myColor;

@override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}

@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}

 解决办法2,覆写State的didUpdateWidget方法,当widget的配置发生变化时,手动更新state,例如:

///Called whenever the widget configuration changes.
void didUpdateWidget(TimePickerBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initValue != widget.initValue) {
_currentTime = widget.initValue;
_tempTime = DateTime.fromMillisecondsSinceEpoch(
_currentTime.millisecondsSinceEpoch);
}
}

当子树 (subtree) 中存在有状态 (stateful) 的 widget 时,修改子树时才需要 key 来维护 widget 状态。如果整个 widget subtree 中都是无状态 (stateless) 的 widgets,key 是不需要的。

从技术上讲,这就是在 Flutter 中使用 key 所需要知道的全部内容。但是,如果您想了解这一切的根本原因……

为什么有时候 Key 是需要的

众所周知,Flutter 为每个 widget 构建一个对应的 ​​Element​​​。就像构建 Widget 树一样,Flutter 也同时构建了 ​​Element​​​树。​​ElementTree​​​ 很简单,仅仅保存 widget 的类型信息和对子元素的引用。可以认为 ​​ElementTree​​ 是 Flutter 应用程序的骨架。它显示了应用程序的结构,所有的其他附加信息都可以通过引用原始的 widget 来查找到。

上面示例中的 ​​Row​​​ widget 实际上为它的每个子 widget 保存了一组有序的插槽 (slot)。当交换 ​​Row​​​ 中 Tile widgets 的顺序时,Flutter将遍历​​ElementTree​​,以查看骨架结构是否与之相同。

Flutter 中的key、LocalKey、GlobalKey_ico_04

 从 ​​RowElement​​​ 开始,依次遍历子元素。​​RowElement​​ 检查新 widget 的 类型 (type) 和 key 是否和持有的老 widget 相同,如果相同,则将引用指向新的 widget。在无状态 (stateless) 版本中,widget 并没有 key,所以 Flutter 仅仅检查类型 (type) 是否相同。(如果这看起来信息量太多,请仔细参考上面的动图)。

有状态 (stateful) widget 对应的 ​​Element​​ tree 的结构看起来有些不同。widgets 和 elements 都与无状态版本的时候一样,但多了一个相关联的状态对象 (state object)。有状态 (stateful) widget 的颜色信息是存储在 ​​State​​ 对象中,而不是存储在 widget 本身中。

Flutter 中的key、LocalKey、GlobalKey_ico_05

 当使用有状态的​​Tile​​​,且没有传入 key 的情况下,交换两个 widget 的顺序时,Flutter 会查看​​ElementTree​​​,检查​​RowWidget​​​的类型并更新引用。随后​​TileElement​​会检查相应 widget 的类型 (type) 是否与之相同,并更新对 widget 引用。这里 widget 的类型显然是相同的。同样的事情在第二个 child 上也会做一遍。由于 Flutter 使用​​ElementTree​​和 与之对应的state对象来决定什么内容应该显示在你的设备上,从我们人类的视角看,weidget 并没有正确的进行交换。

Flutter 中的key、LocalKey、GlobalKey_ico_06

 使用有状态​​Tile​​​的修复了该问题版本中,我们将 key 添加到了 widget 中。现在再交换 widget,​​Row​​widgets 像之前版本一样得到匹配,但 Tile Element 的 key 和对应的 Tile Widget 的 key 并不匹配。这将导致 Flutter 从第一个不匹配的元素开始,停用 (deactivate) 这些不匹配的 elements,并将这些 Tile Element 的引用从 Element Tree 中移除。

Flutter 中的key、LocalKey、GlobalKey_flutter_07

 然后,Flutter 会在 ​​Row​​ 的不匹配的 child elements 中查找具有正确 key 的 element。找到匹配项后,更新 element 对 widget 的引用。然后,对第二个孩子做同样的事情。现在 Flutter 将按照我们所期望的显示,按下按钮时,widget 会交换位置并更新它们的颜色。

总而言之,当修改集合中有状态 widget 的顺序或数量时,key 很有用。为了便于说明,此示例中将颜色存储为状态。但是,状态往往比这要隐晦得多。播放动画、显示用户输入的数据、滚动位置都涉及到状态。

Key 应该放在哪里?

简单的说:如果需要给 app 添加 key,应该把 key 添加到需要维护状态的 widget subtree 的顶部

我见过的一个常见错误是:人们认为他们只需要在第一个有状态的 widget 上放一个 key,但“此处危险”。不相信我?为了展示会遇到什么样的麻烦,我用 ​​Padding​​ widget 包裹 colourfulTile widget,但把 key 留在了 tiles 上。

void main() => runApp(new MaterialApp(home: PositionedTiles()));

class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
// Stateful tiles now wrapped in padding (a stateless widget) to increase height
// of widget tree and show why keys are needed at the Padding level.
List<Widget> tiles = [
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(key: UniqueKey()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(key: UniqueKey()),
),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}

swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}

class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);

@override
ColorfulTileState createState() => ColorfulTileState();
}

class ColorfulTileState extends State<ColorfulTile> {
Color myColor;

@override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}

@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}

此时,单击按钮,Tiles 会变成完全不同的随机颜色!

Flutter 中的key、LocalKey、GlobalKey_ico_08

 下图展示了包裹​​Padding​​widget 后,WidgetTree 和 ElementTree 的情况:

Flutter 中的key、LocalKey、GlobalKey_ide_09

当交换子节点的位置时,Flutter 的 element-to-widget 匹配逻辑一次只会检查树的一个层次。下图中将“孙子节点”(子节点的子节点)置灰,方便我们一次只专注于一个层次。​​Padding​​elements 所在的第一层,一切都正确匹配。

Flutter 中的key、LocalKey、GlobalKey_ico_10

 在第二层,Flutter 发现 Tile Element 的 key 和 Tile Widget 的 key 不一致,deactivate 了 Tile Element,并丢弃了这些连接。这个例子中使用的是 LocalKeys,这意味着在将 widget 和 element 做匹配时,Flutter 只在树的特定层级中寻找 key 的匹配关系。

由于在这一层找不到具有该 key 值的 tile element,所以创建了一个新的 Element,并初始化为一个新状态,在本例中,小部件变为了橙色!

Flutter 中的key、LocalKey、GlobalKey_ico_11

 如果在 padding widget 层级添加 key:

void main() => runApp(new MaterialApp(home: PositionedTiles()));

class PositionedTiles extends StatefulWidget {
@override
State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
Padding(
// Place the keys at the *top* of the tree of the items in the collection.
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(),
),
Padding(
key: UniqueKey(),
padding: const EdgeInsets.all(8.0),
child: StatefulColorfulTile(),
),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}

swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}

class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);

@override
ColorfulTileState createState() => ColorfulTileState();
}

class ColorfulTileState extends State<ColorfulTile> {
Color myColor;

@override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}

@override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}

Flutter 正确地更新连接,就像我们在前面的示例中所做的那样。宇宙又恢复了秩序。

Flutter 中的key、LocalKey、GlobalKey_ide_12

 以上关于卡片交互的举例介绍翻译自外网,原文:​​https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d​

中文翻译:

​Flutter: Key! 它们有什么用处 - 知乎​

LocalKey

LocalKey 直接继承至 Key,它应用于拥有相同父 Element 的小部件进行比较的情况,也就是上述例子中,有一个多子 Widget 中需要对它的子 widget 进行移动处理,这时候你应该使用Localkey。

Localkey 派生出了许多子类 key:

  • ValueKey : ValueKey(‘String’)
  • ObjectKey : ObjectKey(Object)
  • UniqueKey : UniqueKey()
  • Valuekey 又派生出了 PageStorageKey : PageStorageKey(‘value’)

GlobalKey

@optionalTypeArgs
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
···
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
static final Set<Element> _debugIllFatedElements = HashSet<Element>();
static final Map<GlobalKey, Element> _debugReservations = <GlobalKey, Element>{};
···
BuildContext get currentContext ···
Widget get currentWidget ···
T get currentState ···

GlobalKey 使用了一个静态常量 Map 来保存它对应的 Element。
你可以通过 GlobalKey 找到持有该GlobalKey的 Widget,State 和 Element。

注意:GlobalKey 是非常昂贵的,需要谨慎使用。

什么时候需要使用 Key

ValueKey

class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);

/// The value to which this key delegates its [operator==]
final T value;

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ValueKey<T>
&& other.value == value;
}


}

源码也解释的比较清楚了 ValueKey继承LocalKey,可以接受任何类型的参数(T),但同一个ValueKey不能出现两次甚至多次,所以要使用Operator函数去鉴别ValueKey是否唯一,也就是ValueKey的值相等。

请思考下面的 To-do list app1 ,它可以根据优先级重新排列 TODO 列表中的条目,并且在完成后将 TODO 项删除。

Flutter 中的key、LocalKey、GlobalKey_ide_13

在这个场景中,假设 TODO 项的文本是不变的、且唯一的,那么,这是一个很好的使用​​ValueKey​​​的场景。其中文本是​​ValueKey​​的“值”。

return TodoItem(
key: ValueKey(todo.task),
todo: todo,
onDismissed: (direction){
_removeTodo(context, todo);
},
);

ObjectKey

class ObjectKey extends LocalKey {
/// Creates a key that uses [identical] on [value] for its [operator==].
const ObjectKey(this.value);

/// The object whose identity is used by this key's [operator==].
final Object value;

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ObjectKey
&& identical(other.value, value);
}

}

ObjectKey和ValueKey的区别再于Identical 和 == 。Identical是判断是否为同一内存(内存中的指针是否相同),类似于java中的 == 判断。

当然Key这个东西简便好,使用ObjectKey的情况下也比较少。当然如果Key比较复杂的话使用ObjectKey也是个不错的选择如果你有一个生日应用,它可以记录某个人的生日,并用列表显示出来,同样的还是需要有一个滑动删除操作。

我们知道人名可能会重复,这时候你无法保证给 Key 的值每次都会不同。但是,当人名和生日组合起来的 Object 将具有唯一性。

这时候你需要使用 ObjectKey!

UniqueKey

class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
///
/// The key cannot be created with a const constructor because that implies
/// that all instantiated keys would be the same instance and therefore not
/// be unique.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();

@override
String toString() => '[#${shortHash(this)}]';
}

源码以看啥也没有 就只剩下一些翻译,那下面我们再来看看这个独一无二的Key怎么理解把。

丢状态

AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: Text("no keyrrths", key: UniqueKey()),
)

每次改变文字时,假如不传uniqueKey,就不会有动画的渐变效果,而如果传了UniqueKey,则会有渐变动画效果。因为不传uniqueKey时,每次都只会认为text的widget发生了变化,只会将text的widget给替换为新的widget,而element还是同一个不会变化,所以会认为UI没有发生变化,因此不会改变;而如果传了uniqueKey时,每次widget比较时都会因为自身的key不一致而被认为是不同的widget,导致会重建element和renderObject,前后两个UI不一致,此时就会发生动画效果。
 

如果组合的 Object 都无法满足唯一性的时候,你想要确保每一个 Key 都具有唯一性。那么,你可以使用 UniqueKey。它将会通过该对象生成一个具有唯一性的 hash 码。

不过这样做,每次 Widget 被构建时都会去重新生成一个新的 UniqueKey,失去了一致性。也就是说你的小部件还是会改变。

PageStorageKey

当你有一个滑动列表,你通过某一个 Item 跳转到了一个新的页面,当你返回之前的列表页面时,你发现滑动的距离回到了顶部。这时候,给 Sliver 一个 PageStorageKey!它将能够保持 Sliver 的滚动状态。

Flutter 中的key、LocalKey、GlobalKey_ide_14

GlobalKey

GlobalKey 能够跨 Widget 访问状态。 在这里我们有一个 Switcher 小部件,它可以通过 changeState 改变它的状态。

class SwitcherScreenState extends State<SwitcherScreen> {
bool isActive = false;

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Switch.adaptive(
value: isActive,
onChanged: (bool currentStatus) {
isActive = currentStatus;
setState(() {});
}),
),
);
}

changeState() {
isActive = !isActive;
setState(() {});
}
}

但是我们想要在外部改变该状态,这时候就需要使用 GlobalKey。

class _ScreenState extends State<Screen> {
final GlobalKey<SwitcherScreenState> key = GlobalKey<SwitcherScreenState>();

@override
Widget build(BuildContext context) {
return Scaffold(
body: SwitcherScreen(
key: key,
),
floatingActionButton: FloatingActionButton(onPressed: () {
key.currentState.changeState();
}),
);
}
}

这里我们通过定义了一个 GlobalKey 并传递给 SwitcherScreen。然后我们便可以通过这个 key 拿到它所绑定的 SwitcherState 并在外部调用 changeState 改变状态了。

通常(但并非总是!),​​GlobalKey​​​ 有点像全局变量。Flutter 中有一种更好的方式来查看状态,即使用 ​​InheritedWidget​​, 或 Redux、BLoC 模式之类的东西。

关于GlobalKey典型使用参考:flutter通过GlobalKey在自定义Widget外部获取其state刷新页面_一叶飘舟的博客

上面是通过GlobalKey获取state,下面我们看一下从GlobalKey获取element。

GlobalKey用于Form表单

登录肯定要有输入用户名和密码的输入框,在​​Flutter​​​中我们只用​​Form​​​表单+​​TextFormField​​​的形式加以实现。现在就来讲讲​​Form​​​和​​TextFormField​​​的简单使用,​​demo​​中登录界面如下:

Flutter 中的key、LocalKey、GlobalKey_ico_15


然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:

Flutter 中的key、LocalKey、GlobalKey_ide_16


上图布局的代码如下所示:

Flutter 中的key、LocalKey、GlobalKey_flutter_17


如上所示首先初始化​​GlobalKey​​​对象。然后将此对象设置为​​Form​​​的key,最后再点击Submit按钮的时候,我们没有直接操作​​TextFormField​​​,而是通过​​_formKey.currentState.validate​​​对输入框​​TextFormField​​​的内容进行非空验证。代码中的​​_formKey.currentState​​​其类型是​​FormState​​:

class Form extends StatefulWidget {
const Form({
Key key,
@required this.child,

}) ;

@override
FormState createState() => FormState();

//调用Form.of(context)也可以获取FormState对象
//详情请看【Flutter之实战InheritedWidget详解】
static FormState of(BuildContext context) {
final _FormScope scope = context.inheritFromWidgetOfExactType(_FormScope);
return scope?._formState;
}

}



GlobalKey获取Element的原理

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
//一个静态的变量map集合
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
}

,从 ​​GlobalKey<T extends State<StatefulWidget>>​​​ 的类结构可以看出,​​GlobalKey​​​主要用来存储状态信息 ​​State<StatefulWidget>​​​,​​State​​​指的是​​StatefulWidget​​​ 的状态类,通过​​StatefulWidget​​​ 的​​createState​​方法创建:

abstract class StatefulWidget extends Widget {
//Key是个options的,可以设置也可以不设置
const StatefulWidget({ Key key }) : super(key: key);
@protected
State createState();
}

上文中为什么通过​​GlobalKey.currentState​​​就可以获取到​​FormState​​呢?二者是怎么关联起来的呢?现在就来一探究竟。

先来看看​​GlobalKey​​​的​​currentState​​方法的具体实现:

T get currentState {
//当前的Element对象
final Element element = _currentElement;
//检测是否是SatefulElement对象
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
//获取StatefulElement对象的State对象
final State state = statefulElement.state;
//如果状态匹配,则返回对应的T
if (state is T)
return state;
}
return null;
}
//_currentElement是一个map集合Map<GlobalKey, Element>
//该集合以GlobalKeyweight对象,其值保存的是Element。
Element get _currentElement => _registry[this];



在​​GlobalKey​​​内部有一个静态的的​​_registry Map​​​集合,该集合以​​GlobalKey​​​为key,以​​Element​​​为value;其提供的​​currentState​​​ 方法就是以​​GlobalKey​​​对象为​​Key​​​获取对应的​​StatefulElement​​​ 对象,然后从​​StatefulElement.state​​​里获取具体的值​​FormState​​​,那么什么时候往_registry 集合里填充数据呢?通过Fultter之Element和Widget对应关系解析​我们知道一个​​Element​​​在创建之后会调用​​mount​​方法:

void mount(Element parent, dynamic newSlot) {
///省略部分代码
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
//将Element对象注册进来
key._register(this);
}

}
//GlobalKey的_register方法。
void _register(Element element) {
_registry[this] = element;
}

可以发现在mount方法将我们创建的​​Element​​​注入到​​GlobalKey​​​的静态map集合中去!所以​​GlobalKey​​的作用就是:*持有当前Widget​Element​对象,因此通过​GlobalKey​对象可以获取到当前​StatefulWidget​​StatefullElement​,在通过​StatefullElement​获取​State​状态对象,从而操控​State​的相关方法。比如​FormState​的validate()方法进行非空校验

事实上我们还可以使用​​Form.of(context)​​​方法也可以获到​​FormState​​​对象,然后调用validate方法完成​​TextFormField​​​的非空校验,其中原理,详细解析见Flutter之实战InheritedWidget详解

GlobalKey的方法

我们可以通过GlobalKey来找到对应的widget,甚至是state等更多相关的东西.

Flutter 中的key、LocalKey、GlobalKey_ico_18

 可以看到,global对象提供了几个方法,我们主要用到的就是前三个,可以通过globalKey来找到对应的BuildContext、State以及Widget.

获取对应的state

比如我们可以通过这个globalKey来找到对应的按钮中的数字,我们打印一下看一看:

Flutter 中的key、LocalKey、GlobalKey_ide_19

 对应的代码:

floatingActionButton: FloatingActionButton(
onPressed: () {
print((_globalKey.currentState as _BoxState).count);
},
child: Icon(Icons.wifi_protected_setup),
)

可以看到,上面我进行了一个类型转换,因为globalKey是任何widget都可以使用的,flutter在运行之前并不知道具体是哪个widget用到了它,所以我们这要转换成对应的类型.

那如果我们想改变里面的值呢,是否可以做到呢?

我们改变一下onPress的代码:

onPressed: () {
final state = _globalKey.currentState as _BoxState;
state.count++;
print(state.count);
state.setState(() {});
}

切记,一定要进行setState操作,来触发Ui的刷新,否则值会修改,但是没有体现在UI上.

更好的写法是

state.setState(() {
state.count++;
});

推荐把setState需要刷新的对象放入这个大括号{}内,因为在实际开发中,并不是每次有对象变化的时候多需要刷新UI的,如果能够按照规范将需要刷新的对象放到setState中,等到需求变更,代码需要修改的时候,setState中是空的时候,我们就知道不需要刷新UI了,就可以删除掉这个setState.

反之,如果是写到外面,当我们把需要刷新的对象的代码删除掉的时候,不知道这个setState到底还有没有用(毕竟实际我们可能是多人开发,也可能自己以前写的代码不记得了),就导致了额外的刷新操作.

当然,其实上述操作也不是推荐的做法,一般都是由自己内部来管理它的状态,如果有多个widget共用的话,则应该进行变量提示操作,把变量写到外层.

获取对应的widget

刚才我们也看到了,除了currentState,它还有一些别的东西.
比如currentWidget,当然我们代码对应的类型也要做改变了.

onPressed: () {
final widget = _globalKey.currentWidget as Box;
print(widget.color);
}

比如这里,因为widget是Box,所以类型转换为Box. 通过currentWidget,我们就可以得到对应的widget中的属性,例如此处的color.

获取对应的context

通过currentContext,可以获得对应的context,context其实指的就是element.

在第二章中我们介绍到了element tree,并没有详细说element.Element的作用还是比较多的.

比如我想知道对应组件的尺寸,位置.这其实都是难以获得的信息.

final renderBox = _globalKey.currentContext!.findRenderObject() as RenderBox;

此处,我们通过currentContext!.findRenderObject找到了对应的renderObject,RenderObject的类型还是比较多的.因为我们此处的Box刚好是RenderBox, 所以将类型转换为RenderBox.

 

Flutter 中的key、LocalKey、GlobalKey_flutter_20

 可以看到renderBox有一个对应的size方法,通过它就可以拿到对应widget的尺寸.
如果想拿到它的位置信息的话,可以通过

renderBox.localToGlobal(Offset.zero);

来拿到对应widget的左上角距离屏幕左上角的坐标信息.
看一下分别打印的信息:

I/flutter ( 2158): Size(100.0, 100.0)      //对应widget的尺寸
I/flutter ( 2158): Offset(100.0, 80.0) //对应widget的坐标

小结

最后做一个简单的总结

本章主要介绍了globalKey的两种用法,两种用法都是先声明一个globalKey,再把这个globalKey传给想要放到的widget里面.

第一种用法,保证有了globalKey的widget可以随便改变它在Wdiget Tree中的位置,而它的状态不会丢失.

第二种用法,相当于通过这个key将widget暴露了出来,借助它就可以利用类似于getElementById之类的方式去找到它各种各样的东西:

  • currentContext: 可以找到包括renderBox在内的各种element有关的东西
  • currentWidget: 可以得到widget的属性
  • currentState: 可以得到state里面的变量
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  4koL3J55wyKx   2023年11月13日   37   0   0 icogitCentOS
  b1UHV4WKBb2S   2023年11月13日   40   0   0 ide抗锯齿
  b1UHV4WKBb2S   2023年11月13日   34   0   0 裁剪ideflutter
  b1UHV4WKBb2S   2023年11月13日   27   0   0 flutterDart
  zSWNgACtCQuP   2023年11月13日   32   0   0 ide
xx2YH4ad7R0N