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. 最常见的三种跳转
Navigator.push(context,
MaterialPageRoute(builder: (_) => const DetailPage()));
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (_) => const HomePage()));
Navigator.of(context)
.pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
B. 使用全局 navigatorKey 脱离 context
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
void main() {
runApp(MaterialApp(
navigatorKey: navKey,
routes: {
'/': (_) => const SplashPage(),
'/home': (_) => const HomePage(),
'/login': (_) => const LoginPage(),
},
));
}
Future<void> logout() async {
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. 路由表、依赖注入与中间件
class AppPages {
static final routes = [
GetPage(
name: Routes.MAIN,
page: () => const MainPage(),
binding: MainBinding(),
),
GetPage(
name: Routes.LOGIN,
page: () => const LoginPage(),
middlewares: [AuthGuard()],
),
GetPage(
name: Routes.WEB,
page: () => HiWebView(),
),
];
}
binding:页面销毁时自动 dispose ViewModel;offAllNamed 不会丢失状态。
middlewares:统一做鉴权、打点、动态改主题。
- 深链 / Web URL:
Get.parameters['id'] 就能拿到 /detail?id=123 里的参数。
|
5. 示例
void main() {
runApp(GetMaterialApp(
initialRoute: Routes.LOGIN,
getPages: AppPages.routes,
defaultTransition: Transition.fade,
navigatorKey: Get.key,
));
}
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: const Text('登录'),
onPressed: () {
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 调用 + 路由表 + 依赖注入 + 中间件”的全家桶。
NavigatorUtil.goToHome();