Flutter三棵树系列之详解各种Key

这篇具有很好参考价值的文章主要介绍了Flutter三棵树系列之详解各种Key。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Flutter三棵树系列之详解各种Key

简介

key是widget、element和semanticsNode的唯一标识,同一个parent下的所有element的key不能重复,但是在特定条件下可以在不同parent下使用相同的key,比如page1和page2都可以使用ValueKey(1)

常用key的UML关系图如上,整体上key分为两大类-LocalKey和GlobalKey,这两个key都是抽象类,LocalKey的实现类有 ValueKey、ObjectKey和UniqueKey,GlobalKey实现类有LabeledGlobalKey和GlobalObjectKey。

Key

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


  @protected
  const Key.empty();
}

Key是所有key的基类,内部实现了一个工厂构造函数,默认创建String类型的ValueKey。内部还是先了一个empty的构造函数,主要是给子类用的。

LocalKey

abstract class LocalKey extends Key {
  const LocalKey() : super.empty();
}

LocalKey没有实际作用,主要是用来区分GlobalKey的,其具体的实现类有ValueKey、ObjectKey、UniqueKey。

ValueKey

class ValueKey<T> extends LocalKey {
  const ValueKey(this.value);


  final T value;


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


  @override
  int get hashCode => hashValues(runtimeType, value);

内部维护了泛型类型的value属性,并实现了==和hashCode方法。只要两个ValueKey的value属性相等,那么就认为两个Key相等。

ObjectKey

class ObjectKey extends LocalKey {
  const ObjectKey(this.value);


  final Object? value;


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


  @override
  int get hashCode => hashValues(runtimeType, identityHashCode(value));

ObjectKey是继承自LocalKey的,可以将其理解成泛型类型为Object的ValueKey。但是注意两者的方法是不一样的,ValueKey根据value的值是否相等来判断ValueKey是否相等(相当于java的equals方法),而ObjectKey根据indentical方法(判断两个引用是否指向同一个对象,相当于java的操作符)来判断两个ObjectKey是否相等的。

UniqueKey

class UniqueKey extends LocalKey {
  UniqueKey();


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

唯一的key,其并未重写==和hashCode方法,所有它只和自己相等。注意看UniqueKey的构造函数,并没有像上面介绍的几个key的构造函数一样使用const修饰,这样做的目的是为了进一步保证UniqueKey的唯一性。这样在调用Element的updateChild方法时,此方法内部调用的Widget.canUpdate方法就会始终返回false,从而每次都会创建新的child element。

所以,如果你想让某一个widget每一次都不复用old element,而是去重新创建新的element,那么就给他添加UniqueKey吧。

const是编译时常量,在编译期,其值就已经确定。背后利用的类似于常量池的概念,被const修饰的对象会保存在常量池中,后面会对其进行复用。如果UniqueKey构造函数添加了const关键词,那么有如下代码 var k1 = const UniqueKey(); var k2 = const UniqueKey(); 此时k1==k2永远为true,就不能保证其唯一性。

GlobalKey

GlobalKey是全局唯一的,其默认实现是LabeledGlobalKey,所以每次创建的都是新的GlobalKey。所有的GlobalKey都保存在BuildOwner类中的一个map里,此map的key为GlobalKey,此map的value则为GlobalKey关联的element。

对于GlobalKey,需要知道如下几点:

  • 当拥有GlobalKey的widget从tree的一个位置上移动到另一个位置时,需要reparent它的子树。为了reparent它的子树,必须在一个动画帧里完成从旧位置移动到新位置的操作。
  • 上面说到的reparent操作是昂贵的,因为要调用所有相关联的State和所有子节点的deactive方法,并且所有依赖InheritedWidget的widget去重建。
  • 不要在build方法里创建GlobalKey,性能肯定不好,而且也容易出现意想不到的异常,比如子树里的GestureDetector可能会由于每次build时重新创建GlobalKey而无法继续追踪手势事件。
  • GlobalKey提供了访问其关联的Element和State的方法。

下面看下其源码:

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  ///这里的debugLabel仅仅为了debug时使用
  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);


  ///给子类使用的
  const GlobalKey.constructor() : super.empty();


