若是說構成視圖元素的基本單位是組件,那麼構成應用程序的基本單位就是頁面。對於擁有多個頁面的應用程序而言,如何從一個頁面平滑地過渡到另外一個頁面,是技術框架須要考慮的問題。css
在前端開發中,可使用路由框架來統一管理頁面及它們之間的跳轉。在Android中路由指的是一個Activity,在iOS中指的是一個ViewController,能夠經過startActivity或pushViewController來打開一個新的路由。在Flutter中,路由的管理和導航借鑑了前端和客戶端的設計思路,須要使用Route和Navigator來進行統一管理。前端
其中,Route是頁面的抽象,主要負責建立界面、接收參數以及響應導航器Navigator的打開與關閉。而Navigator則用於維護路由棧管理,Route打開即入棧,Route關閉即出棧,固然還能夠替換棧內的某一個Route。做爲官方提供的路由管理組件,Navigator提供了一系列方法來管理路由棧,其中最經常使用的兩個方法是push()和pop(),它們的含義以下。app
除了push()和pop()方法外,Navigator還提供了不少其它實用的方法,如replace()、removeRoute()和popUntil()等,能夠根據使用場景合理的選取。框架
根據是否須要提早註冊頁面標識符,Flutter中的路由管理能夠分爲基本路由和命名路由兩種。less
下面就讓咱們重點來看一下Flutter中的路由管理的基本路由和命名路由等相關知識。ide
在Flutter開發中,基本路由的使用方式和原生Android、iOS打開新頁面的方式很是相似。要打開一個新的頁面,只須要建立一個MaterialPageRoute對象實例,而後調用Navigator.push()方法將新頁面壓到路由堆棧的頂部便可,若是要返回上一個頁面,則能夠調用Navigator.pop()方法。模塊化
其中,MaterialPageRoute是一種路由模板,定義了路由建立以及路由切換過渡動畫的相關配置,該配置能夠根據不一樣的平臺實現與平臺頁面切換動畫風格一致的路由切換動畫。下面是使用Navigator實現頁面跳轉的示例,代碼以下。函數
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第一個頁面'),
),
body: Center(
child: RaisedButton(
child: Text('跳轉到第二個頁面'),
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondPage()))),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第二個頁面'),
),
body: Center(
child: RaisedButton(
child: Text('返回上一個頁面'),
onPressed: () => Navigator.pop(context)),
),
);
}
}
複製代碼
在上面的示例中,咱們建立了兩個頁面,每一個頁面都包含一個按鈕。當點擊第一個頁面上的按鈕時將導航到第二個頁面,點擊第二個頁面上的按鈕將返回第一個頁面。運行上面的代碼,效果以下圖所示。 源碼分析
能夠發現,跳轉頁面使用的是Navigator.push()方法,該方法能夠將一個新的路由添加到由Navigator管理的路由對象的棧頂。而建立新的路由對象使用的是MaterialPageRoute,MaterialPageRoute是PageRoute的子類,定義了路由建立及切換時過渡動畫的相關接口及屬性,而且自帶頁面切換動畫,Android平臺頁面進入動畫是向上滑動並淡出,退出是相反,iOS平臺頁面進入動畫是從右側滑入,退出則相反。基本路由的使用方式相對簡單靈活,適用於應用中頁面很少的場景。而對於應用中頁面比較多的狀況下,若是再使用基本路由方式,那麼每次跳轉一個新的頁面都要手動建立MaterialPageRoute實例,而後再調用push()方法來打開一個新的頁面,此時頁面的管理和跳轉就比較混亂。佈局
爲了不頻繁的建立MaterialPageRoute實例,Flutter提供了另一種方式來簡化路由管理,即命名路由。所謂命名路由,就是給頁面起一個別名,而後使用頁面的別名就能夠打開它,使用此種方式來管理路由,使得路由的管理更加清晰直觀。
要想經過別名來指定頁面切換,必須先給應用程序MaterialApp提供一個頁面名稱映射規則,即路由表。路由表是一個Map<String,WidgetBuilder>的結構,其中key對應頁面名字,value則是對應的頁面,以下所示。
MaterialApp(
... //其餘配置
routes:{ //註冊路由
'first':(context)=>FirstPage(),
'second':(context)=>SecondPage(),
},
initialRoute: 'first', //初始路由頁面
);
複製代碼
在路由表中註冊好頁面後,而後就能夠經過Navigator.pushNamed()方法來打開頁面,以下所示。
Navigator.pushNamed(context,"second "); // second表示頁面別名
複製代碼
不過,因爲路由的註冊和使用都採用字符串來標識,這就會帶來一個問題,即若是打開一個不存在的路由頁面。對應這類問題,移動應用有一個通用的解決方案,即跳轉到一個統一的錯誤頁面。在註冊路由表時,Flutter提供了一個UnknownRoute屬性,用來對未知的路由標識符進行統一的頁面跳轉處理,以下所示。
MaterialApp(
…
routes:{},
onUnknownRoute: (RouteSettings setting) => MaterialPageRoute(builder: (context) => UnknownPage()), //錯誤路由處理,返回UnknownPage
);
class UnknownPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('錯誤路由'),
),
);
}
}
複製代碼
有時候,一個應用可能不止一個導航器,而是可能有多個導航器,將一個導航器嵌套在另外一個導航器的行爲稱爲路由嵌套。路由嵌套在移動開發中是很常見的,好比,移動開發中常常會看到應用主頁有底部導航欄,每一個底部導航欄又嵌套其餘頁面的狀況,效果以下圖所示。
要實現上面的示例效果,首先須要新建一個底部導航欄,而後再由底部導航欄去嵌套其餘子路由。關於底部導航欄的實現,能夠直接使用Scaffold佈局組件的bottomNavigationBar屬性實現,以下所示。class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MainPageState();
}
}
class MainPageState extends State<MainPage> {
int currentIndex = 0; //底部導航欄索引
final List<Widget> children = [
HomePage(), //首頁
MinePage(), //個人
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: children[currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentIndex,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首頁')),
BottomNavigationBarItem(icon: Icon(Icons.person), title: Text('個人')),
],
),
);
}
void onTabTapped(int index) {
setState(() {
currentIndex = index;
});
}
}
複製代碼
而後,每一個底部導航欄會嵌套一個子路由,而後子路由再去管理對應的路由頁面。在Flutter中,建立子路由須要使用Navigator組件,而且子路由的攔截須要使用onGenerateRoute屬性,以下所示。
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: 'first',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'first':
builder = (BuildContext context) => FirstPage();
break;
case 'second':
builder = (BuildContext context) => SecondPage();
break;
}
return new MaterialPageRoute(builder: builder, settings: settings);
},
);
}
}
複製代碼
運行上面的代碼,當點擊子路由頁面上的按鈕時,底部的導航欄欄並不會消失,這是由於子路由僅在本身的範圍內有效。要想跳轉到其餘子路由管理的頁面,就須要在根導航器中進行註冊,也就是MaterialApp內部的導航器。
在移動應用開發中,頁面參數的傳遞也是一個比較常見的需求。爲了知足不一樣場景下頁面跳轉過程當中參數傳遞的需求,Flutter提供了路由參數機制,能夠在打開路由時傳遞參數,而後在目標頁面經過RouteSettings來獲取頁面傳遞的參數,以下所示。
Navigator.of(context).pushNamed("second ", arguments: " from first page");
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出路由參數
String msg = ModalRoute.of(context).settings.arguments as String;
… //數據處理
}
}
複製代碼
除此以外,對於某些特定的頁面,還須要在其關閉時回傳頁面處理的處理結果。這與Android提供的startActivityForResult()方法監聽目標頁面返回處理結果的場景相似,Flutter也提供了頁面返回的參數機制。具體來講,就是在使用push()方法打開目標頁面時,能夠設置目標頁面關閉時監聽函數來獲取返回參數,當目標頁面關閉路由時使用pop()方法回傳參數便可。例如,下面是兩個頁面之間參數值傳遞和參數值回傳,代碼以下。
class FirstPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return FirstPageState();
}
}
class FirstPageState extends State<FirstPage> {
String result = '';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
Text('from seconde page: ' + result, style: TextStyle(fontSize: 20)),
RaisedButton(
child: Text('跳轉'),
//使用then()獲取目標頁面返回參數
onPressed: () => Navigator.of(context)
.pushNamed("second", arguments: "from first page")
.then((msg) => setState(() => result = msg)))
],
)),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
String msg = ModalRoute.of(context).settings.arguments as String;
return Scaffold(
body: Center(
child: Column(children: [
Text('from first screen: ' + msg, style: TextStyle(fontSize: 20)),
RaisedButton(
child: Text('返回'),
onPressed: () => Navigator.pop(context, "from second page"))
]),
));
}
}
複製代碼
運行上面的代碼,能夠看到,當SecondPage頁面被關閉從新回到FirstPage頁面時,FirstPage會把回傳的參數值展現出來,最終效果以下圖所示。
在使用路由過程當中,通過會使用到MaterialPageRoute類。MaterialPageRoute繼承自PageRoute類,PageRoute類是一個抽象類,表示佔有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。
MaterialPageRoute 是Material組件庫提供的組件,它能夠針對不一樣平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面所有顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。
MaterialPageRoute 構造函數和各個參數的意義以下:
MaterialPageRoute({
@required this.builder,
RouteSettings settings,
this.maintainState = true,
bool fullscreenDialog = false,
})
複製代碼
它們的具體含義以下:
Flutter 提供了基本路由和命名路由兩種方式,來管理頁面間的跳轉。其中,基本路由須要本身手動建立頁面實例,經過 Navigator.push 完成頁面跳轉;而命名路由須要提早註冊頁面標識符和頁面建立方法,經過 Navigator.pushNamed 傳入標識符實現頁面跳轉。
對於命名路由,若是咱們須要響應錯誤路由標識符,還須要一併註冊 UnknownRoute。爲了精細化控制路由切換,Flutter 提供了頁面打開與頁面關閉的參數機制,咱們能夠在頁面建立和目標頁面關閉時,取出相應的參數。能夠看到,關於路由導航,Flutter 綜合了 Android、iOS 和 React 的特色,簡潔而不失強大。
在中大型應用中,一般還會使用命名路由來管理頁面間的切換。命名路由的最重要做用,就是創建了字符串標識符與各個頁面之間的映射關係,使得各個頁面之間徹底解耦,應用內頁面的切換隻須要經過一個字符串標識符就能夠搞定,爲後期模塊化打好基礎。
除此以外,嵌套路由和路由傳參也是路由框架中比較核心的內容。本篇只是Flutter路由與導航的基本知識,後面將會從pushReplacementNamed 、 popAndPushNamed、pushNamedAndRemoveUntil和popUntil,以及第三方導航庫和源碼分析等方面來深刻介紹Flutter的路由開發與導航。