Flutter | 从基本跳转到路由守卫

路由基础概念

在 Flutter 中,路由(Route) 代表应用中的一个页面。页面之间的跳转由 Navigator 管理,它维护一个 路由栈(Stack)

复制代码
┌─────────────┐
│  详情页      │  ← 栈顶(当前可见页面)
├─────────────┤
│  列表页      │
├─────────────┤
│  首页        │  ← 栈底
└─────────────┘
操作 API 效果
打开新页面 Navigator.push 压入栈顶
返回上一页 Navigator.pop 弹出栈顶
命名跳转 Navigator.pushNamed 通过路由名跳转

路由配置通常写在 MaterialApp 上,它是整个应用的入口容器。


第一阶段:基本路由

1.1 设置首页

最简单的方式是通过 home 指定应用启动后显示的第一个页面:

dart 复制代码
return MaterialApp(home: ListPage());

1.2 跳转到新页面

列表项点击时,使用 Navigator.push 配合 MaterialPageRoute 打开详情页:

dart 复制代码
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(message: "这是第$index个详情"),
  ),
);

三个关键要素:

要素 说明
context 当前 Widget 的上下文,Navigator 通过它定位路由栈
MaterialPageRoute Material 风格的页面路由,负责页面切换动画
builder 返回目标页面的 Widget

1.3 接收参数

详情页通过 构造函数 接收参数,这是基本路由传参最直接的方式:

dart 复制代码
class DetailPage extends StatelessWidget {
  final String message;
  DetailPage({Key? key, required this.message}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text("详情页-${this.message}")),
    );
  }
}

1.4 阶段小结

  • 适合页面少、跳转关系简单的场景
  • 每次跳转都要写 MaterialPageRoute,路由逻辑分散在各处
  • 传参依赖构造函数,类型安全、IDE 提示友好

第二阶段:基本路由传参与返回

在第一阶段基础上,本节重点学习 传参返回

2.1 传递不同类型的参数

列表页跳转时传入 id(整型):

dart 复制代码
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(id: index + 1),
  ),
);

详情页用 StatefulWidget 接收,在 initState 中读取:

dart 复制代码
class DetailPage extends StatefulWidget {
  final int? id;
  DetailPage({Key? key, required this.id}) : super(key: key);
}

class _DetailPageState extends State<DetailPage> {
  @override
  void initState() {
    super.initState();
    print(widget.id); // 通过 widget.id 访问参数
  }
}

2.2 返回上一页

使用 Navigator.pop(context) 关闭当前页面,回到栈中上一个页面:

dart 复制代码
TextButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text("返回列表页${widget.id}"),
)

2.3 带返回值 pop(扩展知识)

Navigator.pop 还可以向上一页传递数据:

dart 复制代码
// 详情页返回时
Navigator.pop(context, {"result": "操作成功"});

// 列表页接收(需要用 async/await)
final result = await Navigator.push(...);
print(result); // {"result": "操作成功"}

2.4 阶段小结

能力 API
跳转 Navigator.push + MaterialPageRoute
传参 目标页构造函数
返回 Navigator.pop(context)
带值返回 Navigator.pop(context, data)

第三阶段:命名路由

当页面增多、跳转关系复杂时,应改用 命名路由 ------在 MaterialApp 中集中注册路由表。

3.1 注册路由表

dart 复制代码
return MaterialApp(
  initialRoute: "/detail",
  routes: {
    "/list": (context) => ListPage(),
    "/detail": (context) => DetailPage(),
  },
  theme: ThemeData(primarySwatch: Colors.blue),
);
配置项 作用
routes 路由名 → 页面 Widget 的映射表
initialRoute 应用启动时打开的命名路由

3.2 命名跳转

不再手写 MaterialPageRoute,一行即可跳转:

dart 复制代码
Navigator.pushNamed(context, "/list");

对比基本路由:

dart 复制代码
// 基本路由 --- 需要知道目标 Widget
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage()));

// 命名路由 --- 只需知道路由名
Navigator.pushNamed(context, "/detail");

3.3 基本路由与命名路由混用

列表页使用 Navigator.push + MaterialPageRoute,详情页用 pushNamed 返回列表。两种写法可以共存,但生产环境建议统一为一种风格。

3.4 注意事项

homeinitialRoute 不要同时使用。

同时写了 initialRoute: "/detail"home: ListPage(),后者会覆盖前者。实际使用时二选一即可。

