简介
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
@immutableabstract 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抵触的问题。
作者:京东物流 沈亮堂
内容起源:京东云开发者社区