概要
key是widget、element和semanticsNode的惟一标记,同一parent下的大部份element的key无法多次重复,但在个别情况下能在相同parent下采用完全相同的key,比如说page1和page2都能采用ValueKey(1)。
常见key的UML亲密关系图如上,总体上key分成两类-LocalKey和GlobalKey,这三个key都是tcsh,LocalKey的同时实现Chlorophyta ValueKey、ObjectKey和UniqueKey,GlobalKey同时实现ChlorophytaLabeledGlobalKey和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的,其具体内容的同时实现ChlorophytaValueKey、ObjectKey、UniqueKey。
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相等。
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是否相等的。
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的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。
需要注意的是其并没重写==和hashCode方法,缺省也没被const修饰,这也就使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;}
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,同时实现ChlorophytaValueKey、ObjectKey、UniqueKey;一类是GlobalKey,同时实现ChlorophytaLabeledGlobalKey、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冲突的问题。