路由与导航
在原生的Android中,一个Activity称为一个路由,在iOS中指的是一个ViewController。如果需要打开一个新的路由,在Android中可以使用startActivity()方法,并且传入参数。在iOS中可以使用pushViewController()。
而在Flutter中,使用了Router与Navigator来组成统一的管理,Router是页面的一个抽象概念,用这个可以创建界面、接收参数以及响应Navigator的打开和关闭;而Navigator则是用于管理和维护路由栈,打开路由页面或者关闭路由页面,即入栈出栈操作。
在Flutter中,路由分为两种,一种是基本路由,一种是命名路由。
- 基本路由:无需提前注册,在切换页面的时候需要手动构造页面的实例。
- 命名路由:需要提前注册路由页面标识符,在页面切换时通过路由标识符打开一个新的路由页面。
基本路由
如果需要打开一个新的页面,需要创建一个MaterialPageRoute对象实例,然后调用Navigator.push(),那么Flutter就会把新跳转的页面放到栈顶。如果需要关闭新的页面,那么使用Navigator.pop()即可。一个样例代码如下:
first_page.dart
import 'package:flutter/material.dart';
import 'second_page.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("first page"),),
body: Center(
child: TextButton(
child: Text("turn to second page"),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => SecondPage())),
),
),
);
}
}
second_page.dart
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("second page"),),
body: Center(
child: TextButton(
child: Text("back to first page"),
onPressed: () => Navigator.pop(context),
),
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_router_page/first_page.dart';
void main(List<String> args) => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: FirstPage(),);
}
}
这样就可以做成一个简单的路由跳转了。创建新的路由对象使用的是MaterialPageRoute类,该类是PageRoute的子类,定义了路由创建及切换时过渡动画的相关接口和属性,并且自带页面切换动画。
命名路由
为了必要频繁的创建MaterialPageRoute实例,可以使用命名路由的方式。在命名路由中,需要提前创建好映射规则,即路由表。对应路由表中,第一个参数对应页面的名字,第二个参数对应页面,且需要以MaterialApp为根。
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_namerouter_page/first_page.dart';
import 'package:flutter_namerouter_page/second_page.dart';
import 'package:flutter_namerouter_page/unknown_page.dart';
void main(List<String> args) => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
/// 路由表结构
routes: {
'first': (context) => FirstPage(),
'second': (context) => SecondPage(),
},
/// 初始化页面
initialRoute: 'first',
/// 路由表中没有的页面会跳转到的地方
onUnknownRoute: (RouteSettings setting) =>
MaterialPageRoute(builder: (context) => UnKnownPage()),
);
}
}
first_page.dart
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("first page"),
),
body: Center(
child: TextButton(
child: Text("turn to second page"),
onPressed: () => {Navigator.pushNamed(context, "second")},
),
),
);
}
}
second_page.dart
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("second page"),
),
body: Center(
child: Column(
children: [
TextButton(
child: Text("back to first page"),
onPressed: () => {Navigator.pop(context)},
),
TextButton(
child: Text("turn to first page"),
onPressed: () => Navigator.pushNamed(context, 'first'),
),
TextButton(
onPressed: () => Navigator.pushNamed(context, 'third'),
child: Text("turn to third page")),
],
)),
);
}
}
unknown.dart
import 'package:flutter/material.dart';
class UnKnownPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("unknown page"),
),
body: Center(
child: TextButton(
child: Text("unknown page"),
onPressed: () => Navigator.pop(context),
),
),
);
}
}
路由传参
在Flutter中,路由类似于Activity一样。在Android中进行页面传参,使用的是Intent进行传递,然后使用startActivityForResult()进行传参。
在Flutter中,使用Navigator.of(context).pushNamed('页面名称', arguments: 传递的数据).then((value){如果是StatefulWidget的话,那么可以在此使用setState进行设置。})
在第二个页面中,使用 ModalRoute.of(context).settings.arguments
获取页面传输过来的参数,类似于Android中的getIntent() ,然后进行转换。在第二个页面中,可以使用 Navigator.pop(context,'传递的参数')
路由栈
在Android中,Activity具有四种不同的启动模式,standard,singleTask,singleTop,singleInstance。在Flutter中,使用了安卓中的启动模式的管理方式。
在Flutter中,可以单独移除路由栈中的一个特定的页面。使用Navigator.removeRoute() & Navigator.removeRouteBelow()
进行移除。
方式1: push() & pushNamed() => pop()
这种方式类似于Android中的standard的模式,模型图如下:
使用push或者pushNamed后的模型图
使用pop后的模型图
方式2: pushReplacement() & pushReplacementNamed()
这种方式用在路由栈顶页面的替换场景,比如栈顶是b,想替换成c,那么使用该函数即可。类似于Android中先把栈顶弹出,然后再放入c路由。这种模型的方式所展现的动画仅仅是路由c的进入动画,并没有b的退出动画。模型图如下:
方式3: popAndPushNamed()
这种方式类似于方式2,但是在动画方面有不一样的地方。使用这种方式,在b弹出的时候,会显示b的弹出动画,而c进入的时候,又回显示c的进入动画。比方式2多一个弹出动画。
方式4: pushAndRemoveUntil() & pushNamedAndRemoveUntil()
从路由栈中,新添加一个页面,并且删除路由栈中所有之前的路由。也就是说,使用这种方式进行路由的添加,路由栈中仅有一个路由。也可以使用这个方法,进行打开一个新的页面,并且制定路由栈中的某一个页面以上的路由页面都删除。
比如执行这条语句Navigator.pushNamedAndRemoveUtil(context,'page_e',ModalRoute.withName('page_b'))
意思是打开一个d页面,然后删除b页面以上的页面,示意图如下:
方式5: popUntil()
一直弹出,直到某一个页面位置,没有push的操作。Navigator.popUtil(context,ModalRoute.withName('page_a'))
自定义路由
如果在两个路由之间跳转,需要自定义动画,那么会使用到自定义路由。自定义路由需要继承PageRouteBuilder类。该类是所有自定义路由的基类。在PageRouteBuilder中,有几个属性比较重要,如下:
编号 | 名称 | 作用 |
---|---|---|
1 | pageBuilder | 用来创建所需要跳转的路由页面 |
2 | opaque | 是否需要遮挡整个页面 |
3 | transitionsBuilder | 用于自定义专场动画 |
4 | transitionDuration | 自定义专场动画的执行时间 |