在〈Navigator 與 Route〉中,簡介了 Navigator
與 Route
的關係,必須記得的是,Route
不等於頁面,Route
代表的是資源的銜接,頁面只是資源的一部份,有了這個正確觀念後,接下來討論頁面間的資料傳遞才有意義。
以 MaterialPageRoute
為例,如何將資料傳給接下來要呈現的頁面呢?例如,傳給〈Navigator 與 Route〉中範例的 DetailPage
?建構 MaterialPageRoute
時,指定的 builder
就告訴你答案了:
...略
Navigator.push(context,
MaterialPageRoute(builder: (_) => DetailPage())
);
...略
也就是透過 DetailPage
的建構式;從 DetailPage
回到 MainPage
時呢?Navigator
的 push
其實是個非同步方法,它傳回的是 Future
實例,Route
從 Navigator
管理的堆疊中彈出時,也就是 Navigator.pop
執行時,若指定了第二個 result
參數的值,該值就會成為 push
傳回的 Future
實例之結果。
MaterialPageRoute
其實支援泛型,如果預期 MaterialPageRoute
從 Navigator
管理的堆疊中彈出時,也就是 Navigator.pop
執行時,第二個 result
參數值的型態是字串,就可以如下撰寫:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: 'Openhome',
home: MainPage(),
));
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('主畫面'),
),
body: GestureDetector(
// 註冊一個非同步處理器
onTap: () async {
// 等待頁面的結果
String result = await Navigator.push(context,
// 傳遞給頁面的資料會作為建構 DetailPage 的值
MaterialPageRoute(builder: (_) => DetailPage('說明'))
);
print(result);
},
child: Image.asset('images/caterpillar.png'),
),
);
}
}
class DetailPage extends StatelessWidget {
String title;
DetailPage(this.title);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: GestureDetector(
// 指定 pop 的第二個參數值
onTap: () => Navigator.pop(context, '結果值'),
child: Center(
child: Image.asset('images/caterpillar.png'),
),
),
);
}
}
執行結果如下:
那麼問題來了,如果 DetailPage
頁面必須擁有狀態呢?繼承 StatefulWidget
?對於操作 DetailPage
期間產生的狀態變化,確實是該這麼做,問題在於從 MainPage
切換為 DetailPage
時,繼承了 StatefulWidget
的 DetailPage
,每次都會產生新的狀態物件,先前操作的狀態就無法保留了。
若要避免複雜的狀態管理,最簡單的方式,當然就是不考慮之前頁面的操作狀態了,每次切換頁面就視為新的操作流程。
若要真的想接續狀態,是將 DetailPage
的狀態儲存下來,下次切換至 DetailPage
,將對應狀態傳給 DetailPage
進行建構,儲存的地方可以是檔案、遠端,或者是放在主頁面,例如:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: 'Openhome',
home: MainPage(),
));
class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
// 在主頁面保存次頁面切回時的狀態
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('主畫面'),
),
body: GestureDetector(
onTap: () async {
// 在主頁面保存次頁面切回時的狀態
_counter = await Navigator.push(context,
// 傳入上次保有的狀態
MaterialPageRoute(builder: (_) => ClickPage(_counter, title: '按我'))
);
},
child: Image.asset('images/caterpillar.png'),
),
);
}
}
class ClickPage extends StatefulWidget {
ClickPage(this._counter, {Key key, this.title}) : super(key: key);
// 次頁面自己目前的操作狀態
final int _counter;
final String title;
@override
_ClickPageState createState() => _ClickPageState(_counter);
}
class _ClickPageState extends State<ClickPage> {
int _counter;
_ClickPageState(this._counter);
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
// 按功能列的箭號按鈕時,傳回 _counter
onPressed: () => Navigator.pop(context, _counter),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'你按下按鈕的次數:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
這個範例的 ClickPage
是修改自〈第一個 Flutter 專案〉,要注意的是,AppBar
上預設的箭號按鈕按下時,沒有預設的處理器,因此必須自行指定,這可透過 leading
來建立新的 IconButton
來設定。
以下是操作的結果: