本文继续分析flutter中各种各样的key
key的种类
@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,类图如下
GlobalKey
- GlobalKey唯一标识Elements,它提供了与Element相关联的访问,如BuildContext、State(对于StatefulWidget)
- 不要在两个Widget中使用相同的GlobalKey
- Global keys 是很昂贵的,如果你不需要访问BuildContext、Element、State这些的话,请尽量使用[Key], [ValueKey], [ObjectKey] 或者 [UniqueKey]
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
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
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
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
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
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
/// 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,失去了一致性