# 路由管理

本文作者:阳九五 (opens new window)

本站地址:https://blog.56321654.xyz (opens new window)

导航器通过维护内部的一个栈(stack,数据结构,不是Stack组件)来管理各个页面组件,并通过调用Overlay悬浮的方式,将多个页面叠加,以保证最新的页面显示在最顶层。

实战中很少需要自己创建Navigator组件。因为一般Flutter程序的根组件(如MaterialApp)会自动创建一个导航器,因此实战中只需使用Navigator.of(context)方法查找组件树中的父级Navigator

# 匿名路由

主要通过Push()Pop()来操作路由

  • Navigator路由管理对象
    Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。
    Future push(BuildContext context, Route route) 压入一个新页面到路由堆栈
    bool pop(BuildContext context, [ result ])压出一个页面出路由堆栈

  • MaterialPageRoute 定义
    MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。

MaterialPageRoute({
    // 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。
    WidgetBuilder builder,
    
    // 包含路由的配置信息,如路由名称、是否初始路由(首页)。
    RouteSettings settings,
    
    // 默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为 false。
    bool maintainState = true,
    
    // 表示新的路由页面是否是一个全屏的模态对话框,在 iOS 中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
    bool fullscreenDialog = false,
})
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 路由传值
    传递可以在初始新界面对象时通过构造函数压入
    新界面退出后的返回值通过 Navigator.pop 的参数返回

  • 示例
    首页NavPaged

import 'package:flutter/material.dart';

class NavPaged extends StatelessWidget {
  const NavPaged({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('NavPaged'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            var result = await Navigator.push(
              context,
              MaterialPageRoute(builder: (context) {
                return const DetailPaged(
                  title: "ducafecat",
                );
              }),
            );

            print("路由返回值: $result");
          },
          child: const Text("Navigator.push DetailPage"),
        ),
      ),
    );
  }
}
1
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

详情页 DetailPaged

class DetailPaged extends StatelessWidget {
  const DetailPaged({Key? key, this.title}) : super(key: key);

  // 参数
  final String? title;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DetailPaged'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 按钮
            OutlinedButton(
              onPressed: () {
                Navigator.pop(context, "任意类型的返回值");
              },
              child: const Text('Back'),
            ),
            // 显示传值
            Text(title ?? ""),
          ],
        ),
      ),
    );
  }
}
1
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

# 命名路由

当程序的页面数量较多时,应考虑使用命名路由,这样可以更方便地切换到某一页面或连续跳转多页。

# 路由表

由于大部分Flutter程序会直接使用根组件(如MaterialApp)自动创建的导航器,开发者很少会手动创建Navigator组件,因此MaterialAppCupertinoApp等根组件也都提供了routes参数,以便迅速地为整个程序创建路由表。

void main(){
    runApp(MaterialApp(
        home: MyHomePage(), // 根目录,即"/"路径
        routes: <String, WidgetBuilder>{
            '/a': (BuildContext) => MyPage(title: 'Page A'),
            '/b': (BuildContext) => MyPage(title: 'Page B'),
            '/c': (BuildContext) => MyOtherPage(),
        },
    ));
}
1
2
3
4
5
6
7
8
9
10

需要打开页面时可调用导航器的pushNamed方法,代码如下:

Navigator.of(context).pushNamed("/b");
1

Navigator会根据路由表,查询到"/b"路径所对应的是MyPage这个组件(与路径"/a"相同,提高了代码的复用性),且应向其title参数传入'Page B'。同理,若调用pushNamed("/c")则会打开MyOtherPage组件

# 生成路由

对于更复杂的路由情况可使用onGenerateRoute方法,并根据传入的RouteSettings类,动态生成路由。

RouteSettings属性 说明
name 路由名称
arguments 附加参数

其中arguments对应的是导航器的pushNamed方法所支持的可选arguments参数。

生成路由代码

void main(){
    runApp(MaterialApp(
        home: MyHomePage(),
        onGenerateRoute: (RouteSettings settings) {
          if( settings.name == "/user" ) {
            final args = settings.arguments as User;
            return MaterialPageRoute(builder: (_) => MyUserPage(user: args) );
          }
          return null;
        },
    ));
}
1
2
3
4
5
6
7
8
9
10
11
12

按钮部分代码

ElevatedButton(
  child: Text("Goto Yang"),
  onPressed: () => Navigator.of(context)
    .pushNamed("/user", arguments: User("Yang", 24));
)
1
2
3
4
5

由此可见,onGenerateRoute方式主要目的是配合arguments附加参数,解决普通路由表不易向新页面传附加参数的问题。

# 未知路由

