Flutter 中的key、LocalKey、GlobalKey

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,点击按钮时它们会交换位置:

在无状态的版本里,一个Row中放置两个带有随机背景色的无状态 (stateless) 的StatelessColorfulTile,使用继承自StatefulWidgetPositionedTiles存储这些 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中时,点击按钮,界面看上去并没有任何变化。

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 开始按我们的预期正确交换位置:

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,以查看骨架结构是否与之相同。

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

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

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

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

 然后,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 会变成完全不同的随机颜色!

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

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

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

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

 如果在 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 正确地更新连接,就像我们在前面的示例中所做的那样。宇宙又恢复了秩序。

 以上关于卡片交互的举例介绍翻译自外网,原文: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 项删除。

在这个场景中,假设 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 的滚动状态。

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刷新页面_一叶飘舟的博客-CSDN博客

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

GlobalKey用于Form表单

登录肯定要有输入用户名和密码的输入框,在Flutter中我们只用Form表单+TextFormField的形式加以实现。现在就来讲讲FormTextFormField的简单使用,demo中登录界面如下:
在这里插入图片描述
然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:
在这里插入图片描述
上图布局的代码如下所示:
在这里插入图片描述
如上所示首先初始化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呢?二者是怎么关联起来的呢?现在就来一探究竟。

先来看看GlobalKeycurrentState方法的具体实现:

  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的作用就是:*持有当前WidgetElement对象,因此通过GlobalKey对象可以获取到当前StatefulWidgetStatefullElement,在通过StatefullElement获取State状态对象,从而操控State的相关方法。比如FormState的validate()方法进行非空校验

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

GlobalKey的方法

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

image.png

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

获取对应的state

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

asda.gif

 对应的代码:

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.

 image.png

 可以看到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里面的变量
     

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/126563450