以ListView为例:
ListView的controller即scrollController里有个属性叫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);
}
}
其实就是调用了PageStorageBucket的readState()方法,从之前存储的_storage里拿出数据恢复。