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

相关推荐
menu4 小时前
AI给我的建议
前端
张可爱4 小时前
20251018-JavaScript八股文整理版(上篇)
前端
小小测试开发4 小时前
Python Arrow库:告别datetime繁琐,优雅处理时间与时区
开发语言·前端·python
自律版Zz4 小时前
手写 Promise.resolve:从使用场景到实现的完整推导
前端·javascript
golang学习记4 小时前
从0死磕全栈之Next.js 自定义 Server 指南:何时使用及如何实现
前端
张可爱4 小时前
从奶茶店悟透 JavaScript:递归、继承、浮点数精度、尾递归全解析(通俗易懂版)
前端
梵得儿SHI4 小时前
Vue 开发环境搭建全指南:从工具准备到项目启动
前端·javascript·vue.js·node.js·pnpm·vue开发环境·nvm版本管理
八月ouc4 小时前
每日小知识点:10.14 webpack 有几种文件指纹
前端·webpack