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 |
1. Navigator 的基础三件套
对象 |
本质 |
记住一句话 |
Navigator |
StatefulWidget,内部维护一条 List<Route> 栈 |
"Flutter 的 Activity 栈" |
Route |
「一次页面切换」的数据结构 |
push 就是 stack.add(route) ,pop 就是 removeLast() |
BuildContext |
找到最近的 NavigatorState 的钥匙 |
生命周期短,写 DAO/单例时往往拿不到 |
2 . 纯 原生 Navigator(不依赖第三方框架)
场景 |
原生 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);
B. 使用全局 navigatorKey
脱离 context
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)