Take ListView as an example:

The ListView controller, the scrollController, has a property called keepScrollOffset. When it is true (the default is true), its scroll position is stored.

There is logic in the code’s ScrollPosition class:

  /// 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();
  }
Copy the code

The didEndScroll() method is called in multiple places, like the jumpTo() method in ScrollController.

So saveScrollOffset() uses the PageStorage class writeState() method.

@protected void saveScrollOffset() { PageStorage.of(context.storageContext)? .writeState(context.storageContext, pixels); }Copy the code

PageStorage

PageStorage is a component used to save page (route) related data, it does not affect the UI appearance of the subtree, in fact, PageStorage is a functional component, it has a bucket (bucket), Widgets in a subtree can store their own data or state by specifying different PagestorageKeys.

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; }Copy the code

PageStorage provides a static of method to access its bucket properties.

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; }}Copy the code

We saw earlier that saveScrollOffset() calls the writeState() method in PageStorageBucket.

The _computeIdentifier() method is called in the writeState() method, and the _allKeys() method is used to determine whether the widget’s Key is PageStorageKey. If so, add it to the list. Put it in the _StorageEntryIdentifier class

_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(":")})'; }}Copy the code

The main purpose of using _StorageEntryIdentifier is to do some comparison processing, that is, one more layer of encapsulation.

Finally, the scrolling pixel data and the corresponding PageStorageKey are written to _storage as keys.

When restoring data, the ScrollPosition class calls the restoreScrollOffset() method

@protected void restoreScrollOffset() { if (pixels == null) { final double value = PageStorage.of(context.storageContext)? .readState(context.storageContext) as double; if (value ! = null) correctPixels(value); }}Copy the code

The readState() method of PageStorageBucket is called to retrieve the data from _storage.

reference

6.6 Scroll monitoring and control