flutter PageStorage
在给一个widget加key的时候, copilot给我补了一个PageStorageKey
. 这东西我还是第一次见, 搜了一下, 发现它用于切换页面时保存页面的状态.
好东西, 之前想要保存组件状态, 我通常是将其存在父组件或是一个全局的StateController
, 因为太麻烦了, 很多时候懒得写, 用户体验低点就低点吧.
这么好用的东西当然要立即添加到我的项目里
final _pages = [
const HomePage(
key: PageStorageKey('home'),
),
const FavoritesPage(
key: PageStorageKey('favorites'),
),
const ExplorePage(
key: PageStorageKey('explore'),
),
const CategoriesPage(
key: PageStorageKey('categories'),
),
];
然而没有这么简单, HomePage
和FavoritesPage
工作正常, 成功保存了滚动状态. 而ExplorePage
和CategoriesPage
是非常复杂的页面, 它们都是TabBarView
, 并且每个Tab都是一个滚动页面. 当切换到这个页面时, TabBar
被移动到了超出最大值的位置, 然后滚动到最后一个Tab. 猜测是当前的Tab页面的滚动状态被记录, 并且当页面重建时应用在了TabBar
上. 查看文档, 发现PageStorageBucket.readState
里注释有这样的话using the specified identifier or an identifier computed from the given context
. 也就是说滚动页面是调用此方法读取先前保存的数据, 标识符的计算方法应该是从当前组件到PageStorage
组件路径上所有的PageStorageKey合并结果. 那么给每个Tab页面都加一个PageStorageKey
应该就没问题了. 这样做之后, 我的TabBar
的状态却没有被保留, 看来还是要自己写一些代码.
给TabBar加一个状态保留
首先在state内加上这一行
PageStorageBucket get bucket => PageStorage.of(context);
当index改变时记录状态
void onTabChanged() {
final int i = _controller.index;
if (i == previousIndex) {
return;
}
updateScrollOffset(i);
previousIndex = i;
bucket.writeState(context, i); // 记录状态
}
组件重建时恢复状态
@override
void didChangeDependencies() {
_controller = widget.controller ?? DefaultTabController.of(context);
_controller.animation!.addListener(onTabChanged);
initPainter();
super.didChangeDependencies();
var prevIndex = bucket.readState(context) as int?;
if (prevIndex != null && prevIndex != _controller.index) {
_controller.index = prevIndex;
}
}
ScrollController
的恢复状态行为需要被禁用
var scrollController = ScrollController(
keepScrollOffset: false
);
这样就完成了TabBar的状态保存
保存更多状态
在一个需要网络请求加载数据的组件里, 这样写
Map<String, dynamic> get state => {
'maxPage': _maxPage,
'data': _data,
'page': _page,
'error': _error,
'loading': _loading,
'nextUrl': _nextUrl,
};
void restoreState(Map<String, dynamic>? state) {
if (state == null) {
return;
}
_maxPage = state['maxPage'];
_data.clear();
_data.addAll(state['data']);
_page = state['page'];
_error = state['error'];
_loading.clear();
_loading.addAll(state['loading']);
_nextUrl = state['nextUrl'];
}
void storeState() {
PageStorage.of(context).writeState(context, state);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
restoreState(PageStorage.of(context).readState(context));
}
当网络请求完成时, 调用一下storeState
即可
需要注意的是, 如过该组件构建了一个可滚动组件, 要给他加一个PageStorageKey
, 否则滚动状态的储存和数据状态的储存会共用一个identifier
return SmoothCustomScrollView(
key: const PageStorageKey('scroll'),
controller: controller,
slivers: [],
);