本文繼續分析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,失去了一致性