路由(Route)在移動開發中一般指頁面(Page),這跟web開發中單頁應用的Route概念意義是相同的,Route在Android中一般指一個Activity,在iOS中指一個ViewController。所謂路由管理,就是管理頁面之間如何跳轉,一般也可被稱爲導航管理。這和原生開發相似,不管是Android仍是iOS,導航管理都會維護一個路由棧,路由入棧(push)操做對應打開一個新頁面,路由出棧(pop)操做對應頁面關閉操做,而路由管理主要是指如何來管理路由棧。git
咱們在上一節「計數器」示例的基礎上,作以下修改:github
class SecondPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第二個路由頁面"), ), ); } }
新路由繼承自StatelessWidget,界面很簡單,在頁面中間顯示一句"這是第二個路由頁面"。web
Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ... //省略無關代碼 FlatButton( child: Text("點我打開一個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.push(context, new MaterialPageRoute(builder: (context) { return SecondPageRoute(); }) ); }, ), ], )
咱們添加了一個打開新路由的按鈕,並將按鈕文字顏色設置爲藍色,點擊該按鈕後就會打開新的路由頁面。ajax
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '【全棧編程】- onajax.com', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '【全棧編程】- onajax.com'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( '【全棧編程】- onajax.com提示:你已經點擊了--', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), FlatButton( child: Text("點我打開一個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.push(context, new MaterialPageRoute(builder: (context) { return SecondPageRoute(); }) ); }, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: '【全棧編程】- onajax.com', child: new Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } class SecondPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第二個路由頁面"), ), ); } }
MaterialPageRoute繼承自PageRoute類,PageRoute類是一個抽象類,表示佔有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。編程
MaterialPageRoute 是Material組件庫的一個Widget,它能夠針對不一樣平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:bash
對於Android,當打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當關閉頁面時,當前頁面會從屏幕頂部滑動到屏幕底部後消失,同時上一個頁面會顯示到屏幕上。app
對於iOS,當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面所有顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。less
下面咱們介紹一下MaterialPageRoute 構造函數的各個參數的意義:ide
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是一個路由管理的widget,它經過一個棧來管理一個路由widget集合。一般當前屏幕顯示的頁面就是棧頂的路由。Navigator提供了一系列方法來管理路由棧,在此咱們只介紹其最經常使用的兩個方法:
將給定的路由入棧(即打開新的頁面),返回值是一個Future對象,用以接收新路由出棧(即關閉)時的返回數據。
將棧頂路由出棧,result爲頁面關閉時返回給上一個頁面的數據。
Navigator 還有不少其它方法,如Navigator.replace、Navigator.popUntil等,詳情請參考API文檔或SDK源碼註釋,在此再也不贅述。下面咱們還須要介紹一下路由相關的另外一個概念「命名路由」。
Navigator類中第一個參數爲context的靜態方法都對應一個Navigator的實例方法, 好比Navigator.push(BuildContext context, Route route)等價於Navigator.of(context).push(Route route) ,下面命名路由相關的方法也是同樣的。
所謂命名路由(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: '【全棧編程】- onajax.com', theme: new ThemeData( primarySwatch: Colors.blue, ), routes: { "third_page":(context)=>ThirdPageRoute(), }, home: new MyHomePage(title: '【全棧編程】- onajax.com'), ); //同時添加第三個頁面的代碼 class ThirdPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第三個路由頁面"), ), ); } }
如今咱們就完成了路由表的註冊。
要經過路由名稱來打開新路由,可使用:
Future pushNamed(BuildContext context, String routeName,{Object arguments})
Navigator 除了pushNamed方法,還有pushReplacementNamed等其餘管理命名路由的方法,讀者能夠自行查看API文檔。
接下來咱們經過路由名來打開新的路由頁,再添加一個FlatButton的onPressed回調代碼,改成:
FlatButton( child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.pushNamed(context, "third_page"); }, )
熱重載應用,再次點擊「點我打開第三個新頁面」按鈕,依然能夠打開新的路由頁。
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '【全棧編程】- onajax.com', theme: new ThemeData( primarySwatch: Colors.blue, ), routes: { "third_page":(context)=>ThirdPageRoute(), }, home: new MyHomePage(title: '【全棧編程】- onajax.com'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( '【全棧編程】- onajax.com提示:你已經點擊了--', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), FlatButton( child: Text("點我打開第二個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.push(context, new MaterialPageRoute(builder: (context) { return SecondPageRoute(); }) ); }, ), FlatButton( child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.pushNamed(context, "third_page"); }, ) ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: '【全棧編程】- onajax.com', child: new Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } class SecondPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第二個路由頁面"), ), ); } } class ThirdPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第三個路由頁面"), ), ); } }
在Flutter最初的版本中,命名路由是不能傳遞參數的,後來才支持了參數;下面展現命名路由如何傳遞並獲取路由參數:
咱們先註冊一個路由:
routes:{ "parameters_page":(context)=>ParametersRoute(), } ,
在路由頁經過RouteSetting對象獲取路由參數:
class ParametersRoute extends StatelessWidget { @override Widget build(BuildContext context) { //獲取路由參數 var args = ModalRoute.of(context).settings.arguments //...省略無關代碼 } }
在打開路由時傳遞參數
Navigator.of(context).pushNamed("parameters_page", arguments: "【全棧編程】- onajax.com");
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '【全棧編程】- onajax.com', theme: new ThemeData( primarySwatch: Colors.blue, ), routes: { "third_page":(context)=>ThirdPageRoute(), "parameters_page":(context)=>ParametersRoute(), }, home: new MyHomePage(title: '【全棧編程】- onajax.com'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( '【全棧編程】- onajax.com提示:你已經點擊了--', ), new Text( '$_counter', style: Theme.of(context).textTheme.display1, ), FlatButton( child: Text("點我打開第二個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.push(context, new MaterialPageRoute(builder: (context) { return SecondPageRoute(); }) ); }, ), FlatButton( child: Text("點我打開第三個新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 Navigator.pushNamed(context, "third_page"); }, ), FlatButton( child: Text("攜帶參數打開新頁面",style: TextStyle(fontWeight: FontWeight.bold),), textColor: Colors.green, onPressed: () { //導航到一個新的路由頁面 // Navigator.pushNamed(context, "third_page"); Navigator.of(context).pushNamed("parameters_page",arguments:"【全棧編程】- onajax.com"); }, ) ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: '【全棧編程】- onajax.com', child: new Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } class SecondPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第二個路由頁面"), ), ); } } class ThirdPageRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("這是第三個路由頁面"), ), ); } } class ParametersRoute extends StatelessWidget { final Topic = Text("【全棧編程】- onajax.com"); @override Widget build(BuildContext context) { // TODO: implement build var args = ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text("【全棧編程】- onajax.com"), ), body: Center( child: Text("路由到此頁面獲取到的數據爲:\n" + args), ), ); } }