Flutter的頁面,怎么進行跳轉的呢?通過路由和導航呢。
一、路由和導航,初認識
言簡意賅!
-
路由(Route)
: route是一個屏幕或頁面的抽象(可以大概理解為安卓的Activity) -
導航(Navigator)
: Navigator是管理route的Widget。導航器管理著路由對象的堆棧并提供管理堆棧的方法,如 Navigator.push入棧 和 Navigator.pop出棧
Navigator可以通過route入棧和出棧來實現頁面之間的跳轉。
一1、最簡單的頁面跳轉
一個頁面,在Flutter里面,被理解為一個路由。
多個路由,可以存在與同一個dart文件中的。
- 使用Navigator.pushNamed方法首先需要在 MaterialApp 中定義routes。
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(title:'導航頁面示例', home: new Demo()));
}
class Demo extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: Text('導航頁面示例'),
),
body: new Center(
child:RaisedButton(
child: Text('查看詳情頁面'),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context)=>new SecondScreen()));
},
)
),
);
}
}
class SecondScreen extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('導航頁面第二屏'),
),
body: new Center(
child: new RaisedButton(
onPressed: (){
Navigator.pop(context);
},
child: new Text('返回頁面'),
),
),
);
}
}
.
.
從上面的例子,我們看到進入新頁面,用push(入棧),返回上個頁面,用pop(出棧)。
MaterialPageRoute
-
MaterialPageRoute
是Material組件庫的一個Widget,它可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫
:對于Android,當打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當關閉頁面時,當前頁面會從屏幕頂部滑動到屏幕底部后消失,同時上一個頁面會顯示到屏幕上。
對于iOS,當打開頁面時,新的頁面會從屏幕右側邊緣一致滑動到屏幕左邊,直到新頁面全部顯示到屏幕上,而上一個頁面則會從當前屏幕滑動到屏幕左側而消失;當關閉頁面時,正好相反,當前頁面會從屏幕右側滑出,同時上一個頁面會從屏幕左側滑入。
默認情況下,當一個模態路由被另一個替換時,上一個路由將保留在內存中,如果想釋放所有資源,可以將 maintainState 設置為 false。
MaterialPageRoute的構造函數
MaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
})
builder 是一個WidgetBuilder類型的回調函數,它的作用是構建路由頁面的具體內容,返回值是一個widget。我們通常要實現此回調,返回新路由的實例。
settings 包含路由的配置信息,如路由名稱、是否初始路由(首頁)。
maintainState:默認情況下,當入棧一個新路由時,原來的路由仍然會被保存在內存中,如果想在路由沒用的時候釋放其所占用的所有資源,可以設置maintainState為false。
fullscreenDialog表示新的路由頁面是否是一個全屏的模態對話框,在iOS中,如果fullscreenDialog為true,新頁面將會從屏幕底部滑入(而不是水平方向)。
.
.
示例:
一.2、跳轉頁面時傳遞數據
- 使用Navigator.push接收參數的重點在構造函數,通過在構造函數中接受參數進行傳遞
傳遞數據,其實也很簡單。P1跳轉到P2,P2的構造函數預留好參數,P1跳轉到P2的時候,傳遞一下即可。
import 'package:flutter/material.dart';
class Product {
final String title;
final String description;
Product(this.title,this.description);
} //Product 類 屬性
void main(){
runApp(new MaterialApp(
title:'傳遞數據示例',
home:new ProductList(
products:new List.generate(20, (i)=>new Product('商品 $i', '這是一個商品的詳情 $i')) //父子傳值
)
));
}
class ProductList extends StatelessWidget{
final List<Product> products;
ProductList({Key key,@required this.products}):super(key:key);
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: new Text('商品列表'),
),
body: new ListView.builder(
itemCount:products.length,
itemBuilder:(context,index){
return new ListTile(
title:new Text(products[index].title),
onTap: (){
Navigator.push(
context,
// 傳遞數據
new MaterialPageRoute(
builder: (context)=>new ProductDetail(product:products[index])
)
);
},
);
}
),
);
}
}
class ProductDetail extends StatelessWidget {
final Product product;
ProductDetail({Key key,@required this.product}):super(key:key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('${product.title}'),
),
body: new Padding(
padding: new EdgeInsets.all(16.0),
child: new Text('${product.description}'),
),
);
}
}
.
.
效果:
一3、頁面返回數據
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(title:'導航頁面示例', home: new ArticleListScreen()));
}
class Article {
String title;
String content;
Article({this.title, this.content});
}
class ArticleListScreen extends StatelessWidget {
final List<Article> articles = new List.generate(
10,
(i) => new Article(
title: 'Article $i',
content: '文章 $i: 你喜歡這個文章嗎親.',
),
);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Article List'),
),
body: new ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
// 當用戶點擊列表中的文章時將跳轉到ContentScreen,并將 article 傳遞給 ContentScreen。
//為了實現這一點,我們將實現 ListTile 的 onTap 回調。 在的 onTap 回調中,再次調用Navigator.push方法。
return new ListTile(
title: new Text(articles[index].title),
// 列表項的 onTap 回調,處理內容頁面返回的數據并顯示。
/*onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new ContentScreen(articles[index]),
),
);
},*/
onTap: () async {
// 接受頁面返回值
String result = await Navigator.push(
context,
new MaterialPageRoute(
// 文章頁面跳轉到內容頁面,傳遞個值
builder: (context) => new ContentScreen(articles[index]),
),
);
if (result != null) {
Scaffold.of(context).showSnackBar(
new SnackBar(
// 顯示一下從別人頁面傳遞過來的值,如果有值的話
content: new Text("$result"),
duration: const Duration(seconds: 1),
),
);
}
},
);
},
),
);
}
}
class ContentScreen extends StatelessWidget {
final Article article;
ContentScreen(this.article);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('${article.title}'),
),
body: new Padding(
padding: new EdgeInsets.all(15.0),
child: new Column(
children: <Widget>[
new Text('${article.content}'),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new RaisedButton(
onPressed: () {
// pop 第二個參數,就是表示給上個頁面傳遞
Navigator.pop(context, 'Like');
},
child: new Text('Like'),
),
new RaisedButton(
onPressed: () {
// pop 第二個參數,就是表示給上個頁面傳遞
Navigator.pop(context, 'Unlike');
},
child: new Text('Unlike'),
),
],
)
],
),
),
);
}
}
返回數據,主要就是pop的時候返回,然后想接受的,在push的時候,就接受下
.
.
效果
二、定制路由
通常,我們可能需要定制路由以實現自定義的過渡效果等。定制路由有兩種方式:
- 繼承路由子類,如:PopupRoute、ModalRoute 等。
- 使用 PageRouteBuilder 類通過回調函數定義路由。
- 下面使用 PageRouteBuilder 實現一個頁面旋轉淡出的效果。
onTap: () async {
String result = await Navigator.push(
context,
new PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 1000),
pageBuilder: (context, _, __) =>
new ContentScreen(articles[index]),
transitionsBuilder:
(_, Animation<double> animation, __, Widget child) =>
new FadeTransition(
opacity: animation,
child: new RotationTransition(
turns: new Tween<double>(begin: 0.0, end: 1.0)
.animate(animation),
child: child,
),
),
));
if (result != null) {
Scaffold.of(context).showSnackBar(
new SnackBar(
content: new Text("$result"),
duration: const Duration(seconds: 1),
),
);
}
},
三、命名路由
在Flutter最初的版本中,命名路由是不能傳遞參數的,后來才支持了參數.
當使用 initialRoute 時,需要確保你沒有同時定義 home 屬性。
通常,移動應用管理著大量的路由,并且最容易的是使用名稱來引用它們
。路由名稱通常使用路徑結構:“/a/b/c”,主頁默認為 “/”
。
MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
// 使用“/”命名路由來啟動應用(Start the app with the "/" named route. In our case, the app will start)
// 在這里,應用將從 FirstScreen Widget 啟動(on the FirstScreen Widget)
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
// 當我們跳轉到“/”時,構建 FirstScreen Widget(When we navigate to the "/" route, build the FirstScreen Widget)
'/': (context) => FirstScreen(),
// When navigating to the "/second" route, build the SecondScreen widget.
// 當我們跳轉到“/second”時,構建 SecondScreen Widget(When we navigate to the "/second" route, build the SecondScreen Widget)
'/second': (context) => SecondScreen(),
},
);
創建 MaterialApp 時可以指定 routes 參數,該參數是一個映射路由名稱和構造器的 Map
。MaterialApp 使用此映射為導航器的 onGenerateRoute 回調參數提供路由。
注冊路由
routes: {
// 注冊路由
"parameters_page":(context)=>ParametersRoute(),
},
在路由頁通過RouteSetting對象獲取路由參數:
class ParametersRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
//獲取路由參數
var args = ModalRoute.of(context).settings.arguments
//...省略無關代碼
}
}
在打開路由時傳遞參數
onPressed: () {
//導航到一個新的路由頁面
//Navigator.pushNamed(context, "third_page");
Navigator.of(context).pushNamed("parameters_page",arguments:"命名路由傳遞的參數");
},
指定給 parameters_page路由 傳遞參數 "命名路由傳遞的參數"
更加常見的,是弄個路由表
本部分和示例無關。
- 提供一個路由表,這是一個Map,是字符串和WidgetBuilder的對應關系。比如:
/// 路由表
final Map<String, WidgetBuilder> routeTable = {
'/' : (content) => Home(),
'/page1' : (content) => Page1(),
'/page2' : (content) => Page2(),
'/page3' : (content) => Page3(),
};
- 把這個路由表放在MaterialApp的routes參數中
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routeTable,
);
}
}
- 使用的例子如下,比如跳轉到Page1頁面:
onPressed: (){
Navigator.of(context).pushNamed('/page1');
}
一個示例代碼
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: '路由測試',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
routes: {
// 注冊路由
"home": (context) => MyHomePage(),
"parameters_page": (context) => ParametersRoute(),
},
home: new MyHomePage(title: '路由測試'),
);
}
}
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>[
FlatButton(
child: Text(
"攜帶參數打開新頁面",
style: TextStyle(fontWeight: FontWeight.bold),
),
textColor: Colors.blue,
onPressed: () {
//導航到一個新的路由頁面
// Navigator.pushNamed(context, "third_page");
Navigator.of(context)
.pushNamed("parameters_page", arguments: "命名路由傳遞的參數");
},
)
],
),
),
);
}
}
class ParametersRoute extends StatelessWidget {
final Topic = Text("路由測試");
@override
Widget build(BuildContext context) {
// TODO: implement build
var args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text("路由測試"),
),
body: new ListView(
children: <Widget>[
Center(
child: Text("路由到此頁面獲取到的數據為:\n\n" + args),
),
],
)
);
}
}
.
.
四、onGenerateRoute 攔截器
假設我們要開發一個電商APP,當用戶沒有登錄時可以看店鋪、商品等信息.
但交易記錄、購物車、用戶個人信息等頁面需要登錄后才能看。為了實現上述功能,我們需要在打開每一個路由頁前判斷用戶登錄狀態!如果每次打開路由前我們都需要去判斷一下將會非常麻煩,那有什么更好的辦法嗎?答案是有! —— onGenerateRoute
- onGenerateRoute 可以做攔截器
- onGenerateRoute可以變相的接受參數
- 可以在onGenerateRoute()函數中提取參數并將它們傳遞給widget,而不是直接在窗口小部件中提取參數。
- 注意,onGenerateRoute只會對命名路由生效
.
.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 提供處理命名路由的函數。使用此功能可識別要推送的命名路徑,并創建正確的頁面。
onGenerateRoute: (settings) {
// 如果您要 打開 PassArguments 路由
if (settings.name == PassArgumentsScreen.routeName) {
// 將參數轉換為正確的類型:ScreenArguments。
final ScreenArguments args = settings.arguments;
// 從參數中提取所需數據并將數據傳遞到正確的屏幕。
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
title: 'Navigation with Arguments',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// A button that navigates to a named route that. The named route
// extracts the arguments by itself.
RaisedButton(
child: Text("帶參數 push 方式"),
onPressed: () {
// When the user taps the button, navigate to the specific route
// and provide the arguments as part of the RouteSettings.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
// Pass the arguments as part of the RouteSettings. The
// ExtractArgumentScreen reads the arguments from these
// settings.
settings: RouteSettings(
arguments: ScreenArguments(
'tag1 來自HomeScreen的 push',
'tag1 這個消息將被 build 方法 提取',
),
),
),
);
},
),
// A button that navigates to a named route. For this route, extract
// the arguments in the onGenerateRoute function and pass them
// to the screen.
RaisedButton(
child: Text("帶參 pushNamed onGenerateRoute 方式"),
onPressed: () {
// When the user taps the button, navigate to a named route
// and provide the arguments as an optional parameter.
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'tag2 來自HomeScreen的 pushNamed',
'tag2 這個消息來自 將被 onGenerateRoute 函數提取',
),
);
},
),
],
),
),
);
}
}
// 一個Widget,它從ModalRoute中提取必要的參數。
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
// 從當前ModalRoute設置中提取參數并將其轉換為ScreenArguments。
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
// Widget,通過構造函數接受必要的參數。
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
// 此Widget接受參數作為構造函數參數。它不從ModalRoute中提取參數。
//
// 參數由提供給MaterialApp小部件的onGenerateRoute函數提取。
const PassArgumentsScreen({
Key key,
@required this.title,
@required this.message,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
// 您可以將任何對象傳遞給arguments參數。在此示例中,創建一個包含可自定義標題和消息的類。
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
這是官方的例子,稍微改了點文字
.
.
END
.
.
.