1、緣起
如果我不提 restorationId
屬性,可能絕大多數人都不知道他是幹嘛的,甚至連它的存在都不知道。即便它在元件作為參中出現的頻率挺高。下面先看一下有該屬性的一些元件,比如:在 ListView
中有 restorationId
的屬性。
在 GridView
中也有 restorationId
的屬性。
PageView
元件中也有 restorationId
的屬性。
在 SingleChildScrollView
元件中也有 restorationId
的屬性。
在 NestedScrollView
元件中也有 restorationId
的屬性。
在 CustomScrollView
元件中也有 restorationId
的屬性。
在 TextField
元件中也有一個 restorationId
的屬性。
除此之外還有很多其他的元件有 restorationId
屬性,可以感覺到只要和 滑動沾點邊的,好像都有 restorationId
的屬性。說了這麼多,下面我們先來看一下這個屬性的作用。
2. restorationId 屬性的作用
下面以 ListView
為例,介紹一下 restorationId
屬性的作用。如下兩個動圖分別是 無 restorationId
和 有 restorationId
的效果。可見 restorationId
的作用是在某種情況下,保持滑動的偏移量
。
無 restorationId | 有 restorationId |
---|---|
class ListViewDemo extends StatelessWidget {
const ListViewDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
restorationId: 'toly', //tag1
children: List.generate(25, (index) => ItemBox(index: index,)).toList());
}
}
class ItemBox extends StatelessWidget {
final int index;
const ItemBox({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1 / window.devicePixelRatio,
))),
height: 56,
child: Text(
'第 $index 個',
style: TextStyle(fontSize: 20),
),
);
}
}
複製程式碼
另外,說明一點,為例方便演示恢復的觸發,需要在 開發者選項
中勾選 不保留活動
,其作用是使用者離開後會殺掉 Activity
。比如點選Home鍵、選單欄切換介面時,Activity
並不為立即銷燬,而是系統視情況而定。開啟這個選項可以避免測試的不確定因素。注意:測試後,一定要關掉
。
在 Android 中,是通過 onSaveInstanceState
進行實現的。 當系統"未經你許可"
時銷燬了你的 Activity 時,比如橫豎屏切換、點選 Home 鍵、導航選單欄切換。系統會提供一個機會讓通過 onSaveInstanceState
回撥來你儲存臨時狀態資料,這樣可以保證下次使用者進入時產生違和感
。
另外有一點非常重要,這裡並不是
將狀態永久儲存,當使用者主動
退出應用,是不會觸發 onSaveInstanceState
的。也就是說,如果你一個 ListView
設定了 restorationId
,使用者滑了一下後,按返回鍵退出,那麼再進來時不會還原到原位置。注意,要是其生效需要在 MaterialApp
中為 restorationScopeId
指定任意字串。
3.如何通過 restoration 機制儲存其他資料
到這裡可能很多人就已滿足了,原來 restorationId
可以儲存臨時狀態,新技能 get
。但這只是冰山一角, restorationId
是被封裝在 ListView
中,只能儲存滑動偏移量,這還有值得舉一反三,繼續深挖的東西。下面通過官方
給的一個計時器小demo,認識一下 RestorationMixin
。
普通計數器 | 狀態儲存計數器 |
---|---|
上面兩個動態表現出通過 狀態儲存
的計時器可以在使用者主動
退出應用時,儲存狀態資料,進入時保持狀態。其中的關鍵在於 RestorationMixin
。普通的計時器原始碼就不貼了,大家應該已經爛熟於心了。實現定義一個 RestorableCounter
元件用於介面展示,
void main() => runApp(const RestorationExampleApp());
class RestorationExampleApp extends StatelessWidget {
const RestorationExampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
restorationScopeId: 'app',
title: 'Restorable Counter',
home: RestorableCounter(restorationId: 'counter'),
);
}
}
class RestorableCounter extends StatefulWidget {
const RestorableCounter({Key? key, this.restorationId}) : super(key: key);
final String? restorationId;
@override
State<RestorableCounter> createState() => _RestorableCounterState();
}
複製程式碼
如下在 _RestorableCounterState
中進行操作:首先混入 RestorationMixin
,然後覆寫 restorationId
和 restoreState
方法。提供 RestorableInt
物件記錄數值 。
class _RestorableCounterState extends State<RestorableCounter>
with RestorationMixin{ // 1. 混入 RestorationMixin
// 3. 使用 RestorableInt 物件記錄數值
final RestorableInt _counter = RestorableInt(0);
// 2. 覆寫 restorationId 提供 id
// @override
String? get restorationId => widget.restorationId;
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// 4. 註冊 _counter
registerForRestoration(_counter, 'count');
}
@override
void dispose() {
_counter.dispose(); // 5. 銷燬
super.dispose();
}
複製程式碼
在元件構建中,我們可以通過 _counter.value
訪問或運算元值。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Restorable Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'${_counter.value}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
void _incrementCounter() {
setState(() {
_counter.value++;
});
}
複製程式碼
剛才有的是 RestorableInt
,可能有人擔心別的資料型別怎麼辦。Flutter 中提供了很多 RestorableXXX
的資料型別以供使用。如果不夠用,可以通過擴充 RestorableProperty<T>
來自定義 RestorableXXX
完成需求。
從官方的更新公告上可以看出,目前暫不支援 iOS
,不過在以後會進行支援。
4. 滑動體系中的狀態儲存是如何實現的
當看完上面的小 demo
,你可能會比較好奇,滑動體系中是如何儲存的,下面我們就來看看吧。我們追隨 ListView
的 restorationId
屬性蹤跡,可以看到它會一路向父級構造中傳遞。最終在 ScrollView
中作為 Scrollable
元件的入參使用。
也就是說,這個屬性的根源是用於 Scrollable
中的。而這個元件是滑動觸發的根基,這也是為什麼滑動相關的元件都有 restorationId
屬性的原因。
ListView --> BoxScrollView --> ScrollView --> Scrollable
複製程式碼
ScrollableState
混入了 RestorationMixin
,其中用於儲存的型別為 _RestorableScrollOffset
。
同樣覆寫了 restoreState
和 restorationId
方法。
這時再看 TextField
元件的實現也是類似,也就說明 TextField
元件也具有這種恢復狀態的特性。
那本文就到這裡,更深層的 RestorationMixin
實現,以及其相關的其他類,還待繼續研究,敬請期待。