欢迎点赞,转载请注明出处
本节示例项目源代码下载点击这里 flutter_route
一个简单的路由
在Flutter中,屏(screen)和页面(page)都叫做路由(route)。在Android开发中,Activity相当于“路由”,在iOS开发中,ViewController 相当于“路由”。在Flutter中,“路由”也是一个Widget。使用Navigator类,可以实现Flutter的路由之间的跳转。
使用Navigator.push()方法跳转到新的路由。push()方法会添加一个Route对象到导航器的堆栈上。可以直接使用MaterialPageRoute或CupertinoPageRoute类。
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SomeWidget()),
);
其中build参数为WidgetBuilder函数自定义类型:Widget Function(BuildContext context)。
使用Navigator.pop() 方法会从导航器堆栈上移除 Route对象。
一个简单的官方路由跳转示例如下:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
构造函数传参
大多数时候,路由之间跳转需要传递参数。最简单的方式,我们只需要在目的路由的构造函数增加入参接收要传递的数据即可,在上面例子的基础上,我们做了一点修改,修改的部分见下图代码红框部分:

RouteSetting传参
我们还可以使用RouteSetting的方式,在目的路由使用ModalRoute.of(context).settings.arguments获取传递的参数。路由跳转时,在MaterialPageRoute增加RouteSettings入参类型,将要传递的入参赋值给它的arguments参数。在第一个例子的基础上,我们做了一点修改,修改的部分见下图代码红框部分:

命名路由
如果在一个App的不同页面需要导航到同一路由时,每次都需要调用重复的Navigator.push代码段。我们可以定义一种命名路由来简化这一操作。
命名路由需要在MaterialApp或Cupertino构造函数中定initialRoute和routes参数。
initialRoute定义了App第一个进入的页面。 routes属性定了可用的命名路由集合,使用Navigator.pushNamed()调用的方式,代替Navigator.push()方式,pushNamed方法第2个参数对应。第一个例子改成命名路由的方式代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Named Route',
initialRoute: '/',
routes: {
'/': (context) => FirstRoute(),
'/second': (context) => SecondRoute(),
},
));
}
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.pushNamed(
context,
'/second',
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
原先示例中的,MaterialApp构造函数的home参数,对应的"/"路由,我们可以把上例的MaterialApp构造函数改为如下方式,运行效果与修改前是一样的。
MaterialApp(
title: 'Named Route',
home: FirstRoute(),
routes: {
'/second': (context) => SecondRoute(),
},
)
initialRoute值一般为'/',当然也可以指定一个具体的路由名称。routes参数里则必须定义 '/',因为initialRoute路由名无效时,应用会自动匹配'/'指向的路由。示例代码如下:
initialRoute: '/first',
routes: {
'/': (context) => FirstRoute(),
'/first': (context) => FirstRoute(),
'/second': (context) => SecondRoute(),
},
路由返回到首页同样使用的是Navigator.pop(context),也可以使用下面的代码返回到首页。如果你了解Stack堆栈数据结构的概念的话,这两者之间的差异是显然易见的:
Navigator.pushNamed(
context,
'/first',
);
Navigator类并没有公开查询堆栈数组的属性或方法,如果你有兴趣的话,可以通过调试模式下,观察Navigator.of(context)的_history变量:

命名路由传参
你同样可以使用构造函数和RouteSetting的类似方式为命令路由传参。pushName可以直接使用arguments命名参数传参,而不需要RouteSetting进行参数包裹,代码类似如下:
Navigator.pushNamed(
context,
'/second',
arguments: times++
);
对于命名路由还有另外一种处理的方式,在MaterialApp或CupertinoApp构造函数里使用onGenerateRoute()函数来构建目的路由,同时对路由的参数进行赋值处理,代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Named Route',
initialRoute: '/',
routes: {
'/': (context) => FirstRoute()
},
onGenerateRoute: (settings) {
if (settings.name == '/second') {
final _times = settings.arguments;
return MaterialPageRoute(
builder: (context) {
return SecondRoute(
_times
);
},
);
}else{
throw "mismatch route";
}
},
));
}
class FirstRoute extends StatelessWidget {
var times = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.pushNamed(
context,
'/second',
arguments: times++
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
var pushed;
SecondRoute(this.pushed);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("#$pushed opend"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
观察上面的代码可以看到,当routes里没有'/second'路由定义时,则系统会去onGenerateRoute函数里进行匹配处理 if (settings.name == '/second'),如果onGenerateRoute函数里也匹配不到,则抛出异常。
路由数据返回
我们有时候希望路由跳转返回后,能够从上一个路由返回一些数据进行逻辑处理,在Android里可以使用startActivityForResult()方法,Flutter写法如下:
mport 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Route return data',
home: FirstRoute(),
));
}
class FirstRoute extends StatefulWidget {
@override
FirstState createState() => FirstState();
}
class FirstState extends State<FirstRoute> {
var dateInfo;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text(dateInfo??="请问现在几点了?"),
onPressed: () async {
var _return = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
setState(() {
dateInfo = _return+"点";
});
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context, DateTime.now().hour.toString());
},
child: Text('点我'),
),
),
);
}
}
为了功能演示需要,我们把FirstRoute改为StatefulWidget类型,在onPressed事件里,使用await Navigator.push的方式进行路由跳转。查看Navigator.push函数官方api说明,可以看到push的返回值实际上Future<T>类型:
@optionalTypeArgs
Future<T> push <T extends Object>(
BuildContext context,
Route<T> route
)
上例的T代表就是String类型。关键字await表示同步等待一个过程的完成,此处就是等待Navigator的pop事件的完成,pop事件的第2个参数作为Future的完成值返回。await必须在async异步函数中执行,因此,我们在onPressed: ()后增加了async修饰符。上述示例的代码运行效果如下图,其中左图代表App运行的初始状态,中图代表导航到第2个路由后的状态,右图代表从第二个路由返回到第一个路由的效果,可以看到DateTime.now().hour.toString()返回给_return变量,并通过setState()更新result达到同步更新界面的效果:

路由跳转动画
MaterialPageRoute或CupertinoPageRoute类控制的路由之间跳转风格是固定的。我们可以使用push函数的第2个参数PageRouteBuilder自定义路由跳转的动画效果,你需要了解一些动画基本原理和Flutter动画相关语法,但这并不在本教程讨论范围之内。你有兴趣的话,可以运行下面代码,观察下它的跳转动画效果,并展开相关的深入学习。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Animation',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (c, a1, a2) => SecondRoute(),
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 2000),
),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
实验九
在实验八的基础上,考虑增加以下功能:
- 登录页面增加一个同意协议功能,点击登录页阅读协议按钮导航到新页面,展示一个协议文本Demo,底部设置‘接受’和'拒绝'2个按钮;
- 接受协议,且账号和密码正确,点击登录按钮导航到新的页面,新页面上显示账号名;
- 在新的页面增加一个注销按钮,点击后,返回登录首页。
上一篇 UI交互控制 下一篇 Widget状态和应用数据管理