本文繼續分析flutter中各類各樣的keymarkdown
@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,類圖以下ide
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被移除ui
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(() {});
}
}
複製代碼
可是咱們想要在外部改變該狀態,這時候就須要使用 GlobalKeythis
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。spa
LocalKey 直接繼承至 Key,它應用於擁有相同父 Element 的小部件進行比較的狀況,也就是上述例子中,有一個多子 Widget 中須要對它的子 widget 進行移動處理,這時候你應該使用Localkey。
Localkey 派生出了許多子類 key:
debug
Valuekey 又派生出了 PageStorageKey : PageStorageKey('value')code
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和valuecdn
使用場景xml
若是您有一個 Todo List 應用程序,它將會記錄你須要完成的事情。咱們假設每一個 Todo 事情都各不相同,而你想要對每一個 Todo 進行滑動刪除操做。
這時候就須要使用 **ValueKey。對象
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 的滾動狀態
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!
/// 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,失去了一致性