flutter:路由Route

  1. 什么是路由
  2. 匿名路由
    1. 一些概念
    2. 代码
  3. 命名路由
  4. onGenerateRoute 手动解析
  5. routes和onGenerateRoute对比

什么是路由

在计算机领域,**路由(Route)**最早来源于 “路径/导航” 的概念:

  • 网络里:路由器(Router)帮数据包找到路径。
  • Web 开发里:URL 与页面之间的对应关系叫路由。
  • Flutter 里:路由就是 一个页面(Screen/Page)的抽象,以及 页面之间跳转的规则

在Flutter里面,路由的基本概念

  • Route(路由)

    • 表示一个页面(Screen/Page)。
    • 在 Flutter 里,页面通常就是一个 Widget,比如一个 Scaffold,但必须由 Route 来管理。
  • Navigator(导航器)

    • 一个路由管理器,维护一个 栈结构(Stack)。
    • 入栈(push)= 打开新页面。
    • 出栈(pop)= 返回上一个页面。
  • MaterialApp

  • 提供路由表(routes)、初始路由(initialRoute)、未知路由处理(onUnknownRoute)等。

匿名路由

一些概念

匿名路由主要是通过 Push() Pop() 来操作路由,简单场景也能满足业务

Navigator是一个路由管理的组件,它提供了打开和退出路由页方。

Future push(BuildContext context, Route route) 压入一个新页面到路由堆栈

bool pop(BuildContext context, [ result ]) 压出一个页面出堆栈

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

MaterialPageRoute({
  // 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。
  // 我们通常要实现此回调,返回新路由的实例。
  WidgetBuilder builder,

  // 包含路由的配置信息,如路由名称、是否初始路由(首页)。
  RouteSettings settings,

  // 默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,
  // 如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为 false。
  bool maintainState = true,

  // 表示新的路由页面是否是一个全屏的模态对话框,
  // 在 iOS 中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
  bool fullscreenDialog = false,
})

路由传值

  • 传递可以在初始新界面对象时通过构造函数压入
  • 新界面退出后的返回值通过 Navigator.pop 的参数返回

代码

import 'package:flutter/material.dart';
import 'package:flutter_quickstart_learn/RouterPage.dart';

void main(List<String> args) {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', home: const NavPaged());
  }
}


import 'package:flutter/material.dart';

class NavPaged extends StatelessWidget {
  const NavPaged({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 页面标题
      appBar: AppBar(title: const Text('NavPaged')),
      body: Column(
        children: [
          Center(
            //ElevatedButton : 按钮
            child: ElevatedButton(
              // onPressed: 按钮点击事件, 异步跳转到详情页
              onPressed: () async {
                // Navigator.push: 异步跳转到新页面
                // 但是因为使用了 await,所以会等待新页面的返回值
                var result = await Navigator.push(
                  context,
                  // MaterialPageRoute: 创建一个新的路由
                  MaterialPageRoute(
                    builder: (context) {
                      // DetailPaged: 详情页
                      return const DetailPaged(title: "create a new route");
                    },
                  ),
                );
                // 上面会等待页面返回新的值,打印返回值
                print("路由返回值: $result");
              },
              child: const Text("Navigator.push DetailPage"),
            ),
          ),
        ],
      ),
    );
  }
}

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

  // 参数
  final String? title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('DetailPaged')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            // 按钮
            OutlinedButton(
              // onPressed: 按钮点击事件, 异步返回到上一个页面
              onPressed: () {
                // Navigator.pop: 异步返回到上一个页面
                // 为什么是异步的?因为Navigator.pop需要等待页面的销毁过程完成
                // static void pop<T extends Object?>(BuildContext context, [T? result])
                Navigator.pop(context, "ok");
              },
              child: const Text('Back'),
            ),
            // 显示传值
            Text(title ?? ""),
          ],
        ),
      ),
    );
  }
}
User          NavPaged            Navigator              DetailPaged
 |                |                     |                       |
 | 点击按钮        |                     |                       |
 |--------------->|                     |                       |
 |                |  Navigator.push     |                       |
 |                |-------------------->|                       |
 |                |   创建路由对象       |                       |
 |                |                     |------ 创建并显示 ----->|
 |                |                     |                       |
 |                | <---- 返回 Future ---|                       |
 |                |  (await 等待结果)    |                       |
 |                |                     |                       |
 |                |                     |                       |
 |                |                     |  用户点击Back按钮      |
 |                |                     |<----------------------|
 |                |                     | Navigator.pop("ok")   |
 |                |                     |---------------------->|
 |                |                     |  销毁 DetailPaged      |
 |                |                     |                       |
 |                | <----- Future 完成 --|                       |
 |                | result = "ok"       |                       |
 |                | 打印 "路由返回值:ok" |                       |
 |                |                     |                       |

