Flutter中的Key(二)

本文繼續分析flutter中各類各樣的keymarkdown

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,類圖以下ide

image.png

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被移除ui

  • 好比在不一樣的屏幕上使用相同的Widget,可是保持相同的State,則須要使用GlobalKeys

1_JIPjn-gM6OIG_TfPJvtuVA.gif

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

LocalKey 直接繼承至 Key,它應用於擁有相同父 Element 的小部件進行比較的狀況,也就是上述例子中,有一個多子 Widget 中須要對它的子 widget 進行移動處理這時候你應該使用Localkey。
Localkey 派生出了許多子類 key:
debug

  • ValueKey : ValueKey('String')
  • ObjectKey : ObjectKey(Object)
  • UniqueKey : UniqueKey()

    Valuekey 又派生出了 PageStorageKey : PageStorageKey('value')code

  • 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和valuecdn

使用場景xml

若是您有一個 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,失去了一致性

相關文章
相關標籤/搜索