路由与导航:Navigator 与 go_router

Flutter 的导航分两层:底层的 Navigator(命令式压栈出栈)和上层路由方案(声明式、可用 URL 表达)。小应用用 Navigator.push(MaterialPageRoute(...)) 完全够;一旦需要深链接、Web URL、嵌套 Tab 各自独立的返回栈,就该上 go_router

命令式导航:最直接的用法

// 跳转并等待返回值
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(builder: (context) => const DetailPage(id: 42)),
);
if (result != null) debugPrint('返回了:$result');
Navigator.pop(context, '已保存');                                     // 带结果返回
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomePage()));
Navigator.popUntil(context, (route) => route.isFirst);                 // 回到栈底
方法行为典型场景
push压入新页面详情页
pop弹出当前页并回传结果返回
pushReplacement替换当前页登录 → 首页
popUntil连续弹出直到条件满足回到首页

传参优先用构造函数:类型安全、可读性好;ModalRoute.of(context)!.settings.arguments 的类型是 Object?,只在路由表统一分发时才用。

命名路由与 onGenerateRoute

命名路由把"页面名 → 页面"集中到一处,适合页面多、需要统一入口的项目,但参数只能靠 arguments 传递,也没有深链接能力。

MaterialApp(
  initialRoute: '/',
  routes: {'/': (context) => const HomePage(), '/settings': (context) => const SettingsPage()},
  onGenerateRoute: (settings) => MaterialPageRoute(
    builder: (_) => settings.name == '/detail'
        ? DetailPage(id: settings.arguments as int? ?? 0)
        : const Scaffold(body: Center(child: Text('页面不存在'))),
  ),
)

go_router:声明式路由

flutter pub add go_router
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  // 全局重定向:未登录一律跳登录页;返回 null 表示不重定向
  redirect: (context, state) {
    const loggedIn = true; // 实际从状态管理里读
    final toLogin = state.matchedLocation == '/login';
    return !loggedIn && !toLogin ? '/login' : (loggedIn && toLogin ? '/' : null);
  },
  routes: [
    GoRoute(path: '/', builder: (context, state) => const HomePage()),
    GoRoute(path: '/login', builder: (context, state) => const LoginPage()),
    // 路径参数用 :id 声明,查询参数从 uri.queryParameters 取
    GoRoute(
      path: '/detail/:id',
      builder: (context, state) => DetailPage(
        id: int.tryParse(state.pathParameters['id']!) ?? 0,
        tab: state.uri.queryParameters['tab'] ?? 'info',
      ),
    ),
    // ShellRoute:子页面共享底部导航栏,各自保留返回栈
    ShellRoute(
      builder: (context, state, child) => Scaffold(
        body: child,
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: state.uri.path.startsWith('/cart') ? 1 : 0,
          onTap: (i) => context.go(i == 0 ? '/' : '/cart'),
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
            BottomNavigationBarItem(icon: Icon(Icons.shopping_cart), label: '购物车'),
          ])),
      routes: [GoRoute(path: '/cart', builder: (context, state) => const CartPage())],
    ),
  ],
  // 兜底 404
  errorBuilder: (context, state) => Scaffold(
    appBar: AppBar(title: const Text('页面不存在')),
    body: Center(child: Text('未找到:${state.uri}')),
  ),
);

void main() => runApp(MaterialApp.router(routerConfig: router));

context.go vs context.push

方式行为浏览器表现适用场景
context.go('/detail/1')替换当前栈URL 直接替换Tab 切换、Web 主路径导航
context.push('/detail/1')压入新页面,保留返回栈新增历史记录详情页等需要返回的场景
context.pop()弹出一层回退历史返回

选错的最典型症状:用 go 进详情页后没有返回按钮,浏览器后退直接回首页。列表进详情用 push,底部导航切换用 go

onTap: () => context.push('/detail/${item.id}?tab=spec'); // 列表进详情
onTap: (i) => context.go(i == 0 ? '/' : '/cart');        // 底部导航切换

多端与返回键处理

平台要处理的事做法
WebURL 与路由同步、刷新后停在原页用路径式 go_router,避免只有名称无路径
Android系统返回键go_router 自动处理,需二次确认时用 PopScope
iOS侧滑返回MaterialPageRoute 默认支持,fullscreenDialog 会关掉
桌面窗口尺寸变化LayoutBuilder 做响应式分栏
PopScope(
  canPop: false, // 自己接管返回键,用于"未保存"二次确认
  onPopInvokedWithResult: (didPop, result) async => didPop ? null : _confirmLeaveAndPop(),
  child: const Scaffold(body: Center(child: Text('编辑页'))),
)

常见坑

现象原因修法
Navigator.of() called with a context that does not contain a Navigator用了 MaterialApp 之外的 context用页面的 context,确认没漏 MaterialApp
push 之后 setState 报错页面已销毁if (!mounted) return;
go 跳转后返回键失效go 替换了栈改用 push
深链接进不去未配置平台 intent / universal linkAndroid 加 intent-filter,iOS 配 associated domains
flutter run -d chrome     # 在浏览器验证 URL 与后退行为

小结:简单应用用 Navigator.push(MaterialPageRoute(...)) 配构造函数传参、pop 回传结果即可;需要深链接、Web URL、嵌套栈时用 go_router——push 用于要返回的页面、go 用于替换式导航,路径参数与查询参数从 state 读,全局用 redirect 鉴权、errorBuilder 兜 404。

笔记加载中…