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
.
.
.