Flutter中各个Key的作用以及GlobalKey的工作原理
前言
在Flutter中,如果想要移除当前的Widget的Element,创建新的Element,重新走一次State的生命周期,我们只需要更改不一样Key就行了, 如Key(‘A’),改为Key(‘B’).不了解这个原理的,去看看我的这篇文章<<20分钟源码角度彻底了解Flutter的Widget, Element, RenderObject的关系>>
Flutter中各个Key
默认的Key
如果我们默认使用带字符串入参的Key, Key(‘abc’), 实质上是使用了ValueKey这个Key。如果使用Key.empty(),才不会使用ValueKey。 带字符参数,是为了方便使用,而又不用声明为ValueKey,可谓一举两得。
abstract class Key {
const factory Key(String value) = ValueKey<String>;
const Key.empty();
}
LocalKey
为了避免默认使用ValueKey问题, 里面拓展了一个LocalKey这个类,他默认是用Key.empty().后面其他Key都会继承它
abstract class LocalKey extends Key {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const LocalKey() : super.empty();
}
UniqueKey
这个UniqueKey,每次new UniqueKey()都可以保证Key是唯一的,每次刷新后,相当于每次都会移除Element并插入新的Element,达到每次整个生命周期都会走一次。如下,每次KeyWidget被build的时候,TestWidget的initState都会被执行一次,并且之前的State也会被先dispose
class KeyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Material(
child: TestWidget(key: UniqueKey(),)
);
}
}
class TestWidget extends StatefulWidget {
const TestWidget({super.key});
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
String title = 'title';
Widget build(BuildContext context) {
return Text(
title,
);
}
}
class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
///
/// The key cannot be created with a const constructor because that implies
/// that all instantiated keys would be the same instance and therefore not
/// be unique.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();
String toString() => '[#${shortHash(this)}]';
}
String shortHash(Object? object) {
return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
}
ValueKey
ValueKey是我们使用到最多的一种之一。Key默认是使用了ValueKey的String类型入参 。ValueKey 可以支持各种类型的值作为入参值,如下
const TestWidget(
key: ValueKey(1),
),
const TestWidget(
key: ValueKey(true),
),
const TestWidget(
key: ValueKey("3"),
),
它的原理是从写了等号操作符
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
/// The value to which this key delegates its [operator==]
final T value;
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ValueKey<T>
&& other.value == value;
}
int get hashCode => Object.hash(runtimeType, value);
String toString() {
final String valueString = T == String ? "<'$value'>" : '<$value>';
// The crazy on the next line is a workaround for
// https://github.com/dart-lang/sdk/issues/33297
if (runtimeType == _TypeLiteral<ValueKey<T>>().type) {
return '[$valueString]';
}
return '[$T $valueString]';
}
}
class _TypeLiteral<T> {
Type get type => T;
}
ObjectKey
通过对比持有的对象是否不同,来达到Key是否一致。
class Data{
}
final data1 = Data();
TestWidget(
key: ObjectKey(data1),
),
GlobalKey
GlobalKey这个Key是比较特殊的,它的作用和上面的作用完全相反。通过使用GlobalKey的widget,他的Element会从树上移除,并且将Element放到全局的BuildOwner这个类里面进行复用。
在日常使用中,通过使用GlobalKey,可以获取到当前RenderBox的数据,还有State里面的数据
class KeyWidget extends StatelessWidget {
GlobalKey<_TestWidgetState> testWidgetGlobalKey =
GlobalKey<_TestWidgetState>();
Widget build(BuildContext context) {
return Material(
child: Stack(
children: [
TestWidget(
key: testWidgetGlobalKey,
),
InkWell(onTap: () {
print(testWidgetGlobalKey.currentState?.title);
final RenderBox renderBox = testWidgetGlobalKey.currentContext!.findRenderObject()! as RenderBox;
print(renderBox.size.width);
}, child: const Text('click')),
],
),
);
}
}
class TestWidget extends StatefulWidget {
const TestWidget({super.key});
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
String title = 'title';
Widget build(BuildContext context) {
return Text(
title,
);
}
}
让我们看看GlobalKey的原理是什么
GlobalKey原理
当我们对Element进行挂载的时候, 如果当前的Widget的key是GlobalKey,就会被BuildOwner保存到_globalKeyRegistry.
class BuildOwner{
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
void _registerGlobalKey(GlobalKey key, Element element) {
//...
_globalKeyRegistry[key] = element;
}
}
abstract class Element extends DiagnosticableTree implements BuildContext {
void mount(Element? parent, Object? newSlot) {
//如果是key是GlobalKey,会被保存到owner,也就相当于将当前Element保存在全局
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
}
}
因此, GlobalKey可以到WidgetsBinding.instance.buildOwner!._globalKeyRegistry直接获取到对应的Element,从而可以获取到对应的State和RenderObject.
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
/// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
/// debugging.
///
/// The label is purely for debugging and not used for comparing the identity
/// of the key.
factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);
/// Creates a global key without a label.
///
/// Used by subclasses because the factory constructor shadows the implicit
/// constructor.
const GlobalKey.constructor() : super.empty();
Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
/// The build context in which the widget with this key builds.
///
/// The current context is null if there is no widget in the tree that matches
/// this global key.
BuildContext? get currentContext => _currentElement;
/// The widget in the tree that currently has this global key.
///
/// The current widget is null if there is no widget in the tree that matches
/// this global key.
Widget? get currentWidget => _currentElement?.widget;
/// The [State] for the widget in the tree that currently has this global key.
///
/// The current state is null if (1) there is no widget in the tree that
/// matches this global key, (2) that widget is not a [StatefulWidget], or the
/// associated [State] object is not a subtype of `T`.
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;
}
}
是不是很赞?如果这篇文章对你有帮助,请关注🙏,点赞👍,收藏😋三连哦