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

发布时间: 2024-10-23 来源: 未知 栏目:生活服务 点击:773

GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode方法,也没有const修饰的构造函数,所以肯定能保证其全局唯一性。其和Key类差不多,也有一个工厂构造函数,默认创建的是Lab

简介

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
@immutableabstractclassKey{constfactoryKey(Stringvalue)=ValueKeyString;@();}

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

LocalKey
abstractclassLocalKeyextsKey{constLocalKey():();}

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

ValueKey

classValueKeyTextsLocalKey{constValueKey();finalTvalue;@overridebooloperator==(Objectother){if(!=runtimeType)returnfalse;==value;}@overrideintgethashCode=hashValues(runtimeType,value);

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

ObjectKey

classObjectKeyextsLocalKey{constObjectKey();finalObject?value;@overridebooloperator==(Objectother){if(!=runtimeType)returnfalse;returnotherisObjectKeyidentical(,value);}@overrideintgethashCode=hashValues(runtimeType,identityHashCode(value));

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

UniqueKey

classUniqueKeyextsLocalKey{UniqueKey();@overrideStringtoString()='[#${shortHash(this)}]';}

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

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

const是编译时常量,在编译期,其值就已经确定。背后利用的类似于常量池的概念,被const修饰的对象会保存在常量池中,后面会对其进行复用。如果UniqueKey构造函数添加了const关键词,那么有如下代码vark1=constUniqueKey();vark2=constUniqueKey();此时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的方法。

下面看下其源码:

abstractclassGlobalKeyTextsStateStatefulWidgetextsKey{///这里的debugLabel仅仅为了debug时使用factoryGlobalKey({String?debugLabel})=LabeledGlobalKeyT(debugLabel);///给子类使用的():();Element?get_currentElement=!.buildOwner!._globalKeyRegistry[this];BuildContext?getcurrentContext=_currentElement;Widget?getcurrentWidget=_currentElement?.widget;T?getcurrentState{finalElement?element=_currentElement;if(elementisStatefulElement){finalStatefulElementstatefulElement=element;finalStatestate=;if(stateisT)returnstate;}returnnull;

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

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

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

LabeledGlobalKey

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

classLabeledGlobalKeyTextsStateStatefulWidgetextsGlobalKeyT{//ignore:prefer_const_constructors_in_immutables,neveruseconstforthisclassLabeledGlobalKey(this._debugLabel):();finalString?_debugLabel;}

GlobalObjectKey

classGlobalObjectKeyTextsStateStatefulWidgetextsGlobalKeyT{constGlobalObjectKey():();finalObjectvalue;@overridebooloperator==(Objectother){if(!=runtimeType)returnfalse;returnotherisGlobalObjectKeyTidentical(,value);}@overrideintgethashCode=identityHashCode(value);

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

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

class_MyGlobalObjectKeyextsGlobalObjectKey{const_MyGlobalObjectKey(Objectvalue):super(value);}
总结

Flutter里的key分为两类,一类是LocalKey,实现类有ValueKey、ObjectKey、UniqueKey;一类是GlobalKey,实现类有LabeledGlobalKey、GlobalObjectKey。

Key是所有keys类的基类,其默认实现是String类型的ValueKey。

相同parent下的key是不能一样的,比如不能再同一个page里使用VlaueKey(1),但是不同parent下是可以存在一样的key的,比如在两个界面里都使用ValueKey(1)。

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

UniqueKey只和自己相等,其并没有重写==和hashCode方法,也没有const修饰的构造函数。当调用Element的updateChild方法时,肯定返回false,所以如果你想让widget每次都去创建新的element而不复用oldelement,那么就给此widget使用UniqueKey。

GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode方法,也没有const修饰的构造函数,所以肯定能保证其全局唯一性。

所有的GlobalKey都保存在BuildOwner类中,其内部维护了一个map用来保存GlobalKey与其对应的Element。

GlobalObjectKey是特殊的GlobalKey,内部维护了一个Object属性,并实现了==和hashCode方法,通过判断runtimeType以及Object属性是否一致来判断两个GlobalObjectKey是否相等。

使用GlobalObjectKey时,为了保证GlobalObjectKey的全局唯一性,最佳实践是继承自GlobalObjectKey实现一个private的内部类,可以有效避免多人开发时可能造成的GlobalObjectKey冲突的问题。

上一篇:奇石仙风神韵,如巨蟒出山,思春女神,揽胜在五岳,绝胜在三清 下一篇:河南18个地级市介绍之-三门峡篇
Top