导航与路由:Navigator 2.0 与 go_router
hardflutternavigationnavigatorgo-routerdeep-linknamed-routeshell-route
Navigator 1.0 —— 命令式栈操作
Flutter 最初的导航 API 是命令式的,像操作一个栈:
// 推入新页面
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
);
// 弹出,带返回值
Navigator.pop(context, 'result');
// 推入+移除所有历史(用于登录后跳首页)
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const HomePage()),
(route) => false, // 清空所有
);
// 命名路由
Navigator.pushNamed(context, '/profile', arguments: {'userId': 42});
// 获取参数
final args = ModalRoute.of(context)!.settings.arguments as Map;
局限:深度链接、Web URL 同步、复杂嵌套导航支持差。
Navigator 2.0 —— 声明式路由
Navigator 2.0 引入声明式 API,将路由状态纳入应用状态:
┌─────────────────────────────────┐
│ App State │
│ authState / routerState │
│ ↓ 驱动 │
│ RouterDelegate │
│ 根据状态决定页面栈 │
│ ↓ │
│ RouteInformationParser │
│ URL ↔ 路由状态的转换 │
└─────────────────────────────────┘
原始 Navigator 2.0 实现复杂,实践中通常使用 go_router。
go_router —— 官方推荐路由库
flutter pub add go_router
基础配置
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (_, __) => const HomePage(),
),
GoRoute(
path: '/profile/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return ProfilePage(userId: userId);
},
),
GoRoute(
path: '/settings',
builder: (_, __) => const SettingsPage(),
// 子路由(嵌套在 /settings 下)
routes: [
GoRoute(
path: 'notifications', // 完整路径: /settings/notifications
builder: (_, __) => const NotificationsPage(),
),
],
),
],
// 全局错误页
errorBuilder: (context, state) => ErrorPage(error: state.error),
);
// 在 MaterialApp.router 中使用
MaterialApp.router(
routerConfig: router,
)
导航操作
// push(返回时有前一页)
context.push('/profile/42');
// go(替换,用于 Tab 切换等)
context.go('/home');
// pop
context.pop();
// 跳转带 extra 参数(非 URL 参数,不持久化)
context.push('/detail', extra: {'product': product});
// 获取 extra
final product = GoRouterState.of(context).extra as Map;
ShellRoute —— 嵌套导航(底部导航栏)
final router = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
// child 是当前选中 Tab 的页面
return ScaffoldWithBottomNav(child: child);
},
routes: [
GoRoute(path: '/home', builder: (_, __) => const HomePage()),
GoRoute(path: '/search', builder: (_, __) => const SearchPage()),
GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()),
],
),
],
);
class ScaffoldWithBottomNav extends StatelessWidget {
final Widget child;
const ScaffoldWithBottomNav({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _getIndex(GoRouterState.of(context).uri.path),
onTap: (i) {
switch (i) {
case 0: context.go('/home');
case 1: context.go('/search');
case 2: context.go('/profile');
}
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '搜索'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
}
重定向(权限守卫)
final router = GoRouter(
redirect: (context, state) {
final isLoggedIn = context.read<AuthNotifier>().isLoggedIn;
final isGoingToLogin = state.matchedLocation == '/login';
if (!isLoggedIn && !isGoingToLogin) {
// 未登录,重定向到登录页并保存原目标
return '/login?redirect=${state.matchedLocation}';
}
if (isLoggedIn && isGoingToLogin) {
// 已登录,不需要去登录页
return '/home';
}
return null; // 不重定向
},
routes: [ /* ... */ ],
);
Deep Link 配置
Android (android/app/src/main/AndroidManifest.xml):
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="myapp.com" />
</intent-filter>
iOS (ios/Runner/Info.plist):
需配置 Associated Domains:applinks:myapp.com
go_router 会自动将 URL /profile/42 映射到 /profile/:userId 路由。
核心架构问题
Q:Navigator 1.0 和 Navigator 2.0 的区别?
Navigator 1.0 是命令式的,手动 push/pop 操作栈;Navigator 2.0 是声明式的,通过状态驱动路由栈,天然支持 Web URL 同步和深度链接。
Q:go_router 的 push 和 go 区别?
push 将新页面压栈(可返回);go 重置到目标路由(用于 Tab 切换,不在历史栈中保留前一个 Tab 页面)。