# Flutter教程 - 6 路由
什么是路由?
其实就是页面跳转。
Flutter 中的路由系统基于导航栈来管理不同页面的顺序和状态。
什么是导航栈?
导航栈(Navigation Stack)是在应用程序中用于管理页面或路由的一种数据结构。它类似于堆栈(Stack)数据结构,遵循先进后出(LIFO)的原则。
在 Flutter 中,导航栈用于存储打开的页面或路由,每当你打开一个新的页面或路由时,它会被推入(push)到导航栈的顶部。因为系统当前显示的页面就是当前在导航栈顶的的页面。当你执行返回操作时,最顶部的页面会被弹出(pop)出栈,回到上一个页面。
在 Flutter 中,Navigator
类负责管理导航栈,并提供了管理堆栈的方法。你可以使用 Navigator
的方法来推入新页面、弹出当前页面、查看当前栈中的页面等。导航栈在应用程序中起着重要的作用,它允许你实现页面之间的流畅切换、返回操作以及状态管理。
以下是 Navigator
类提供的一些常用的方法,了解一下,后面逐一介绍一下如何使用。
- 将新页面推入导航栈,实现页面切换。
Navigator.push(context, MaterialPageRoute(builder: (context) => NewPage()));
- 将当前页面弹出导航栈,返回上一个页面。
Navigator.pop(context);
- 替换当前页面为新页面,从而实现页面切换并且移除当前页面。
Navigator.pushReplacementNamed(context, "/message");
- 将导航栈中的页面依次弹出,直到某个指定页面。
Navigator.popUntil(context, ModalRoute.withName('/home'));
- 检查是否可以执行返回操作(即是否还有页面可以弹出)。
Navigator.canPop(context)
Flutter
中给我们提供了两种配置路由跳转的方式:
- 基本路由
- 命名路由
下面学习一下两种路由的使用,以及如何使用 Navigator
类提供的方法进行页面的跳转。
# 6.1 基本路由
下面实现一个从一个页面跳转到另一个页面,并返回的功能,并可以根据需要传递参数。
# 1 页面跳转
首先准备好两个页面,这里分别是 main.dart
和 search.dart
。
main.dart
在 main.dart
中添加一个按钮,点击按钮,跳转到 search.dart
页面。
import 'package:flutter/material.dart';
import './search.dart';
void main() => runApp(const MyApp());
/// App根Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MainPage(),
);
}
}
/// 主页
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("主页"),
),
body: Center(
// --------------添加一个按钮-----------------
child: ElevatedButton(
onPressed: () {
// 点击按钮,跳转到search页面
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const SearchPage();
}));
},
child: const Text("跳转到search")
)
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
首先在 main.dart
中引入 search.dart
,在按钮的点击事件中,通过 Navigator.push
方法跳转到 SearchPage
页面,Navigator.push
接收两个参数,一个是上下文对象,一个是路由对象。路由对象接收一个参数 builder
是一个函数,在函数中返回要跳转到的新的页面 Widget
。
这里页可以使用箭头函数来写:
// 点击按钮,跳转到search页面
Navigator.push(context, MaterialPageRoute(builder: (context) => const SearchPage()));
2
search.dart
search.dart
页面很简单,就是使用 Scaffold
创建了一个简单的页面。
import 'package:flutter/material.dart';
/// 搜索页面
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("搜索"),
),
body: const Center(
child: Text("搜索页面"),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
显示效果,当点击 跳转到search
按钮的时候,会跳转到搜索页面。
# 2 跳转传递参数
刚才在进行页面跳转的时候,没有传递参数,现在演示一下从 main.dart
跳转到 search.dart
,并传递参数。
传递参数是很简单的,因为 SearchPage
有构造函数,只需要传递参数给构造函数即可,在 SearchPage
中就可以拿到参数了。
search.dart
import 'package:flutter/material.dart';
/// 搜索页面
class SearchPage extends StatefulWidget {
final String content;
// ----- 通过构造函数接收参数
const SearchPage({Key? key, required this.content}) : super(key: key);
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("搜索"),
),
body: Center(
// ----- 将接收到的参数显示出来
child: Text(widget.content),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在 SearchPage
中通过构造方法接收传递参数,在 _SearchPageState
中获取参数需要通过 widget.属性
的方式获取。
main.dart
import 'package:flutter/material.dart';
import './search.dart';
void main() => runApp(const MyApp());
/// App根Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MainPage(),
);
}
}
/// 主页
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("主页"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// ----- 创建SearchPage的时候传递参数
Navigator.push(context, MaterialPageRoute(builder: (context) => const SearchPage(content: "你好,逗比")));
},
child: const Text("跳转到search")
)
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
在创建 SearchPage
的时候,传递参数即可。
# 3 页面返回
从 main.dart
跳转到 search.dart
,如何返回呢?
从一个页面跳转到下一个页面,默认情况下,下一个页面的左上角会显示一个返回按钮,这个时候,点击返回按钮就可以直接返回了。
但是有些时候,我们不想使用返回按钮,而是主动去触发返回,那么就需要使用 Navigator.pop
来返回了。
修改 search.dart
import 'package:flutter/material.dart';
/// 搜索页面
class SearchPage extends StatefulWidget {
final String content;
const SearchPage({Key? key, required this.content}) : super(key: key);
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("搜索"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.content),
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
// ----- 返回到上一个页面
Navigator.pop(context);
},
child: const Text("返回")
)
],
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在当前页面,返回上一个页面,只需要调用 Navigator.pop(context)
,就可以在当前的栈中弹出当前页面,返回到上一个页面。
# 6.2 命名路由
前面介绍了基本路由的使用。但是如果应用页面很多,使用基本路由会很不方便,一个是写法不方便,另外就是不好统一管理,每个用到跳转的地方都需要引入跳转的页面。
下面介绍命名路由的使用,很好的的解决了上面的问题。
首先新建三个页面 HomePage
、SearchPage
、MessagePage
(页面代码在后面)。
然后实现在 HomePage
中通过按钮分别跳转到SearchPage
和 MessagePage
页面。
# 1 配置路由
在使用命名路由之前,需要统一配置路由。命令路由的配置是在 MaterialApp
中的。
代码如下:
main.dart
import 'package:flutter/material.dart';
// 1.引入所有的页面
import './pages/home.dart';
import './pages/search.dart';
import './pages/message.dart';
void main() => runApp(const MyApp());
/// App根Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// ---- 3.初始化路由,初始化进入的页面
initialRoute: "/",
// ---- 2.所有的路由统一配置到这里
routes: {
'/': (context) => const HomePage(),
'/search': (context) => const SearchPage(),
'/message': (context) => const MessagePage(),
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 首先将
MaterialApp
中的home
属性去掉; - 在
main.dart
中引入所有的页面的页面; - 在
MaterialApp
组件中,使用routes
属性配置所有的路由,routes
是一个map
类型,value
是一个函数; - 还需要使用
initialRoute
属性配置初始化的路由。上面初始化的路由是"/"
, 表示初始化的页面是HomePage
。
下面展示一下 SearchPage 、MessagePage 页面的内容:
search.dart
页面非常简单,只有一个返回按钮,用于返回。
import 'package:flutter/material.dart';
/// 搜索页面
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("搜索"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// ----- 返回到上一个页面
Navigator.pop(context);
},
child: const Text("返回")
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
message.dart
页面非常简单,只有一个返回按钮,用于返回。
import 'package:flutter/material.dart';
class MessagePage extends StatefulWidget {
const MessagePage({Key? key}) : super(key: key);
State<MessagePage> createState() => _MessagePageState();
}
class _MessagePageState extends State<MessagePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("消息"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// ----- 返回到上一个页面
Navigator.pop(context);
},
child: const Text("返回")
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 2 页面跳转
在 HomePage
中通过按钮分别跳转到SearchPage
和 MessagePage
页面,下面看一下 home.dart
的代码,也是非常的简单。
home.dart
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("主页"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// ----- 跳转到search页面
Navigator.pushNamed(context, "/search");
},
child: const Text("跳转到search")
),
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
// ----- 跳转到message页面
Navigator.pushNamed(context, "/message");
},
child: const Text("跳转到message")
)
],
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
直接就是在页面上添加了两个按钮,点击按钮分别跳转到响应的页面。
跳转的时候使用 Navigator.pushNamed()
方法来跳转,第一个参数是上下文对象,第二个参数直接写在 MaterialApp
中配置的路由的名字即可。无需引入页面,在哪里都可以用,直接就可以跳转,灰常的方便。
# 3 传递参数
传递参数要稍微复杂一些。
下面实现跳转到搜索页面传递参数。
主要分为三步:
- 接收参数配置
- 路由配置
- 跳转传递参数
接收参数
搜索页面接收参数,所以这里我们先修改 SearchPage ,修改构造函数,接收参数。
import 'package:flutter/material.dart';
/// 搜索页面
class SearchPage extends StatefulWidget {
final Map arguments;
// ---- 使用 arguments 接收参数
const SearchPage({Key? key, required this.arguments}) : super(key: key);
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("搜索"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.arguments["content"]),
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
// ----- 返回到上一个页面
Navigator.pop(context);
},
child: const Text("返回")
)
],
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在上面的代码中,定义了一个 Map
类型的成员变量,用于接收参数。
路由配置
首先在 MaterialApp
中去掉 routes
属性,然后添加 onGenerateRoute
属性。onGenerateRoute
接收的是一个函数,先按照下面的固定写法写就好了。将路由配置提取成一个变量,在 onGenerateRoute
中会用到。
import 'package:flutter/material.dart';
import './pages/home.dart';
import './pages/search.dart';
import './pages/message.dart';
void main() => runApp(const MyApp());
// 1、定义Map类型的routes
final Map routes = {
'/': (context) => const HomePage(),
'/search': (context, {arguments}) => SearchPage(arguments: arguments),
'/message': (context) => const MessagePage(),
};
/// App根Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// 初始化路由,初始化进入的页面
initialRoute: "/",
// 2、调用onGenerateRoute处理
onGenerateRoute: (RouteSettings settings) {
// 统一处理
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route = MaterialPageRoute(
builder: (context) => pageContentBuilder(context));
return route;
}
}
return null;
},
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
在上面的代码中提取了 routes
变量,因为跳转到 SearchPage
页面携带参数,所以在路由配置中,在函数中配置了一个命名参数 arguments
,并将 arguments
传递给了 SearchPage
页面。
上面的流程看起来比较闷逼,待会再解释。
跳转传递参数
现在从 HomePage
跳转到 SearchPage
传递参数,
传递参数很简单,只需要在 HomePage
跳转的时候,通过 arguments
参数传递参数即可。
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("主页"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// ----- 跳转到search页面,传递参数
Navigator.pushNamed(context, "/search", arguments: {"content":"你好,逗比"});
},
child: const Text("跳转到search")
),
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
// ----- 跳转到message页面
Navigator.pushNamed(context, "/message");
},
child: const Text("跳转到message")
)
],
),
),
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4 传递参数流程
- 首先在跳转的时候使用
Navigator.pushNamed
跳转并传递了参数; - 跳转会调用
onGenerateRoute
配置的函数。
(RouteSettings settings) {
// name 这里会拿到路由的名称,也就是/search
final String? name = settings.name;
// 通过/search,在路由配置字典中拿到对应的函数(context, {arguments}) => SearchPage(arguments: arguments)。
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
// 查看跳转有没有携带参数
if (settings.arguments != null) {
// 如果携带了参数,则调用路由配置中对应的函数,将参数传递进去,也就是调用了SearchPage中的构造函数,最终创建了路由对象
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
// 如果跳转没有携带参数,调用pageContentBuilder不带参数
final Route route = MaterialPageRoute(
builder: (context) => pageContentBuilder(context));
return route;
}
}
return null;
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
路由跳转都会走 onGenerateRoute
函数,所以如果要进行一些拦截操作,可以在 onGenerateRoute
中进行处理。
SearchPage
接收到参数。
# 5 重构代码
在上面的代码中,我们在 main.dart
中将路由配置定位为一个 Map
对象,其实这样代码混乱不利于管理,我们可以将路由配置和onGenerateRoute,抽离到一个单独的文件。
例如,新建一个 router.dart
,代码如下:
首先引入路由的各个页面文件,然后定义路由配置和 onGenerateRoute
函数。
import 'package:flutter/material.dart';
import '../pages/home.dart';
import '../pages/search.dart';
import '../pages/message.dart';
// 1、定义Map类型的routes
final Map routes = {
'/': (context) => const HomePage(),
'/search': (context, {arguments}) => SearchPage(arguments: arguments),
'/message': (context) => const MessagePage(),
};
var onGenerateRoute = (RouteSettings settings) {
// 统一处理
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route = MaterialPageRoute(
builder: (context) => pageContentBuilder(context));
return route;
}
}
return null;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
重新修改 main.dart
import 'package:flutter/material.dart';
// 引入路由配置
import './router/router.dart';
void main() => runApp(const MyApp());
/// App根Widget
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// 初始化路由,初始化进入的页面
initialRoute: "/",
// onGenerateRoute处理
onGenerateRoute: onGenerateRoute,
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
代码结构就很清晰了,也显得很简洁了。
# 6.3 替换路由
如果从 HomePage 页面跳转 SearchPage 页面,从 SearchPage 页面跳转到 MessagePage 页面,在 MessagePage 页面点击返回,如何能返回到 HomePage 页面呢?
在 MessagePage 页面通过 Navigator.pop(context);
直接返回 HomePage
页面。
这里只需要在 SearchPage
通过 Navigator.pushReplacementNamed()
方法跳转到 MessagePage
页面接口,MessagePage
会替换掉栈顶的 SearchPage
页面。
// 在 SearchPage 页面跳转到 MessagePage
Navigator.pushReplacementNamed(context, '/message');
2
这样在 MessagePage
页面返回的时候,会直接返回到 HomePage
页面。
# 6.4 返回根路由
如果现在的导航栈是下面的这样的情况,该如何跳转到根路由的首页呢?
我们可以使用 Navigator.pushNamedAndRemoveUntil()
方法,它用于实现页面导航并清除导航栈中的页面,以达到指定条件的效果。具体而言,它的作用如下:
- 首先,它会压入(导航到)一个新的命名路由页面到导航栈。
- 压入新页面之前,它会从导航栈中删除所有指定条件之前的页面,以确保只剩下符合条件的页面。
// ----- 返回首页
Navigator.pushNamedAndRemoveUntil(
context,
'/', // 跳转到的页面名称
(route) => false, // 始终返回false,表示一直删除,删除所有页面
);
2
3
4
5
6
上面的代码会跳转到 "/"
首页,第三个参数是一个回调函数,用于确定是否删除页面。当返回 true
时,删除停止,页面保留在导航栈中。上面一直返回false,则会清空导航栈。
再举一个例子:
从 /
跳转到 /a
,从 /a
跳转到 /b
,从 /b
跳转到 /d
,在 /d
中如何返回 /a
,在 /a
通过 Navigator.pop
返回到 /
,那么在 /d
中该如何返回到 /a
呢?
Navigator.pushNamedAndRemoveUntil(
context,
'/a', // 返回到/a页面
ModalRoute.withName('/'), // 删除中间的页面,直到根页面
);
2
3
4
5
# 6.5 页面切换风格
我们之前使用的风格是 Material
库的风格,Material
组件库中路由的切换是通过 MaterialPageRoute
组件实现的,MaterialPageRoute
组件会根据 Android
和 iOS
上显示不同的风格。例如在切换页面的时候,在 Android
上是上下滑动切换,iOS 是左右滑动切换。
如果想在 Android
上实现和 iOS
上一样的切换效果,可以使用 Cupertino
库,其中 CupertinoPageRoute
是 Cupertino
组件库提供的iOS
风格的路由切换组件。
所以修改之前的路由配置即可,也是很简单的,需要修改两个地方:
- 引入
cupertino.dart
,删除material.dart
; - 将
MaterialPageRoute
替换为CupertinoPageRoute
;
// 1.配置iOS分隔的路由,删掉material.dart,引入cupertino.dart
import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
import '../pages/home.dart';
import '../pages/search.dart';
import '../pages/message.dart';
// 1、定义Map类型的routes
final Map routes = {
'/': (context) => const HomePage(),
'/search': (context, {arguments}) => SearchPage(arguments: arguments),
'/message': (context) => const MessagePage(),
};
var onGenerateRoute = (RouteSettings settings) {
// 统一处理
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
// 2. 将MaterialPageRoute替换为
final Route route = CupertinoPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route = CupertinoPageRoute(
builder: (context) => pageContentBuilder(context));
return route;
}
}
return null;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33