參考 book.flutterchina.club/chapter2/fl…html
路由(Route
)在移動開發中一般指頁面(Page
),在Android
中一般指一個Activity
。所謂路由管理,就是管理頁面之間如何跳轉,一般也可被稱爲導航管理。這和原生開發相似,不管是Android
仍是iOS
,導航管理都會維護一個路由棧,路由入棧(push
)操做對應打開一個新頁面,路由出棧(pop
)操做對應頁面關閉操做,而路由管理主要是指如何來管理路由棧。bash
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: MainRoute(),
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//導航到新路由
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondRoute();
}));
},
child: Text("進入第二頁"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context);
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
MaterialPageRoute
繼承自PageRoute
類,PageRoute
類是一個抽象類,表示佔有整個屏幕空間的一個模態路由頁面,它還定義了路由構建及切換時過渡動畫的相關接口及屬性。MaterialPageRoute
是Material
組件庫的一個Widget
,它能夠針對不一樣平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫:markdown
對於Android
,當打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當關閉頁面時,當前頁面會從屏幕頂部滑動到屏幕底部後消失,同時上一個頁面會顯示到屏幕上。 對於iOS
,當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面所有顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。 下面咱們介紹一下MaterialPageRoute
構造函數的各個參數的意義:app
MaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
})
複製代碼
builder
是一個WidgetBuilder
類型的回調函數,它的做用是構建路由頁面的具體內容,返回值是一個widget
。咱們一般要實現此回調,返回新路由的實例。less
settings
包含路由的配置信息,如路由名稱、是否初始路由(首頁)。async
maintainState
:默認狀況下,當入棧一個新路由時,原來的路由仍然會被保存在內存中,若是想在路由沒用的時候釋放其所佔用的全部資源,能夠設置maintainState
爲false
。ide
fullscreenDialog
表示新的路由頁面是不是一個全屏的模態對話框,在iOS
中,若是fullscreenDialog
爲true
,新頁面將會從屏幕底部滑入(而不是水平方向)。函數
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
)即給路由起一個名字,而後能夠經過路由名字直接打開新的路由。這爲路由管理帶來了一種直觀、簡單的方式。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
//home: MainRoute(),
//註冊路由表
routes: {
/// '/'是特殊地址,第一個頁面
"/" :(context) => MainRoute(),
"new_page": (context) => SecondRoute(),
},
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () async {
//導航到新路由
var result = await Navigator.pushNamed(context, "new_page");
debugPrint("返回:$result");
},
child: Text("進入第二頁"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context, "結束");
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
命名路由的最大優勢是直觀,咱們能夠經過語義化的字符串來管理路由。
但其有一個明顯的缺點:不能直接傳遞路由參數。假設SecondRoute
,須要接受一個字符串參數tip
,而後再在屏幕中心將tip
的內容顯示出來。由於命名路由須要提早註冊到路由表中,因此就沒法動態修改tip
參數。可是後面的版本已經支持了參數,看下面
在Flutter最初的版本中,命名路由是不能傳遞參數的,後來才支持了參數;下面展現命名路由如何傳遞並獲取路由參數:
咱們先註冊一個路由:
routes:{
"new_page": (context) => SecondRoute(),
} ,
複製代碼
在打開路由時傳遞參數
Navigator.of(context).pushNamed("new_page", arguments: "hi");
複製代碼
在路由頁經過RouteSetting對象獲取路由參數:
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
//獲取路由參數
var args=ModalRoute.of(context).settings.arguments
//...省略無關代碼
}
}
複製代碼
Material
庫中提供了MaterialPageRoute
,它在Android
上會上下滑動切換。若是想自定義路由切換動畫,可使用PageRouteBuilder
。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: MainRoute(),
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () async {
//導航到新路由
var result = await Navigator.push(
context,
PageRouteBuilder(
///動畫時間
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
///平移
return SlideTransition(
///Tween:在補間動畫中,定義開始點結束點
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: SecondRoute(),
);
},
),
);
debugPrint("返回:$result");
},
child: Text("進入第二頁"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context, "結束");
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
同時咱們也能夠對動畫進行組合
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: MainRoute(),
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () async {
//導航到新路由
var result = await Navigator.push(
context,
PageRouteBuilder(
///動畫時間
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
///透明漸變與旋轉
return new FadeTransition(
opacity: animation,
child: new RotationTransition(
turns: new Tween<double>(begin: 0.5, end: 1.0)
.animate(animation),
child: SecondRoute(),
),
);
},),
);
debugPrint("返回:$result");
},
child: Text("進入第二頁"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context, "結束");
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
///Navigator.push內部其實就是 Navigator.of(context).push
Navigator.of(context).push(MaterialPageRoute(builder: (_){
return new SecondRoute();
}));
},
child: Text("進入第二頁"),
)
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context);
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
這段代碼運行會出現錯誤:
問題關鍵點在於Navigator operation requested with a context that does not include a Navigator.(導航操做請求使用了不包含Navigator的上下文context)
Navigator
實際上也是一個Widget,這個異常出如今Navigator.of(context)
路由器的獲取上,而這句代碼會從當前的context的父級一層層向上去查找一個Navigator
,咱們當前傳遞的context就是MyApp,它的父級是root——UI根節點。Navigator
這個widget的並非由root建立的,所以在root下一級的上下文中沒法得到Navigator
。
在以前全部的路由案例中,咱們的上下文是MainRoute,它的父級是MaterialApp。MaterialApp內部就會建立一個Navigator。
MaterialApp->_MaterialAppState->WidgetsApp->_WidgetsAppState
![]()
因此問題就在於,Navigator
須要經過MaterialApp或者它孩子的上下文。
按照此筆記最開始的正常路由演示案例來進行修改。
使用Builder
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
///
Builder(builder: (context){
return RaisedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (_){
return new SecondRoute();
}));
},
child: Text("進入第二頁"),
);
})
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context);
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
使用Builder嵌套,Builder的參數能夠當作一個回調,接收自身的context並返回佈局配置。如今路由是從Builder的父親開始查找啦,天然能找到Navigator。
使用navigatorKey
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
@override
Widget build(BuildContext context) {
return new MaterialApp(
///指定路由器widget的key
navigatorKey: navigatorKey,
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text("主頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
///輸出Navigator
debugPrint(navigatorKey.currentWidget.runtimeType.toString());
navigatorKey.currentState.push(MaterialPageRoute(builder: (_){
return new SecondRoute();
}));
},
child: Text("進入第二頁"),
)
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第二頁"),
),
body: Column(
children: <Widget>[
Text("第一個頁面"),
RaisedButton(
onPressed: () {
//路由pop彈出
Navigator.pop(context);
},
child: Text("返回"),
)
],
),
);
}
}
複製代碼
在建立Navigator
的時候,會給一個key,這個key能夠當作一個Widget的id。這裏的**_navigator就是咱們指定的navigatorKey**(若是咱們沒指定,會給默認值的,因此不要疑惑不指定是否是就不建立Navigator
了)。而經過這個key,就可以得到這個Navigator
。直接得到了路由天然不須要再去查找了!