命名路由

如果在routes里面定义了'/',则不能使用home属性,否则会报错

If the home property is specified, the routes table cannot include an entry for "/", since it would be redundant.
意思是: 如果你在 MaterialApp 中指定了 home,那么路由表 (routes) 里就不能再写 '/' 这个路由键,否则会冲突。

Navigator.pushNamed: 异步跳转到详情页

Navigator.pop: 返回到上一个页面

void main(List<String> args) {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      // 在这里定义路由的名字
      // 注册路由表
      routes: {
        '/': (context) => const NavPaged(),
        '/details': (context) => const DetailPaged(),
      },
      // 如果在routes里面定义了'/',则不能使用home属性,否则会报错
      // home: const NavPaged(),
    );
  }
}

import 'package:flutter/material.dart';

class NavPaged extends StatelessWidget {
  const NavPaged({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 页面标题
      appBar: AppBar(title: const Text('NavPaged')),
      body: Column(
        children: [
          Center(
            //ElevatedButton : 按钮
            child: ElevatedButton(
              // onPressed: 按钮点击事件, 跳转到详情页
              onPressed: () async {
                // Navigator .pushNamed: 跳转到新页面
                // 这里使用了命名路由,跳转到 DetailPaged 页面
                /*  @optionalTypeArgs
                static Future<T?> pushNamed<T extends Object?>(
                  BuildContext context,
                  String routeName, {
                  Object? arguments,
                })*/
                var result = await Navigator.pushNamed(
                  context,
                  // 这里是 /details ,是命名路由的名称
                  "/details",
                  // 传递参数到 DetailPaged 页面
                  arguments: {'title': "create a new route"},
                );

                // 上面会等待页面返回新的值,打印返回值
                print("路由返回值: $result");
              },
              child: const Text("Navigator.push DetailPage"),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailPaged extends StatelessWidget {
  // const DetailPaged({super.key, this.title});
  const DetailPaged({super.key});
  // 参数
  // final String? title;

  @override
  Widget build(BuildContext context) {
    // ModalRoute 是 Flutter 中一个类,表示当前页面的路由对象。
    // ModalRoute.of(context) 会根据 context 找到当前页面对应的路由。
    // ModalRoute.of(context)? : 里面的?表示如果找不到对应的路由,则返回null
    // settings: 获取路由的设置
    // settings.arguments: 获取路由传递的参数
    // <String, dynamic>{}: 如果没有传递参数,则使用一个空的Map作为默认值
    // as Map: 将获取到的参数强制转换为Map类型
    final arguments =
        (ModalRoute.of(context)?.settings.arguments ?? <String, dynamic>{})
            as Map;
    var title = arguments['title'];
    return Scaffold(
      appBar: AppBar(title: const Text('DetailPaged')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            // 按钮
            OutlinedButton(
              // onPressed: 按钮点击事件, 返回到上一个页面
              onPressed: () {
                // Navigator.pop: 返回到上一个页面
                // static void pop<T extends Object?>(BuildContext context, [T? result])
                Navigator.pop(context, "ok");
              },
              child: const Text('Back'),
            ),
            // 显示传值
            Text(title ?? ""),
          ],
        ),
      ),
    );
  }
}

onGenerateRoute 手动解析

onGenerateRoute 的原理

  • 作用:当 Navigator.pushNamed() 被调用时,Flutter 会先在 routes 查找对应的路由;如果没找到,就会调用 onGenerateRoute
  • 好处:可以统一管理所有路由逻辑,包括参数传递、异常处理。

onGenerateRoute 更灵活,可以处理:

  • 参数传递
  • 未知路由(404 页面)
  • 动态路由逻辑(比如权限判断)

一般推荐大项目用 onGenerateRoute,因为它集中管理路由逻辑,更好维护。

import 'package:flutter/material.dart';
import 'package:flutter_quickstart_learn/RouterPage.dart';

void main(List<String> args) {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      // onGenerateRoute 是路由生成的回调
      onGenerateRoute: (settings) {
        // settings 是 RouteSettings 对象,里面包含了路由的名称和参数
        print("settings.name: ${settings.name}");
        // Handle '/'
        if (settings.name == '/') {
          return MaterialPageRoute(builder: (context) => const NavPaged());
        }

        // Handle '/details/:id'
        var uri = Uri.parse(settings.name!);
        if (uri.pathSegments.length == 2 &&
            uri.pathSegments.first == 'details') {
          String uid = uri.pathSegments[1];
          return MaterialPageRoute(builder: (context) => DetailPaged(uid: uid));
        }

        return MaterialPageRoute(builder: (context) => const UnknownPage());
      },
    );
  }
}

import 'package:flutter/material.dart';

class NavPaged extends StatelessWidget {
  const NavPaged({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 页面标题
      appBar: AppBar(title: const Text('NavPaged')),
      body: Column(
        children: [
          Center(
            //ElevatedButton : 按钮
            child: ElevatedButton(
              // onPressed: 按钮点击事件, 异步跳转到详情页
              onPressed: () async {
                // Navigator .pushNamed: 异步跳转到新页面
                // 这里使用了命名路由,跳转到 DetailPaged 页面
                /*  @optionalTypeArgs
                static Future<T?> pushNamed<T extends Object?>(
                  BuildContext context,
                  String routeName, {
                  Object? arguments,
                })*/
                var result = await Navigator.pushNamed(
                  context,
                  // 这里是 /details ,是命名路由的名称
                  "/details/312312312312",
                  // 传递参数到 DetailPaged 页面
                  arguments: {'title': "create a new route"},
                );

                // 上面会等待页面返回新的值,打印返回值
                print("路由返回值: $result");
              },
              child: const Text("Navigator.push DetailPage"),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailPaged extends StatelessWidget {
  // const DetailPaged({super.key, this.title});
  // const DetailPaged({super.key});
  // 参数
  // final String? title;

  const DetailPaged({super.key, this.uid});

  final String? uid;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('DetailPaged')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            // 按钮
            OutlinedButton(
              // onPressed: 按钮点击事件, 异步返回到上一个页面
              onPressed: () {
                // Navigator.pop: 异步返回到上一个页面
                // 为什么是异步的?因为Navigator.pop需要等待页面的销毁过程完成
                // static void pop<T extends Object?>(BuildContext context, [T? result])
                Navigator.pop(context, "ok");
              },
              child: const Text('Back'),
            ),
            // 显示传值
            Text(uid ?? ""),
          ],
        ),
      ),
    );
  }
}

class UnknownPage extends StatelessWidget {
  const UnknownPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(body: Text('UnknownPage'));
  }
}

时序图:

sequenceDiagram
    participant U as User
    participant N as NavPaged
    participant Nav as Navigator
    participant App as MyApp.onGenerateRoute
    participant D as DetailPaged

    U->>N: 点击按钮
    N->>Nav: pushNamed("/details/312312312312")
    Nav->>App: 调用 onGenerateRoute
    App-->>Nav: 返回 MaterialPageRoute
    Nav->>D: 加载并渲染 DetailPaged
    D-->>Nav: Navigator.pop("ok")
    Nav-->>N: 返回结果 "ok"
    N->>N: 打印返回值: ok
  • User → NavPaged:用户点击按钮触发事件。
  • NavPaged → Navigator:调用 pushNamed
  • Navigator → MyApp.onGenerateRoute:根据路由规则生成页面。
  • onGenerateRoute → Navigator:返回 MaterialPageRoute
  • Navigator → DetailPaged:加载并渲染目标页面。
  • DetailPaged → Navigator.pop(“ok”):用户返回时传回结果。
  • Navigator → NavPaged:把 "ok" 返回给发起调用的地方。

routesonGenerateRoute对比

特性 routes(命名路由表) onGenerateRoute(动态路由生成)
定义方式 MaterialApproutes 属性中写死一个 Map,{"/": (context) => HomePage(), "/detail": (context) => DetailPage()} onGenerateRoute 回调函数中,根据 settings.name 动态返回 Route
初学者友好度 ✅ 简单直观,适合小项目 ❌ 相对复杂,需要写 switch 或逻辑判断
参数传递 ⚠️ 不太方便,需要额外写构造函数或 settings.arguments 配合使用 ✅ 非常方便,直接在 settings.arguments 里传,统一处理
扩展性 ❌ 不灵活,路由必须预定义在 Map 中 ✅ 高度灵活,可以做权限校验、动态跳转、日志记录
错误处理 ❌ 如果路由未定义会报错,容易崩溃 ✅ 可以处理未匹配路由,返回 404 页面
适用场景 小型应用,页面少,逻辑简单 中大型应用,需要统一管理、权限控制、参数传递复杂
可维护性 随着页面增加,routes 表越来越臃肿 所有路由逻辑集中在 onGenerateRoute,更容易维护

×

喜欢就点赞,疼爱就打赏