路由管理
路由(Route)在移動開發中通常指頁面(Page),這跟web開發中單頁應用的Route概念意義是相同的,Route在Android中通常指一個Activity,在iOS中指一個ViewController。所謂路由管理,就是管理頁面之間如何跳轉,通常也可被稱為導航管理。這和原生開發類似,無論是Android還是iOS,導航管理都會維護一個路由棧,路由入棧(push)操作對應開啟一個新頁面,路由出棧(pop)操作對應頁面關閉操作,而路由管理主要是指如何來管理路由棧。
我們在上一節“計數器”示例的基礎上,做如下修改:
1.建立一個新路由,命名“SecondPageRoute”
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,介面很簡單,在頁面中間顯示一句"這是第二個路由頁面"。
2.在_MyHomePageState.build方法中的Column的子widget中新增一個按鈕(FlatButton)
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();
})
);
},
),
],
)
我們新增了一個開啟新路由的按鈕,並將按鈕文字顏色設定為藍色,點選該按鈕後就會開啟新的路由頁面。
完整程式碼
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
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原始碼註釋,在此不再贅述。下面我們還需要介紹一下路由相關的另一個概念“命名路由”。
例項方法
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),
),
);
}
}