  Element? get _currentElement => WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];


  BuildContext? get currentContext => _currentElement;


  Widget? get currentWidget => _currentElement?.widget;


  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T)
        return state;
    }
    return null;
 

其和Key类差不多,也有一个工厂构造函数,默认创建的是LabeledGlobalKey,其构造函数的debugLabel仅仅是为了debug时使用,并不会用来标识element。

如何获取其关联的element?从源码来看,其直接访问的是BuildOwner里用来保存GlobalKey和Element对应关系的map。获取到了其关联的element,那么就能获取到其对应的widget以及state,详细的可以看上面的源码。

需要注意的是其并没有重写==和hashCode方法,构造函数也没有被const修饰,这也就使LabeledGlobalKey天然就是全局唯一的。

LabeledGlobalKey

这是GlobalKey的默认实现,内部仅有一个debugLabel属性,其他的也没啥。

class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
  // ignore: prefer_const_constructors_in_immutables , never use const for this class
  LabeledGlobalKey(this._debugLabel) : super.constructor();


  final String? _debugLabel;
}

GlobalObjectKey

class GlobalObjectKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
  const GlobalObjectKey(this.value) : super.constructor();


  final Object value;


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


  @override
  int get hashCode => identityHashCode(value);

特殊的GlobalKey,重写了==和hashCode方法,内部维护了一个Object对象,通过判断此Object是否指向同一块内存地址来判断两个GlobalObjectKey是否相等。

GlobalKey被要求全局唯一,其默认实现LabeledGloalKey因为其并没有重写==和hashCode方法,也不支持const构造函数,所以天然是全局唯一的。但是GlobalObjectKey不然,如果有两个或者多个地方使用到了拥有同一个Object的GlobalObjectKey,那么就不能保证其全局唯一性,造成程序出错。此时,可以继承GlobalObjectKey,实现一个private的内部类,比如:

class _MyGlobalObjectKey extends GlobalObjectKey {
  const _MyGlobalObjectKey(Object value) : super(value);
}

总结

  • Flutter里的key分为两类,一类是LocalKey,实现类有ValueKey、ObjectKey、UniqueKey;一类是GlobalKey,实现类有LabeledGlobalKey、GlobalObjectKey。
  • Key是所有keys类的基类,其默认实现是String类型的ValueKey。
  • 相同parent下的key是不能一样的,比如不能再同一个page里使用VlaueKey(1),但是不同parent下是可以存在一样的key的,比如在两个界面里都使用ValueKey(1)。
  • UniqueKey只和自己相等,其并没有重写==和hashCode方法,也没有const修饰的构造函数。当调用Element的updateChild方法时,Widget.canUpdate肯定返回false,所以如果你想让widget每次都去创建新的element而不复用old element,那么就给此widget使用UniqueKey。
  • GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode方法,也没有const修饰的构造函数,所以肯定能保证其全局唯一性。
  • 所有的GlobalKey都保存在BuildOwner类中,其内部维护了一个map用来保存GlobalKey与其对应的Element。
  • GlobalObjectKey是特殊的GlobalKey,内部维护了一个Object属性,并实现了==和hashCode方法,通过判断runtimeType以及Object属性是否一致来判断两个GlobalObjectKey是否相等。
  • 使用GlobalObjectKey时,为了保证GlobalObjectKey的全局唯一性,最佳实践是继承自GlobalObjectKey实现一个private的内部类,可以有效避免多人开发时可能造成的GlobalObjectKey冲突的问题。

作者:京东物流 沈明亮

内容来源:京东云开发者社区文章来源地址https://www.toymoban.com/news/detail-457089.html

