一、需求来源
最近研究多主题(深色、浅色、浅色多种主题色)配置的功能,测试工程中使用原生路由实在太多繁琐。之前也有同学提出过如果不用第三方路由,如何在当前工程内封装一个好用的路由导航工具模块的问题。顺便花了两天时间构思了一下,实现了一个极简版封装,使用方便度等同 Getx 路由管理。

二、使用示例
1、main.dart
dart
return MaterialApp(
navigatorKey: AppNavigator.navigatorKey,
navigatorObservers: [AppNavigatorObserver()],
initialRoute: AppRouter.initial,
routes: AppRouter.routeMap,
);
2、路由导航方法使用示例
dart
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () async {
//跳转
final result = await AppNavigator.toNamed(AppRouter.morePage, arguments: {"id": "111", "a": "999"});
DLog.d("$widget: $result");
//[log][2025-10-18 11:14:00.132836][DEBUG][ios][_HomePageState.build Line:65]: HomePage: {class: MorePage, id: 111, a: 999}
},
),
dart
TextButton(
onPressed: () {
// 带参返回
AppNavigator.back({"class": "$widget", ...?widget.arguments});
},
child: Text('带参返回'),
),
3、路由跳转方法
dart
class AppNavigator {
......
/// 匿名跳转(类似 Get.to)
static Future<T?> to<T>(Widget page, {Object? arguments});
/// 命名跳转(类似 Get.toNamed)
static Future<T?>? toNamed<T>(String routeName, {Object? arguments}) ;
/// 替换当前页面(类似 Get.off)
static Future<T?> off<T>(Widget page, {Object? arguments});
/// 命名替换(类似 Get.offNamed)
static Future<T?>? offNamed<T>(String routeName, {Object? arguments}) ;
static void until(RoutePredicate predicate);
static Future<T?>? offUntil<T>(Route<T> page, RoutePredicate predicate);
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
static Future<T?>? offNamedUntil<T>(
String page,
RoutePredicate predicate, {
int? id,
dynamic arguments,
Map<String, String>? parameters,
});
/// **Navigation.popAndPushNamed()** shortcut.
static Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
dynamic result,
Map<String, String>? parameters,
});
/// **Navigation.removeRoute()** shortcut.
static void removeRoute(Route<dynamic> route);
/// 清空栈并跳转(类似 Get.offAll)
static Future<T?> offAll<T>(Widget page, {Object? arguments});
/// 命名清栈跳转(类似 Get.offAllNamed)
static Future<T?>? offAllNamed<T>(String routeName, {Object? arguments});
/// 返回上一级(类似 Get.back)
static void back<T>([T? result]);
/// **Navigation.popUntil()** (with predicate) shortcut.
static void close(int times, [int? id]);
......
}
三、源码
1、AppNavigator导航类
ini
import 'dart:convert';
import 'package:color_scheme_demo/util/dlog.dart';
import 'package:flutter/material.dart';
import 'AppRouter.dart';
export 'dlog.dart';
/// 路由管理
class AppNavigator {
static bool isLog = false;
/// 全局 NavigatorKey
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
/// 当前 Navigator
static NavigatorState get navigator => navigatorKey.currentState!;
static Map<String, WidgetBuilder> get routeMap => AppRouter.routeMap;
static final List<PageRoute<Object?>> _pageRoutes = [];
static List<PageRoute<Object?>> get pageRoutes => _pageRoutes;
static List<String?> get pageRouteNames => pageRoutes.map((e) => e.settings.name).toList();
/// 之前路由页面
static RouteSettings? _routePre;
/// 之前路由页面
static RouteSettings? get routePre => _routePre;
/// 当前路由页面
static RouteSettings? _route;
/// 当前路由页面
static RouteSettings? get route => _route;
static Object? get argumentsPre => routePre?.arguments;
static String? get routeNamePre => routePre?.name;
static Object? get arguments => route?.arguments;
static String? get routeName => route?.name;
/// 匿名跳转(类似 Get.to)
static Future<T?> to<T>(Widget page, {Object? arguments}) {
return navigator.push<T>(
MaterialPageRoute(builder: (_) => page, settings: RouteSettings(arguments: arguments)),
);
}
/// 命名跳转(类似 Get.toNamed)
static Future<T?>? toNamed<T>(String routeName, {Object? arguments}) {
final builder = routeMap[routeName];
if (builder == null) throw Exception('Route "$routeName" not found.');
return navigator.push<T>(
MaterialPageRoute(builder: builder, settings: RouteSettings(name: routeName, arguments: arguments)),
);
}
/// 替换当前页面(类似 Get.off)
static Future<T?> off<T>(Widget page, {Object? arguments}) {
return navigator.pushReplacement<T, T>(
MaterialPageRoute(builder: (_) => page, settings: RouteSettings(arguments: arguments)),
);
}
/// 命名替换(类似 Get.offNamed)
static Future<T?>? offNamed<T>(String routeName, {Object? arguments}) {
final builder = routeMap[routeName];
if (builder == null) throw Exception('Route "$routeName" not found.');
return navigator.pushReplacement<T, T>(
MaterialPageRoute(builder: builder, settings: RouteSettings(name: routeName, arguments: arguments)),
);
}
static void until(RoutePredicate predicate) {
return navigator.popUntil(predicate);
}
static Future<T?>? offUntil<T>(Route<T> page, RoutePredicate predicate) {
return navigator.pushAndRemoveUntil<T>(page, predicate);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.
static Future<T?>? offNamedUntil<T>(
String page,
RoutePredicate predicate, {
dynamic arguments,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return navigator.pushNamedAndRemoveUntil<T>(
page,
predicate,
arguments: arguments,
);
}
/// **Navigation.popAndPushNamed()** shortcut.
static Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
dynamic result,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return navigator.popAndPushNamed(
page,
arguments: arguments,
result: result,
);
}
/// **Navigation.removeRoute()** shortcut.
static void removeRoute(Route<dynamic> route) {
return navigator.removeRoute(route);
}
/// 清空栈并跳转(类似 Get.offAll)
static Future<T?> offAll<T>(Widget page, {Object? arguments}) {
return navigator.pushAndRemoveUntil<T>(
MaterialPageRoute(builder: (_) => page, settings: RouteSettings(arguments: arguments)),
(route) => false,
);
}
/// 命名清栈跳转(类似 Get.offAllNamed)
static Future<T?>? offAllNamed<T>(String routeName, {Object? arguments}) {
final builder = routeMap[routeName];
if (builder == null) throw Exception('Route "$routeName" not found.');
return navigator.pushAndRemoveUntil<T>(
MaterialPageRoute(builder: builder, settings: RouteSettings(name: routeName, arguments: arguments)),
(route) => false,
);
}
/// 返回上一级(类似 Get.back)
static void back<T>([T? result]) {
if (!navigator.canPop()) {
return;
}
return navigator.pop(result);
}
/// **Navigation.popUntil()** (with predicate) shortcut .
static void close(int times) {
if (times < 1) {
times = 1;
}
var count = 0;
var back = navigator.popUntil((route) => count++ == times);
return back;
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['pageRoutes'] = pageRoutes.map((e) => e.toString()).toList();
data['routeNames'] = pageRouteNames;
data['settingsPre'] = routePre.toString();
data['routeNamePre'] = routeNamePre;
data['routeName'] = routeName;
return data;
}
@override
String toString() {
var encoder = const JsonEncoder.withIndent(' ');
final descption = encoder.convert(toJson());
return "$runtimeType: $descption";
}
}
/// 导航监听
class AppNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
if (previousRoute is PageRoute) {
AppNavigator._routePre = previousRoute.settings;
}
if (route is PageRoute) {
AppNavigator._route = route.settings;
AppNavigator._pageRoutes.add(route);
}
if (AppNavigator.isLog) {
DLog.d([route.settings.name, previousRoute?.settings.name, AppNavigator()].asMap());
}
}
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
// DLog.d(["didPop", route.settings, previousRoute?.settings].asMap());
if (previousRoute is PageRoute) {
AppNavigator._route = previousRoute.settings;
}
if (route is PageRoute) {
AppNavigator._routePre = route.settings;
AppNavigator._pageRoutes.remove(route);
}
if (AppNavigator.isLog) {
DLog.d([route.settings.name, previousRoute?.settings.name, AppNavigator()].asMap());
}
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
// DLog.d(["didReplace", newRoute?.settings, oldRoute?.settings].asMap());
if (oldRoute is PageRoute) {
AppNavigator._routePre = oldRoute.settings;
AppNavigator._pageRoutes.remove(oldRoute);
}
if (newRoute is PageRoute) {
AppNavigator._route = newRoute.settings;
AppNavigator._pageRoutes.add(newRoute);
}
if (AppNavigator.isLog) {
DLog.d([newRoute?.settings.name, oldRoute?.settings.name, AppNavigator()].asMap());
}
}
}
2、AppRouter
dart
import 'package:color_scheme_demo/page/MorePage.dart';
import 'package:flutter/cupertino.dart';
import 'package:color_scheme_demo/page/RestorationMixinDemo.dart';
import 'package:color_scheme_demo/page/HomePage.dart';
import 'package:color_scheme_demo/page/HomePageOne.dart';
import 'package:color_scheme_demo/page/HomePageTwo.dart';
import 'package:color_scheme_demo/page/NotFoundPage.dart';
export 'AppNavigator.dart';
/// 路由页面
class AppPage {
AppPage({
required this.name,
required this.page,
});
final String name;
final WidgetBuilder page;
}
/// 路由定义
class AppRouter {
static const String initial = homePage;
static const String notFoundPage = '/notFoundPage';
static const String homePage = '/homePage';
static const String homePageOne = '/homePageOne';
static const String homePageTwo = '/homePageTwo';
static const String restorationMixinDemo = '/restorationMixinDemo';
static const String detailPage = '/detailPage';
static const String morePage = '/detailPage';
static Map<String, WidgetBuilder> routeMap = Map<String, WidgetBuilder>.fromEntries(
routes.map((e) => MapEntry<String, WidgetBuilder>(e.name, e.page)),
);
static final List<AppPage> routes = [
AppPage(
name: AppRouter.notFoundPage,
page: (context) => NotFoundPage(),
),
AppPage(
name: AppRouter.homePage,
page: (context) => HomePage(),
),
AppPage(
name: AppRouter.homePageOne,
page: (context) => HomePageOne(),
),
AppPage(
name: AppRouter.homePageTwo,
page: (context) => HomePageTwo(),
),
AppPage(
name: AppRouter.restorationMixinDemo,
page: (context) => RestorationMixinDemo(),
),
AppPage(
name: AppRouter.morePage,
page: (context) {
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>? ?? {};
return MorePage(
id: args['id'],
arguments: args,
);
},
),
];
}
最后、总结
实现起来并不复杂,当你因为某些原因不想(能)引入第三方路由管理时,通过这样极简封装就可以很高效。
AppNavigator优点
- 使用不需要 context参数。
- 类支持路由堆栈查询(PageRoute数组) AppNavigator.pageRoutes。
- 当前路由 AppNavigator.route 与之前路由 AppNavigator.routePre。
- 当前页面路由获取 AppNavigator.routeName。
- 当前页面参数获取 AppNavigator.arguments。