Flutter: 当使用了PageStorageKey后发生了什么?

·  阅读 1386

以ListView为例:

ListViewcontrollerscrollController里有个属性叫keepScrollOffset,当它为true的时候(默认即为true),表示它的滚动位置会被存储。

在其代码的ScrollPosition类里有相关的逻辑:

  /// Called by [beginActivity] to report when an activity has ended.
  ///
  /// This also saves the scroll offset using [saveScrollOffset].
  void didEndScroll() {
    activity.dispatchScrollEndNotification(copyWith(), context.notificationContext);
    if (keepScrollOffset)
      saveScrollOffset();
  }
复制代码

didEndScroll()这个方法在多处地方被调用到,比如ScrollController里的jumpTo()方法。

那么在saveScrollOffset()里用到了PageStorage类的writeState()方法。

 @protected
  void saveScrollOffset() {
    PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels);
  }
复制代码

PageStorage

PageStorage是一个用于保存页面(路由)相关数据的组件,它并不会影响子树的UI外观,其实,PageStorage是一个功能型组件,它拥有一个存储桶(bucket),子树中的Widget可以通过指定不同的PageStorageKey来存储各自的数据或状态。

class PageStorage extends StatelessWidget {
 
  const PageStorage({
    Key key,
    @required this.bucket,
    @required this.child,
  }) : assert(bucket != null),
       super(key: key);

  final Widget child;

  final PageStorageBucket bucket;

  static PageStorageBucket of(BuildContext context) {
    final PageStorage widget = context.findAncestorWidgetOfExactType<PageStorage>();
    return widget?.bucket;
  }

  @override
  Widget build(BuildContext context) => child;
}
复制代码

PageStorage提供了一个静态的of方法,用于访问它的bucket属性。

PageStorageBucket

class PageStorageBucket {
  static bool _maybeAddKey(BuildContext context, List<PageStorageKey<dynamic>> keys) {
    final Widget widget = context.widget;
    final Key key = widget.key;
    if (key is PageStorageKey)
      keys.add(key);
    return widget is! PageStorage;
  }

  List<PageStorageKey<dynamic>> _allKeys(BuildContext context) {
    final List<PageStorageKey<dynamic>> keys = <PageStorageKey<dynamic>>[];
    if (_maybeAddKey(context, keys)) {
      context.visitAncestorElements((Element element) {
        return _maybeAddKey(element, keys);
      });
    }
    return keys;
  }

  _StorageEntryIdentifier _computeIdentifier(BuildContext context) {
    return _StorageEntryIdentifier(_allKeys(context));
  }

  Map<Object, dynamic> _storage;

  void writeState(BuildContext context, dynamic data, { Object identifier }) {
    _storage ??= <Object, dynamic>{};
    if (identifier != null) {
      _storage[identifier] = data;
    } else {
      final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
      if (contextIdentifier.isNotEmpty)
        _storage[contextIdentifier] = data;
    }
  }

  dynamic readState(BuildContext context, { Object identifier }) {
    if (_storage == null)
      return null;
    if (identifier != null)
      return _storage[identifier];
    final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
    return contextIdentifier.isNotEmpty ? _storage[contextIdentifier] : null;
  }
}
复制代码

在前面我们知道saveScrollOffset()调用的就是PageStorageBucket里的writeState()方法。

writeState()方法里调用了_computeIdentifier()方法,_allKeys()方法里主要就是判断该widget的Key是否为PageStorageKey,如果是就加入到列表里,放到_StorageEntryIdentifier类里

_StorageEntryIdentifier

class _StorageEntryIdentifier {
  _StorageEntryIdentifier(this.keys)
    : assert(keys != null);

  final List<PageStorageKey<dynamic>> keys;

  bool get isNotEmpty => keys.isNotEmpty;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is _StorageEntryIdentifier
        && listEquals<PageStorageKey<dynamic>>(other.keys, keys);
  }

  @override
  int get hashCode => hashList(keys);

  @override
  String toString() {
    return 'StorageEntryIdentifier(${keys?.join(":")})';
  }
}
复制代码

使用_StorageEntryIdentifier主要是为了做一些比较处理,即多一层封装。

最后就是把滚动的pixel数据和对应的PageStorageKey做为key写入_storage里。

当要恢复数据的时候,ScrollPosition类里会调用restoreScrollOffset()方法

 @protected
  void restoreScrollOffset() {
    if (pixels == null) {
      final double value = PageStorage.of(context.storageContext)?.readState(context.storageContext) as double;
      if (value != null)
        correctPixels(value);
    }
  }
复制代码

其实就是调用了PageStorageBucketreadState()方法,从之前存储的_storage里拿出数据恢复。

参考

6.6 滚动监听及控制

分类:
阅读
标签:
收藏成功!
已添加到「」, 点击更改