Flutter封装:原生路由管理极简封装 AppNavigator

一、需求来源

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

  1. 使用不需要 context参数。
  2. 类支持路由堆栈查询(PageRoute数组) AppNavigator.pageRoutes。
  3. 当前路由 AppNavigator.route 与之前路由 AppNavigator.routePre。
  4. 当前页面路由获取 AppNavigator.routeName。
  5. 当前页面参数获取 AppNavigator.arguments。

AppRouter.dart

AppNavigator.dart

相关推荐
却尘12 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare13 分钟前
浅浅看一下设计模式
前端
Lee川16 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
不爱吃糖的程序媛31 分钟前
解锁Flutter鸿蒙开发新姿势——flutter_ohfeatures插件集实战指南
flutter
Ticnix43 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端