3.5 阶段小结

  • 路由集中管理,跳转代码更简洁
  • 适合中大型应用
  • 传参需要额外机制(见下一阶段)

第四阶段:命名路由传参

命名路由无法通过构造函数直接传参,需借助 arguments 参数。

4.1 发送参数

跳转时通过 arguments 传递 Map:

dart 复制代码
Navigator.pushNamed(
  context,
  "/detail",
  arguments: {"id": index + 1},
);

arguments 类型为 Object?,通常传 Map<String, dynamic>

4.2 接收参数

目标页通过 ModalRoute.of(context) 获取当前路由信息:

dart 复制代码
@override
void initState() {
  super.initState();
  Future.microtask(() {
    if (ModalRoute.of(context) != null) {
      Map<String, dynamic> arguments =
          ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
      print("从列表页传递过来的参数:${arguments["id"]}");
    } else {
      print("获取不到参数");
    }
  });
}

为什么用 Future.microtask

initState 中直接调用 ModalRoute.of(context) 时,Widget 可能尚未挂载到路由树,context 还不可用。Future.microtask 将读取推迟到当前帧结束后,确保路由信息已就绪。

4.3 在 build 中接收(替代写法)

也可以在 build 方法中读取,无需 Future.microtask

dart 复制代码
@override
Widget build(BuildContext context) {
  final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
  final id = args?["id"];
  return Scaffold(body: Text("详情 id: $id"));
}

4.4 两种传参方式对比

方式 适用场景 类型安全 写法
构造函数传参 基本路由 强类型 DetailPage(id: 1)
arguments 传参 命名路由 需手动 cast arguments: {"id": 1}

4.5 阶段小结

  • 命名路由传参依赖 arguments + ModalRoute.of(context)
  • 注意 initState 中获取参数的时机问题
  • 生产环境建议封装路由工具类,避免到处写 cast

第五阶段:路由管理与守卫

这是路由学习的 进阶阶段,涵盖动态路由生成、404 处理和登录拦截。

5.1 完整路由架构

dart 复制代码
return MaterialApp(
  initialRoute: "goodsList",
  routes: {
    "goodsList": (context) => GoodsListPage(),
  },
  onUnknownRoute: (settings) {
    return MaterialPageRoute(builder: (context) => NotFoundPage());
  },
  onGenerateRoute: (settings) {
    if (settings.name == "cartList") {
      bool isLogin = false;
      if (isLogin) {
        return MaterialPageRoute(builder: (context) => CartListPage());
      } else {
        return MaterialPageRoute(builder: (context) => LoginPage());
      }
    } else if (settings.name == "login") {
      return MaterialPageRoute(builder: (context) => LoginPage());
    }
    return null; // 建议显式返回 null,交给 onUnknownRoute 处理
  },
);

5.2 路由匹配优先级

Flutter 按以下顺序匹配路由:

复制代码
1. routes 表中查找
       ↓ 未找到
2. onGenerateRoute 动态生成
       ↓ 返回 null 或未处理
3. onUnknownRoute 兜底(404)

流程图:

复制代码
pushNamed("cartList")
        │
        ▼
   routes 中有吗? ──是──→ 直接打开对应页面
        │否
        ▼
 onGenerateRoute ──→ 动态判断(登录守卫等)
        │返回 null
        ▼
  onUnknownRoute ──→ 404 页面

5.3 onGenerateRoute:动态路由与登录守卫

onGenerateRouteroutes 找不到匹配项时触发,适合:

  • 需要 条件判断 的路由(如登录拦截)
  • 路由表过大、不便全部写在 routes 中的场景
  • 需要根据 settings.arguments 动态创建页面的场景

项目中的 登录守卫 示例:

dart 复制代码
if (settings.name == "cartList") {
  bool isLogin = false; // 实际项目中从全局状态或本地存储读取
  if (isLogin) {
    return MaterialPageRoute(builder: (context) => CartListPage());
  } else {
    return MaterialPageRoute(builder: (context) => LoginPage()); // 未登录跳转登录页
  }
}

这是电商类 App 的常见模式:访问购物车前检查登录状态。

5.4 onUnknownRoute:404 兜底

当路由名既不在 routes 中,onGenerateRoute 也未处理时,触发 onUnknownRoute

dart 复制代码
onUnknownRoute: (settings) {
  return MaterialPageRoute(builder: (context) => NotFoundPage());
},

