# 路由管理
路由(Route)在移動開發中通常指頁面(Page),這跟web開發中單頁應用的Route概念意義是相同的,Route在Android中通常指一個Activity,在iOS中指一個ViewController。所謂路由管理,就是管理頁面之間如何跳轉,通常也可被稱為導航管理。這和原生開發類似,無論是Android還是iOS,導航管理都會維護一個路由棧,路由入棧(push)操作對應打開一個新頁面,路由出棧(pop)操作對應頁面關閉操作,而路由管理主要是指如何來管理路由棧。
## 示例
我們在上一節“計數器”示例的基礎上,做如下修改:
1. 創建一個新路由,命名“NewRoute”
```
class NewRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("New route"),
),
body: Center(
child: Text("This is new route"),
),
);
}
}
```
新路由繼承自`StatelessWidget`,界面很簡單,在頁面中間顯示一句"This is new route"。
2. 在`_MyHomePageState.build`方法中的`Column`的子widget中添加一個按鈕(`FlatButton`) :
```
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
... //省略無關代碼
FlatButton(
child: Text("open new route"),
textColor: Colors.blue,
onPressed: () {
//導航到新路由
Navigator.push( context,
new MaterialPageRoute(builder: (context) {
return new NewRoute();
}));
},
),
],
)
```
我們添加了一個打開新路由的按鈕,并將按鈕文字顏色設置為藍色,點擊該按鈕后就會打開新的路由頁面。

## MaterialPageRoute
`MaterialPageRoute`繼承自`PageRoute`類,`PageRoute`類是一個抽象類,表示占有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。`MaterialPageRoute` 是Material組件庫的一個Widget,它可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:
- 對于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`,新頁面將會從屏幕底部滑入(而不是水平方向)。
> 如果想自定義路由切換動畫,可以自己繼承PageRoute來實現,我們將在后面介紹動畫時,實現一個自定義的路由Widget。
## Navigator
`Navigator`是一個路由管理的widget,它通過一個棧來管理一個路由widget集合。通常當前屏幕顯示的頁面就是棧頂的路由。`Navigator`提供了一系列方法來管理路由棧,在此我們只介紹其最常用的兩個方法:
### Future push(BuildContext context, Route route)
將給定的路由入棧(即打開新的頁面),返回值是一個`Future`對象,用以接收新路由出棧(即關閉)時的返回數據。
### bool pop(BuildContext context, \[ result \])
將棧頂路由出棧,`result`為頁面關閉時返回給上一個頁面的數據。
`Navigator` 還有很多其它方法,如`Navigator.replace`、`Navigator.popUntil`等,詳情請參考API文檔或SDK源碼注釋,在此不再贅述。下面我們還需要介紹一下路由相關的另一個概念“命名路由”。
## 命名路由
所謂命名路由(Named Route)即給路由起一個名字,然后可以通過路由名字直接打開新的路由。這為路由管理帶來了一種直觀、簡單的方式。
### 路由表
要想使用命名路由,我們必須先提供并注冊一個路由表(routing table),這樣應用程序才知道哪個名稱與哪個路由Widget對應。路由表的定義如下:
```
Map<String, WidgetBuilder> routes;
```
它是一個`Map`, key 為路由的名稱,是個字符串;value是個builder回調函數,用于生成相應的路由Widget。我們在通過路由名稱入棧新路由時,應用會根據路由名稱在路由表中找到對應的WidgetBuilder回調函數,然后調用該回調函數生成路由widget并返回。
### 注冊路由表
我們需要先注冊路由表后,我們的Flutter應用才能正確處理命名路由的跳轉。注冊方式很簡單,我們回到之前“計數器”的示例,然后在`MyApp`類的`build`方法中找到`MaterialApp`,添加`routes`屬性,代碼如下:
```
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
//注冊路由表
routes:{
"new_page":(context)=>NewRoute(),
} ,
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
```
現在我們就完成了路由表的注冊。
### 通過路由名打開新路由頁
要通過路由名稱來打開新路由,可以使用:
```
Future pushNamed(BuildContext context, String routeName)
```
`Navigator` 除了`pushNamed`方法,還有`pushReplacementNamed`等其他管理命名路由的方法,讀者可以自行查看API文檔。
接下來我們通過路由名來打開新的路由頁,修改`FlatButton`的`onPressed`回調代碼,改為:
```
onPressed: () {
Navigator.pushNamed(context, "new_page");
//Navigator.push(context,
// new MaterialPageRoute(builder: (context) {
// return new NewRoute();
//}));
},
```
熱重載應用,再次點擊“open new route”按鈕,依然可以打開新的路由頁。
### 命名路由的優缺點
命名路由的最大優點是直觀,我們可以通過語義化的字符串來管理路由。但其有一個明顯的缺點:不能直接傳遞路由參數。舉個例子,假設有一個新路由EchoRoute,它的功能是接受一個字符串參數`tip`,然后再在屏幕中心將`tip`的內容顯示出來,代碼如下:
```
class EchoRoute extends StatelessWidget {
EchoRoute(this.tip);
final String tip;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Echo route"),
),
body: Center(
//回顯tip內容
child: Text(tip),
),
);
}
}
```
如果我們使用命名參數,就必須將路由提前注冊到路由表中,所以就無法動態修改`tip`參數,如:
```
{
"tip_widgets":(context)=>EchoRoute("內容固定")
}
```
綜上所述,我們可以看到當路由需要參數時,使用命名路由則不夠靈活。
- 緣起
- 起步
- 移動開發技術簡介
- Flutter簡介
- 搭建Flutter開發環境
- 常見配置問題
- Dart語言簡介
- 第一個Flutter應用
- 計數器示例
- 路由管理
- 包管理
- 資源管理
- 調試Flutter APP
- Dart線程模型及異常捕獲
- 基礎Widgets
- Widget簡介
- 文本、字體樣式
- 按鈕
- 圖片和Icon
- 單選框和復選框
- 輸入框和表單
- 布局類Widgets
- 布局類Widgets簡介
- 線性布局Row、Column
- 彈性布局Flex
- 流式布局Wrap、Flow
- 層疊布局Stack、Positioned
- 容器類Widgets
- Padding
- 布局限制類容器ConstrainedBox、SizeBox
- 裝飾容器DecoratedBox
- 變換Transform
- Container容器
- Scaffold、TabBar、底部導航
- 可滾動Widgets
- 可滾動Widgets簡介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滾動監聽及控制ScrollController
- 功能型Widgets
- 導航返回攔截-WillPopScope
- 數據共享-InheritedWidget
- 主題-Theme
- 事件處理與通知
- 原始指針事件處理
- 手勢識別
- 全局事件總線
- 通知Notification
- 動畫
- Flutter動畫簡介
- 動畫結構
- 自定義路由過渡動畫
- Hero動畫
- 交錯動畫
- 自定義Widget
- 自定義Widget方法簡介
- 通過組合現有Widget實現
- 實例:TurnBox
- CustomPaint與Canvas
- 實例:圓形漸變進度條(自繪)
- 文件操作與網絡請求
- 文件操作
- Http請求-HttpClient
- Http請求-Dio package
- 實例:Http分塊下載
- WebSocket
- 使用Socket API
- Json轉Model
- 包與插件
- 開發package
- 插件開發:平臺通道簡介
- 插件開發:實現Android端API
- 插件開發:實現IOS端API
- 系統能力調用
- 國際化
- 讓App支持多語言
- 實現Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系統
- Element和BuildContext
- RenderObject與RenderBox
- Flutter從啟動到顯示