到了这里,关于Flutter三棵树系列之详解各种Key的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Flutter】【基础】CustomPaint 绘画功能,绘制各种图形(二)

    1.canvas.drawColor 绘制背景颜色 2.canvas.drawCircle 绘制圆 3.canvas.drawRect绘四边形 4.canvas.drawRRect 绘制圆角矩形 其他的方法可以自行尝试,参数大同小异 5.canvas.drawRRect 嵌套绘制 6.canvas.drawOval 绘制椭圆形 7.canvas.drawPath 绘制路径 8.canvas.drawArc 绘制弧形 9.canvas.drawShadow绘制阴影 10.canvas.

    2024年02月13日
    浏览(38)
  • Flutter 引入包import的各种含义,及常用命名规范

    import \\\'dart:xxx\\\'; 引入Dart标准库 import \\\'xxx/xxx.dart\\\'; 引入相对路径的Dart文件 import \\\'package:xxx/xxx.dart\\\'; 引入Pub仓库pub.dev(或者pub.flutter-io.cn)中的第三方库 import \\\'package:project/xxx/xxx.dart\\\'; 引入自定义的dart文件 import \\\'xxx\\\' show compute1, compute2 只导入compute1,compute2 import \\\'xxx\\\' hide compute3 除了

    2024年02月12日
    浏览(64)
  • 面试系列-各种组件问一下(二)

    欢迎大家对答案进行补充、勘误,可以私信或者文章底部评论 1、spark宽窄依赖区分     宽依赖:是指一个父RDD分区对应多个子RDD的分区,比如map、filter等算子     窄依赖:是指多个父RDD分区对应一个子RDD分区,比如groupByKey,reduceByKey等算子,会产生shuffler操作     区分:可以

    2024年02月01日
    浏览(33)
  • Flutter系列文章-Flutter进阶

    在前两篇文章中,我们已经了解了Flutter的基础知识,包括Flutter的设计理念、框架结构、Widget系统、基础Widgets以及布局。在本文中,我们将进一步探讨Flutter的高级主题,包括处理用户交互、创建动画、访问网络数据等等。为了更好地理解这些概念,我们将通过实际的示例代码

    2024年02月16日
    浏览(55)
  • Flutter系列文章-Flutter基础

    Flutter是Google推出的一种新的移动应用开发框架,允许开发者使用一套代码库同时开发Android和iOS应用。它的设计理念、框架结构、以及对Widget的使用,都让开发者能更有效率地创建高质量的应用。 Flutter的设计理念是“一切皆为Widget”。这意味着不论是按钮、字体、颜色、布局

    2024年02月16日
    浏览(45)
  • Flutter系列文章-Flutter 插件开发

    在本篇文章中,我们将学习如何开发 Flutter 插件,实现 Flutter 与原生平台的交互。我们将详细介绍插件的开发过程,包括如何创建插件项目、实现方法通信、处理异步任务等。最后,我们还将演示如何将插件打包并发布到 Flutter 社区。 在 Flutter 项目中,你可能需要与原生平台

    2024年02月11日
    浏览(39)
  • Flutter系列文章-Flutter应用优化

    当涉及到优化 Flutter 应用时,考虑性能、UI 渲染和内存管理是至关重要的。在本篇文章中,我们将通过实例深入讨论这些主题,展示如何通过优化技巧改进你的 Flutter 应用。 1. 使用 const 构造函数 在构建小部件时,尽可能使用 const 构造函数来创建静态小部件。这将避免在每次

    2024年02月11日
    浏览(40)
  • Flutter系列文章-Flutter进阶2

    这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由、状态管理、异步处理、HTTP请求和Rest API,以及数据持久化。让我们逐个介绍这些主题。 在 Flutter 中,导航和路由是构建多页面应用的关键概念。导航是指从一个页面(或称为路由)切换到另一个页面的过程。每

    2024年02月15日
    浏览(43)
  • Flutter系列文章-Flutter UI进阶

    在本篇文章中,我们将深入学习 Flutter UI 的进阶技巧,涵盖了布局原理、动画实现、自定义绘图和效果、以及 Material 和 Cupertino 组件库的使用。通过实例演示,你将更加了解如何创建复杂、令人印象深刻的用户界面。 Row 和 Column 是常用的布局组件,但灵活地使用它们可以带来

    2024年02月13日
    浏览(36)
  • Flutter系列:Flutter常见问答(可用于面试)

    Flutter系列 Flutter常见问答 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/135604801 【简介】:本文总结了数十道 Flutter/Dart 中常见的问题,所有的问题提供了答案提示。 1. 什么是 Flutter? Flutter 是由

    2024年01月17日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包