项目中商品列表页点击按钮跳转到 "abc"(不存在的路由),最终会进入 404 页面:

dart 复制代码
Navigator.pushNamed(context, "abc"); // → NotFoundPage

5.5 页面结构

页面 路由名 注册方式
商品列表 goodsList routes 静态注册
购物车 cartList onGenerateRoute + 登录守卫
登录页 login onGenerateRoute
未知页面 --- onUnknownRoute 兜底

5.6 阶段小结

API 用途
routes 静态路由表,无条件的固定页面
onGenerateRoute 动态路由、登录守卫、复杂传参
onUnknownRoute 404 兜底,提升用户体验
initialRoute 指定启动页(与 home 二选一)

API 说明 示例
Navigator.push 压入新页面 Navigator.push(context, MaterialPageRoute(...))
Navigator.pop 弹出当前页 Navigator.pop(context)
Navigator.pushNamed 命名路由跳转 Navigator.pushNamed(context, "/detail")
Navigator.popUntil 弹出直到满足条件 Navigator.popUntil(context, ModalRoute.withName("/home"))
Navigator.pushReplacement 替换当前页(不可返回) 登录成功后跳转首页
Navigator.pushNamedAndRemoveUntil 跳转并清空栈 退出登录回登录页

生产环境进阶建议

1. 封装路由工具类

避免路由名硬编码散落各处:

dart 复制代码
class AppRoutes {
  static const goodsList = "goodsList";
  static const cartList = "cartList";
  static const login = "login";

  static Route<dynamic>? onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case cartList:
        return _guardCart(settings);
      case login:
        return MaterialPageRoute(builder: (_) => LoginPage());
      default:
        return null;
    }
  }
}

2. 使用 go_router 等第三方库

Flutter 官方推荐的 go_router 支持:

  • 声明式路由
  • 深度链接(Deep Link)
  • 路由重定向(Redirect)
  • 更清晰的嵌套路由

当项目路由复杂度继续增长时,值得迁移。

3. 统一参数模型

为命名路由定义参数类,替代裸 Map

dart 复制代码
class DetailArgs {
  final int id;
  DetailArgs({required this.id});
}

// 跳转
Navigator.pushNamed(context, "/detail", arguments: DetailArgs(id: 1));

// 接收
final args = ModalRoute.of(context)!.settings.arguments as DetailArgs;

4. 修复 onGenerateRoute 的返回值

当前 路由管理.dart 中,未匹配的路由分支没有显式 return null,建议补上,让 onUnknownRoute 可靠兜底:

dart 复制代码
onGenerateRoute: (settings) {
  if (settings.name == "cartList") { ... }
  else if (settings.name == "login") { ... }
  return null; // 显式返回 null,触发 onUnknownRoute
},

总结

Flutter 路由学习可以归纳为一条清晰的路径:

复制代码
基本路由(push + MaterialPageRoute)
    ↓ 加参数、加返回
基本路由传参(构造函数 + pop)
    ↓ 集中管理
命名路由(routes + pushNamed)
    ↓ 命名路由传参
arguments + ModalRoute
    ↓ 动态控制
路由管理(onGenerateRoute + onUnknownRoute + 守卫)
相关推荐
SilentSamsara1 小时前
HTTP 客户端实战:httpx/重试/限速/连接池/中间件设计
开发语言·网络·python·http·青少年编程·中间件·httpx
24zhgjx-fuhao1 小时前
ISIS:多区域集成IS-IS
网络·智能路由器
小熊officer1 小时前
网络渗透和网络安全
网络·安全·web安全
神奇的代码在哪里1 小时前
【单机离线版】大学考试题库复习工具:前端离线Excel解析 + localStorage持久化 + Playwright
前端·html·ai编程·题库复习·刷题软件·大学考试
IOT.FIVE.NO.11 小时前
Claude code+Vscode+Remote ssh+ 服务器自定义第三方API配置保姆级教程
服务器·vscode·ssh
饿了吃洗衣凝珠1 小时前
【无标题】
运维·服务器·网络
状元岐1 小时前
1. ModBus从入门到精通
网络
爱讲故事的1 小时前
计算机网络第六讲复习博客:链路层与局域网
网络·计算机网络·智能路由器
luweis1 小时前
企智孪生 ETA (3.5 执行层技术落地)【浙江联保网络 卢伟舜】
网络·人工智能·程序人生·职场和发展·学习方法