当程序出现不存在的路径时,可使用onUnknownRoute方法,并根据传入的RouteSettings类,生成未知路由类似网站的404页面。

void main(){
    runApp(MaterialApp(
        home: MyHomePage(),
        onUnknownRoute: (RouteSettings settings) {
          return MaterialPageRoute(builder: (_)=> Text("404") )
        }
    ));
}
1
2
3
4
5
6
7
8

# 示例

import 'package:flutter/material.dart';

// 应用程序的入口函数
void main() {
  runApp(MaterialApp(
    // 设置应用的首页为 MyHomePage
    home: const MyHomePage(),
    // 定义命名路由
    routes: <String, WidgetBuilder>{
      '/a': (BuildContext context) => const MyPage(title: 'Page A'), // 路由 "/a" 映射到 MyPage,标题为 "Page A"
      '/b': (BuildContext context) => const MyPage(title: 'Page B'), // 路由 "/b" 映射到 MyPage,标题为 "Page B"
      '/page/other': (BuildContext context) => const MyOtherPage(),  // 路由 "/page/other" 映射到 MyOtherPage
    },
    // 动态路由生成器
    onGenerateRoute: (RouteSettings settings) {
      // 如果路由是 "/user",则解析参数并导航到 MyUserPage
      if (settings.name == "/user") {
        final User user = settings.arguments as User; // 获取传递的 User 参数
        return MaterialPageRoute(
          builder: (BuildContext context) => MyUserPage(user: user), // 返回 MyUserPage
        );
      }
      return null; // 如果未匹配,返回 null
    },
    // 未知路由处理
    onUnknownRoute: (RouteSettings settings) {
      // 如果路由未定义,显示 "404 Not Found"
      return MaterialPageRoute(
        builder: (BuildContext context) => const Text("404 Not Found"),
      );
    },
  ));
}

// 主页面 MyHomePage
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("路由演示"), // 应用标题
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 子组件均匀分布
          children: [
            // 按钮:通过命名路由打开 Page A
            ElevatedButton(
              child: const Text("命名打开 Page A"),
              onPressed: () => Navigator.pushNamed(context, "/a"),
            ),
            // 按钮:通过命名路由打开 Page B
            ElevatedButton(
              child: const Text("命名打开 Page B"),
              onPressed: () => Navigator.pushNamed(context, "/b"),
            ),
            // 按钮:通过命名路由打开 Other 页面
            ElevatedButton(
              child: const Text("命名打开 Other"),
              onPressed: () => Navigator.pushNamed(context, "/page/other"),
            ),
            // 按钮:直接通过 MaterialPageRoute 打开 Other 页面
            ElevatedButton(
              child: const Text("直接打开 Other"),
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(
                    builder: (BuildContext context) => const MyOtherPage()),
              ),
            ),
            // 按钮:通过命名路由打开 User 页面,并传递参数
            ElevatedButton(
              child: const Text("命令打开 User"),
              onPressed: () {
                final User user = User("阳九五", 18); // 创建一个 User 对象
                Navigator.of(context).pushNamed("/user", arguments: user); // 传递参数
              },
            ),
            // 按钮:直接通过 MaterialPageRoute 打开 User 页面,并传递参数
            ElevatedButton(
              child: const Text("直接打开 User"),
              onPressed: () {
                final User user = User("阳九五", 18); // 创建一个 User 对象
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (BuildContext context) => MyUserPage(user: user), // 传递参数
                  ),
                );
              },
            ),
            // 按钮:打开一个未知路由
            ElevatedButton(
              child: const Text("打开未知路由"),
              onPressed: () => Navigator.of(context).pushNamed("/unknown"),
            ),
          ],
        ),
      ),
    );
  }
}

// 页面 MyPage,显示传递的标题
class MyPage extends StatelessWidget {
  final String title;

  const MyPage({super.key, required this.title});

  
  Widget build(BuildContext context) {
    return Text(title); // 显示标题
  }
}

// 页面 MyOtherPage,显示固定文本
class MyOtherPage extends StatelessWidget {
  const MyOtherPage({super.key});

  
  Widget build(BuildContext context) {
    return const Text("My Other Page"); // 显示固定文本
  }
}

// 页面 MyUserPage,显示用户信息
class MyUserPage extends StatelessWidget {
  final User user;

  const MyUserPage({super.key, required this.user});

  
  Widget build(BuildContext context) {
    return Text("user: ${user.name}, age: ${user.age}"); // 显示用户的姓名和年龄
  }
}

// 用户类 User,包含姓名和年龄
class User {
  final String name;
  final int age;

  User(this.name, this.age);
}
1
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
最近更新: 8/6/2025, 2:39:35 PM