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

相关推荐
共享家95273 分钟前
基于 Coze 工作流搭建历史主题图片生成器
前端·人工智能·js
zhaoyin19945 分钟前
fiddler抓包工具使用
前端·测试工具·fiddler
微祎_6 分钟前
Flutter for OpenHarmony:构建一个 Flutter 单词拼图游戏,深入解析状态驱动 UI、交互式字母操作与教育类应用设计
javascript·flutter·ui
一起养小猫10 分钟前
Flutter for OpenHarmony 实战:文件加密解密器完整开发指南
flutter
Doris89323 分钟前
【 Vue】 Vue3全面讲解文档
前端·javascript·vue.js
linweidong30 分钟前
大厂工程化实践:如何构建可运维、高稳定性的 Flutter 混合体系
javascript·flutter
Hexene...33 分钟前
【前端Vue】如何快速直观地查看引入的前端依赖?名称版本、仓库地址、开源协议、作者、依赖介绍、关系树...(Node Modules Inspector)
前端·javascript·vue.js
fanruitian35 分钟前
div水平垂直居中
前端·javascript·html
旭久38 分钟前
react+antd实现一个支持多种类型及可新增编辑搜索的下拉框
前端·javascript·react.js
一起养小猫1 小时前
Flutter for OpenHarmony 进阶:异步编程与同步机制深度解析
flutter·harmonyos