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>
切换根路由并清空栈 (登录成功 / 退出登录) pushNamedAndRemoveUntilpushReplacement 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.argumentspop(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 的组件(showDialogScaffoldMessenger.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 把 上下文查找、动画默认值、栈检查 等细节都包了,我们才可以在 DAOInterceptor 里一句 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 URLGet.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)
相关推荐
小小小小宇4 小时前
TS泛型笔记
前端
小小小小宇4 小时前
前端canvas手动实现复杂动画示例
前端
codingandsleeping4 小时前
重读《你不知道的JavaScript》(上)- 作用域和闭包
前端·javascript
小小小小宇5 小时前
前端PerformanceObserver使用
前端
烛阴6 小时前
Puppeteer入门指南:掌控浏览器,开启自动化新时代
前端·javascript
全宝7 小时前
🖲️一行代码实现鼠标换肤
前端·css·html
小小小小宇8 小时前
前端模拟一个setTimeout
前端
萌萌哒草头将军8 小时前
🚀🚀🚀 不要只知道 Vite 了,可以看看 Farm ,Rust 编写的快速且一致的打包工具
前端·vue.js·react.js
芝士加8 小时前
Playwright vs MidScene:自动化工具“双雄”谁更适合你?
前端·javascript