Flutter中的页面跳转

Flutter中的路由

层次 关键词 典型调用 解决的痛点
① 原生 Navigator API push / pop / pushReplacement Navigator.push(context, ...) 必须手握 context,路由增删要自己写
② 路由表 + 命名路由 routes / onGenerateRoute Navigator.of(context).pushNamed('/detail') 统一管理路径;但依旧传 context,状态注入麻烦
③ 路由框架(GetX、go_router...) Get.to / offAll / arguments Get.to(Page()) 、Get.offAllNamed(Routes.MAIN) 把 导航 、依赖注入 、URL 映射 、生命周期 收拢到一起;脱离 context

对象 本质 记住一句话
Navigator StatefulWidget,内部维护一条 List<Route> 栈 "Flutter 的 Activity 栈"
Route 「一次页面切换」的数据结构 push 就是 stack.add(route),pop 就是 removeLast()
BuildContext 找到最近的 NavigatorState 的钥匙 生命周期短,写 DAO/单例时往往拿不到

场景 原生 Navigator 方案 代码示例
在 Widget 内部跳转 Navigator.push / pop 直接用 context dart<br>// 进入详情页<br>Navigator.push(<br> context,<br> MaterialPageRoute(builder: (_) => DetailPage(id: 42)),<br>);<br>
切换根路由并清空栈 (登录成功 / 退出登录) pushNamedAndRemoveUntil 或 pushReplacement dart<br>// 登录成功后,只保留首页<br>Navigator.of(context).pushNamedAndRemoveUntil(<br> '/home', // 目标路由<br> (_) => false, // 清空旧栈<br>);<br>
在 DAO / Service 等非-Widget 层跳转 给 MaterialApp 配置全局 GlobalKey<NavigatorState> ,随后用 navKey.currentState 操作 dart<br>// 入口文件<br>final navKey = GlobalKey<NavigatorState>();<br><br>runApp(MaterialApp(<br> navigatorKey: navKey,<br> routes: {<br> '/login': (_) => const LoginPage(),<br> '/home' : (_) => const HomePage(),<br> },<br>));<br><br>// DAO 中<br>navKey.currentState?.pushNamed('/login');<br>
传递 / 接收参数 & 返回值 ① pushNamed + settings.arguments ② pop(result) dart<br>// 发送参数<br>Navigator.pushNamed(<br> context,<br> '/detail',<br> arguments: {'id': 42},<br>);<br><br>// 接收参数<br>final args = ModalRoute.of(context)!.settings.arguments as Map;<br><br>// 带返回值的跳转<br>final result = await Navigator.push(...);<br>
自定义转场动画 PageRouteBuilder dart<br>Navigator.of(context).push(<br> PageRouteBuilder(<br> pageBuilder: (_, __, ___) => const NewPage(),<br> transitionsBuilder: (_, anim, __, child) =><br> FadeTransition(opacity: anim, child: child),<br> ),<br>);<br>

A. 最常见的三种跳转

dart 复制代码
// 1. 普通 push
Navigator.push(context,
    MaterialPageRoute(builder: (_) => const DetailPage()));

// 2. pushReplacement(把当前页踢掉)
Navigator.pushReplacement(context,
    MaterialPageRoute(builder: (_) => const HomePage()));

// 3. pushNamedAndRemoveUntil(清空旧栈)
Navigator.of(context)
    .pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
dart 复制代码
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();

void main() {
  runApp(MaterialApp(
    navigatorKey: navKey,
    routes: {
      '/': (_) => const SplashPage(),
      '/home': (_) => const HomePage(),
      '/login': (_) => const LoginPage(),
    },
  ));
}

// DAO / Service 里直接调用
Future<void> logout() async {
  // ...清理 token...
  navKey.currentState?.pushNamedAndRemoveUntil('/login', (_) => false);
}
  • navigatorKey 只提供 Navigator 的操作权 ,不会持有 BuildContext,因此不会导致内存泄漏。
  • 对需要 BuildContext 的组件(showDialog、ScaffoldMessenger.of 等)仍然建议在 Widget 层调用。

3. GetX 路由核心 API 与对应原生操作

NavigatorUtil 封装 等价原生写法 说明
Get.to(Page()) Navigator.push(context, MaterialPageRoute(...)) 普通 push
Get.back() Navigator.pop(context) 返回
Get.offAll(Page()) pushNamedAndRemoveUntil('/', (r) => false) 清空栈 后推入新页
Get.offAllNamed('/home') 同上,但用路由名 保证依赖注入 (Bindings) 不被销毁
Get.offNamed('/login') pushReplacementNamed 用新页替换当前页
Get.to(Page(), arguments: {...}) push + 自己写 ModalRoute.of(context).settings.arguments 传参更简洁
Get.to(Page(), preventDuplicates: true) 判断栈顶后再 push 避免重复跳转

结论: GetX 把 上下文查找、动画默认值、栈检查 等细节都包了,我们才可以在 DAO、Interceptor 里一句 Get.offAllNamed(Routes.LOGIN) 完成登出等类似的操作。


4. 路由表、依赖注入与中间件

dart 复制代码
class AppPages {
  static final routes = [
    GetPage(
      name: Routes.MAIN,
      page: () => const MainPage(),
      binding: MainBinding(),            // ← 在进入页面前注入 Controller/Service
    ),
    GetPage(
      name: Routes.LOGIN,
      page: () => const LoginPage(),
      middlewares: [AuthGuard()],        // ← 路由守卫,未登录重定向
    ),
    GetPage(
      name: Routes.WEB,
      page: () => HiWebView(),           // ← WebView 共用 1 份组件
    ),
  ];
}
  • binding :页面销毁时自动 dispose ViewModel;offAllNamed 不会丢失状态。
  • middlewares:统一做鉴权、打点、动态改主题。
  • 深链 / Web URL :Get.parameters['id'] 就能拿到 /detail?id=123 里的参数。 |

5. 示例

dart 复制代码
void main() {
  runApp(GetMaterialApp(
    initialRoute: Routes.LOGIN,
    getPages: AppPages.routes,
    defaultTransition: Transition.fade,
    navigatorKey: Get.key,        // 让非 Widget 也能导航
  ));
}

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: const Text('登录'),
          onPressed: () {
            // 保存 token...
            NavigatorUtil.goToHome();
          },
        ),
      ),
    );
  }
}

class MainPage extends StatelessWidget {
  const MainPage({super.key});
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('首页')),
        body: Center(
          child: ElevatedButton(
            child: const Text('登出'),
            onPressed: NavigatorUtil.goToLogin,
          ),
        ),
      );
}

⚡ 总结一句话

原生 Navigator = 栈数据结构 + 依赖 BuildContext
GetX / go_router 等路由框架 = 在此之上做了"无 context 调用 + 路由表 + 依赖注入 + 中间件"的全家桶。

dart 复制代码
NavigatorUtil.goToHome();  // 或 Get.offAllNamed(Routes.MAIN)
相关推荐
To_OC8 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
excel9 小时前
研究 Vue 3 源码的收获
前端·vue.js
可乐鸡翅yeah_11 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx11 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师11 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow12 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路12 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他12 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou13213 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙13 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript