路由(Route)在移動開發中通常指頁面(Page),Route在Android中通常指一個Activity,在iOS中指一個ViewController。路由管理,就是管理頁面之間如何跳轉。Flutter中的路由管理和原生開發類似,無論是Android還是iOS,導航管理都會維護一個路由棧,路由入棧(push)操作對應打開一個新頁面,路由出棧(pop)操作對應頁面關閉操作,而路由管理主要是指如何來管理路由棧。
MaterialPageRoute
MaterialPageRoute
繼承自PageRoute
,PageRoute
是一個抽象類,表示占有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。MaterialPageRoute
是Material
組件庫提供的組件,它可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫。對于Android,當打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當關閉頁面時,當前頁面會從屏幕頂部滑動到屏幕底部后消失,同時上一個頁面會顯示到屏幕上。
對于iOS,當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面全部顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。
MaterialPageRoute
構造函數:
MaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
})
- builder
一個WidgetBuilder類型的回調函數,它的作用是構建路由頁面的具體內容,返回值是一個widget。通常要實現此回調,返回新路由的實例。 - settings
包含路由的配置信息,如路由名稱、是否初始路由(首頁)。 - maintainState
默認情況下,當入棧一個新路由時,原來的路由仍然會被保存在內存中,如果想在路由沒用的時候釋放其所占用的所有資源,可以設置maintainState為false。 - fullscreenDialog
表示新的路由頁面是否是一個全屏的模態對話框,在iOS中,如果fullscreenDialog為true,新頁面將會從屏幕底部滑入(而不是水平方向)。
Navigator
Navigator
是一個路由管理的組件,它提供了打開和退出路由頁方法。Navigator
通過一個棧來管理活動路由集合。通常當前屏幕顯示的頁面就是棧頂的路由。Navigator
提供了一系列方法來管理路由棧,最常用的兩個方法:
- Future push(BuildContext context, Route route)
將給定的路由入棧(即打開新的頁面),返回值是一個Future對象,用以接收新路由出棧(即關閉)時的返回數據。 - bool pop(BuildContext context, [ result ])
將棧頂路由出棧,result為頁面關閉時返回給上一個頁面的數據。
Navigator
還有很多其它方法,詳情請參考文檔。
路由傳值
很多時候,在路由跳轉或者返回上一級時需要帶一些參數。代碼示例:
class FirstPage extends StatefulWidget {
@override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
String _text = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第一頁'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('返回傳參:$_text'),
RaisedButton(
onPressed: () => _goToSecondPage(context),
child: Text('下一頁'),
),
],
),
),
);
}
void _goToSecondPage(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondPage(text: '第二頁');
})).then((value) {
setState(() {
_text = value;
});
});
}
}
class SecondPage extends StatelessWidget {
SecondPage({
Key key,
this.text,
}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第二頁'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(text),
RaisedButton(
onPressed: () => Navigator.pop(context, '返回值'),
child: Text('返回'),
),
],
),
),
);
}
}
在SecondPage中有兩種方式可以返回到上一頁;第一種方式時直接點擊導航欄返回箭頭,第二種方式是點擊頁面中的“返回”按鈕。這兩種返回方式的區別是前者不會返回數據給上一個路由,而后者會。如果點擊頁面中的返回按鈕也想返回數據,有兩種方法:
- 自定義返回的按鈕
appBar: AppBar(
title: Text("第二頁"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop("返回值");
},
),
),
- 監聽返回按鈕點擊(給Scaffold包裹一個WillPopScope)
WillPopScope
有一個onWillPop
的回調函數,當點擊返回按鈕時會執行,這個函數要求有一個Future的返回值:
true:系統會自動執行pop操作
false:系統不再執行pop操作,需要自己來執行
return WillPopScope(
onWillPop: () {
Navigator.pop(context, '返回值');
return Future.value(false);
},
child: Scaffold(
appBar: AppBar(
title: Text('第二頁'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(text),
RaisedButton(
onPressed: () => Navigator.pop(context, '返回值'),
child: Text('返回'),
),
],
),
),
),
);
命名路由
命名路由(Named Route)即有名字的路由,可以先給路由起一個名字,然后就可以通過路由名字直接打開新的路由了。
路由表
要想使用命名路由,必須先提供并注冊一個路由表(routing table),這樣應用程序才知道哪個名字與哪個路由組件相對應。路由表的定義如下:
Map<String, WidgetBuilder> routes;
它是一個Map
,key
為路由的名字,是個字符串;value
是個builder
回調函數,用于生成相應的路由widget
。在通過路由名字打開新路由時,應用會根據路由名字在路由表中查找到對應的 WidgetBuilder
回調函數,然后調用該回調函數生成路由widget
并返回。
注冊路由表
路由表的注冊方式很簡單,在build方法中找到MaterialApp,添加routes屬性。代碼示例:
MaterialApp(
title: 'Route Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 注冊路由表
routes:{
'secondPage': (context) => SecondPage(),
// 省略其它路由注冊信息
} ,
home: FirstPage(),
);
如果也想將home注冊為命名路由,只需在路由表中注冊一下FirstPage
路由,然后將其名字作為MaterialApp
的initialRoute
屬性值即可。代碼示例:
MaterialApp(
title: 'Route Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 名為"/"的路由作為應用的home(首頁)
initialRoute: '/',
// 注冊路由表
routes:{
'/': (context) => FirstPage(),
'secondPage': (context) => SecondPage(),
// 省略其它路由注冊信息
} ,
);
通過路由名打開新路由頁
要通過路由名稱來打開新路由,可以使用Navigator 的pushNamed方法:
Future pushNamed(BuildContext context, String routeName,{Object arguments})
代碼示例:
void _goToSecondPage(BuildContext context) {
Navigator.pushNamed(context, 'secondPage').then((value) {
setState(() {
_text = value;
});
});
}
依然可以打開新的路由頁,同樣可以接收返回參數。
命名路由參數傳遞
在路由頁通過RouteSetting對象獲取路由參數:
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 獲取路由參數
var args = ModalRoute.of(context).settings.arguments;
// 省略無關代碼
}
}
在打開路由時傳遞參數:
Navigator.of(context).pushNamed("secondPage", arguments: "第二頁");
如果想將上面路由傳參示例中的SecondPage路由頁注冊到路由表中,以便也可以通過路由名來打開它。但是,由于SecondPage接受一個text參數,可以不改變SecondPage源碼的前提下適配這種情況:
MaterialApp(
// 省略無關代碼
routes: {
'secondPage': (context) => SecondPage(text: ModalRoute.of(context).settings.arguments),
},
);