title: “Flutter中的Key(二)”
comments: true
share: true
toc: true
categories:

  • Flutter
    tags:

Flutter中的Key(二)

本文继续分析flutter中各种各样的key

key的种类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@immutable
abstract class Key {
/// Construct a [ValueKey<String>] with the given [String].
///
/// This is the simplest way to create keys.
const factory Key(String value) = ValueKey<String>;

/// Default constructor, used by subclasses.
///
/// Useful so that subclasses can call us, because the [new Key] factory
/// constructor shadows the implicit constructor.
@protected
const Key.empty();
}

默认的key会通过工厂方法传入的key 创建一个ValueKey,Key派生出两种用途不同的的key:LocalKey和GlobalKey,类图如下

image.png

GlobalKey

  • GlobalKey唯一标识Elements,它提供了与Element相关联的访问,如BuildContext、State(对于StatefulWidget)
  • 不要在两个Widget中使用相同的GlobalKey
  • Global keys 是很昂贵的,如果你不需要访问BuildContext、Element、State这些的话,请尽量使用[Key], [ValueKey], [ObjectKey] 或者 [UniqueKey]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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();

static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};

void _register(Element element) {
_registry[this] = element;
}

void _unregister(Element element) {
if (_registry[this] == element)
_registry.remove(this);
}

Element get _currentElement => _registry[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 currentContext => _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;
}
}

GlobalKey 使用了一个静态常量 Map 来保存它对应的 Element。你可以通过 GlobalKey 找到持有该GlobalKey的 Widget,State 和 Element。它们允许widget在应用中的任何位置更改父级而不会丢失State,内部有几个属性,意味着你可以访问到currentContext、currentContext和currentState(如果是StatefullWidget),这是通过过在Element被mount到树上的时候调用_register,如果是类型是GlobalKey,那么Element就会加入到一个静态Map里,unmount的时候调用_unregister被移除

  • 比如在不同的屏幕上使用相同的Widget,但是保持相同的State,则需要使用GlobalKeys

1_JIPjn-gM6OIG_TfPJvtuVA.gif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SwitcherScreenState extends State<SwitcherScreen> {
bool isActive = false;

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Switch.adaptive(
value: isActive,
onChanged: (bool currentStatus) {
isActive = currentStatus;
setState(() {});
}),
),
);
}

changeState() {
isActive = !isActive;
setState(() {});
}
}

但是我们想要在外部改变该状态,这时候就需要使用 GlobalKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class _ScreenState extends State<Screen> {
final GlobalKey<SwitcherScreenState> key = GlobalKey<SwitcherScreenState>();

@override
Widget build(BuildContext context) {
return Scaffold(
body: SwitcherScreen(
key: key,
),
floatingActionButton: FloatingActionButton(onPressed: () {
key.currentState.changeState();
}),
);
}
}


通常,GlobalKey看起来有点像全局变量。也有其他更好的办法去查找状态,比如 InheritedWidget、Redux 或 Block Pattern。

Localkey

LocalKey 直接继承至 Key,它应用于拥有相同父 Element 的小部件进行比较的情况,也就是上述例子中,有一个多子 Widget 中需要对它的子 widget 进行移动处理这时候你应该使用Localkey。
Localkey 派生出了许多子类 key:

  • ValueKey : ValueKey(‘String’)
  • ObjectKey : ObjectKey(Object)
  • UniqueKey : UniqueKey()

   Valuekey 又派生出了 PageStorageKey : PageStorageKey(‘value‘)

  • ValueKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final ValueKey<T> typedOther = other;
return value == typedOther.value;
}
@override
int get hashCode => hashValues(runtimeType, value);
}

派生自LocalKey,接受一个泛型类,重写了==方法和hash方法,需要同时校验runtimeType和value

使用场景

如果您有一个 Todo List 应用程序,它将会记录你需要完成的事情。我们假设每个 Todo 事情都各不相同,而你想要对每个 Todo 进行滑动删除操作。
这时候就需要使用 **ValueKey。

  • PageStoreKey
1
2
3
4
class PageStorageKey<T> extends ValueKey<T> {
/// Creates a [ValueKey] that defines where [PageStorage] values will be saved.
const PageStorageKey(T value) : super(value);
}

PageStorageKey是继承自ValueKey,传入一个value,它是用于保存Scrollable的偏移
Scrollable(实际是ScrollPositions)使用PageStorage保存它们的滚动偏移,每次滚动完成时,存储的滚动信息都会更新。
PageStorage用于保存和恢复比widget生命周期更长的值,这些值存储在per-route Map中,它的key由widget及它的祖先的PageStorageKeys定义

使用场景

当你有一个滑动列表,你通过某一个 Item 跳转到了一个新的页面,当你返回之前的列表页面时,你发现滑动的距离回到了顶部。这时候,给 Sliver 一个 PageStorageKey 它将能够保持 Sliver 的滚动状态

  • ObjectKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ObjectKey extends LocalKey {
/// Creates a key that uses [identical] on [value] for its [operator==].
const ObjectKey(this.value);
/// The object whose identity is used by this key's [operator==].
final Object value;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final ObjectKey typedOther = other;
return identical(value, typedOther.value);
}
@override
int get hashCode => hashValues(runtimeType, identityHashCode(value));
}

ObjectKey也是集成自LocalKey,而且构造方法也是需要传入一个value,但是这个value是Object类型的,也就是说可以传任意类型,identical 方法返回的是两个Object的hashCode是否相等,当runtimeType跟value.hashCode都相等的情况下,ObjectKey才会被认为相等,它跟ValueKey的区别在于它比较的是value的引用,而ValueKey是直接比较值

使用场景

如果你有一个生日应用,它可以记录某个人的生日,并用列表显示出来,同样的还是需要有一个滑动删除操作。
我们知道人名可能会重复,这时候你无法保证给 Key 的值每次都会不同。但是,当人名和生日组合起来的 Object 将具有唯一性。
这时候你需要使用 ObjectKey

  • UniqueKey
1
2
3
4
5
6
/// A key that is only equal to itself.
class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();
}

如果组合的 Object 都无法满足唯一性的时候,你想要确保每一个 Key 都具有唯一性。那么,你可以使用 UniqueKey。它将会通过该对象生成一个具有唯一性的 hash 码。
不过这样做,每次 Widget 被构建时都会去重新生成一个新的 UniqueKey,失去了一致性

0%