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。
@immutable
abstract class Key {
const factory Key(String value) = ValueKey<String>;
@protected
const Key.empty();
}
Key是所有key的基类,内部实现了一个工厂构造函数,默认创建String类型的ValueKey。内部还是先了一个empty的构造函数,主要是给子类用的。
abstract class LocalKey extends Key {
const LocalKey() : super.empty();
}
LocalKey没有实际作用,主要是用来区分GlobalKey的,其具体的实现类有ValueKey、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是全局唯一的,其默认实现是LabeledGlobalKey,所以每次创建的都是新的GlobalKey。所有的GlobalKey都保存在BuildOwner类中的一个map里,此map的key为GlobalKey,此map的value则为GlobalKey关联的element。
对于GlobalKey,需要知道如下几点:
下面看下其源码:
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天然就是全局唯一的。
这是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开发中三棵树的重要性不言而喻,了解其原理有助于我们开发出性能更优的App,此文主要从源码角度介绍Element树的管理类BuildOwner。
使用Flutter自带的SearchDelegate组件实现搜索界面,通过魔改实现如下效果:搜素建议、搜索结果,支持刷新和加载更多,解决IOS中文输入拼音问题。