如何 Flutter 中封装一个 `Router.dart` 文件来统一管理页面跳转?

33 阅读5分钟

自己学习的小demo地址放在这里,仅供学习参考,flutter学习demo

在 Flutter 中封装一个 Router.dart 文件来统一管理页面跳转是一个非常实用的做法,可以提升代码的可维护性和复用性。下面我将为你提供一个结构清晰、功能全面的 Router 类封装示例,并附上使用说明。


✅ 1. 创建 Router.dart 文件

// lib/router/router.dart

import 'package:flutter/material.dart';

class Router {
  // 路由表:定义所有页面的路由路径与对应的构建器
  static final Map<String, WidgetBuilder> routeTable = {
    '/': (context) => const HomeScreen(),
    '/detail': (context) => DetailScreen(
        arguments: ModalRoute.of(context)!.settings.arguments as String),
    '/settings': (context) => const SettingsScreen(),
  };

  // 页面跳转(带返回按钮)
  static Future<T?> pushNamed<T extends Object?>(
    BuildContext context,
    String routeName, {
    Object? arguments,
  }) {
    return Navigator.pushNamed<T>(
      context,
      routeName,
      arguments: arguments,
    );
  }

  // 页面跳转并替换当前页面(无返回按钮)
  static Future<T?> pushReplacementNamed<T extends Object?>(
    BuildContext context,
    String routeName, {
    Object? arguments,
  }) {
    return Navigator.pushReplacementNamed<T, Object?>(
      context,
      routeName,
      arguments: arguments,
    );
  }

  // 弹出当前页面(返回上一页)
  static void pop(BuildContext context, [Object? result]) {
    Navigator.of(context).pop(result);
  }

  // 弹出当前页面并返回数据
  static void popWithResult(BuildContext context, Object? result) {
    Navigator.of(context).pop(result);
  }

  // 弹出所有页面并跳转到指定页面
  static Future<T?> pushAndRemoveUntil<T extends Object?>(
    BuildContext context,
    String routeName, {
    Object? arguments,
  }) {
    return Navigator.of(context).pushNamedAndRemoveUntil(
      routeName,
      (route) => false,
      arguments: arguments,
    );
  }

  // 处理未注册的路由
  static Route<dynamic> onUnknownRoute(RouteSettings settings) {
    return MaterialPageRoute(
      builder: (context) => const ErrorScreen(),
      settings: settings,
    );
  }
}

✅ 2. 定义页面组件(示例)

// lib/screens/home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Router.pushNamed(context, '/detail', arguments: 'Detail Page');
          },
          child: const Text('Go to Detail'),
        ),
      ),
    );
  }
}
// lib/screens/detail_screen.dart

import 'package:flutter/material.dart';

class DetailScreen extends StatelessWidget {
  final String arguments;

  const DetailScreen({Key? key, required this.arguments}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail: $arguments')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Router.pop(context),
          child: const Text('Back'),
        ),
      ),
    );
  }
}
// lib/screens/settings_screen.dart

import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: const Center(child: Text('Settings Page')),
    );
  }
}
// lib/screens/error_screen.dart

import 'package:flutter/material.dart';

class ErrorScreen extends StatelessWidget {
  const ErrorScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('404')),
      body: const Center(child: Text('Page Not Found')),
    );
  }
}

✅ 3. 在 main.dart 中注册路由

// lib/main.dart

import 'package:flutter/material.dart';
import 'router/router.dart';
import 'screens/home_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Router Demo',
      initialRoute: '/',
      onGenerateRoute: Router.routeTable.isEmpty
          ? null
          : (settings) => MaterialPageRoute(
                builder: (context) => Router.routeTable[settings.name]!(context),
                settings: settings,
              ),
      onUnknownRoute: Router.onUnknownRoute,
      home: const HomeScreen(),
    );
  }
}

✅ 4. 使用封装的跳转方法

// 示例:跳转到详情页并传递参数
Router.pushNamed(context, '/detail', arguments: 'Hello from Home');

// 示例:跳转并替换当前页面
Router.pushReplacementNamed(context, '/settings');

// 示例:弹出当前页面
Router.pop(context);

// 示例:弹出所有页面并跳转到首页
Router.pushAndRemoveUntil(context, '/');

✅ 5. 优势总结

优点说明
统一管理所有页面跳转逻辑集中在 Router 类中,便于维护。
参数传递支持带参数的跳转和返回数据处理。
错误处理自动处理未注册的路由(404 页面)。
可扩展性强可轻松添加新页面或修改现有路由规则。

通过这种方式,你可以将 Flutter 应用中的页面跳转逻辑统一管理,提升代码结构的清晰度和可维护性。如果你有更复杂的需求(如嵌套路由、动画跳转等),也可以在此基础上扩展。

✅ 6. 命名空间错误解决

这个错误是因为 Flutter 框架中的 Router 类(来自 package:flutter/src/widgets/router.dart)与你自定义的 Router 类(来自 package:my_app/alearn_demo/router_demo/router.dart)发生了 命名冲突。你需要通过 重命名导入隐藏冲突类 的方式来解决这个问题。


✅ 解决方案一:使用 as 重命名导入

通过给其中一个库添加别名,区分冲突的 Router 类。

修改 main.dart 或其他使用 Router 的文件

// 1. 导入 Flutter 的 Material 库(默认会引入 Router 类)
import 'package:flutter/material.dart' as flutter;

// 2. 导入自定义的 Router 类(添加别名)
import 'package:my_app/alearn_demo/router_demo/router.dart' as my_router;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return flutter.MaterialApp(
      title: 'Router Demo',
      initialRoute: '/',
      onGenerateRoute: (settings) => flutter.MaterialPageRoute(
        builder: (context) => my_router.Router.routeTable[settings.name]!(context),
      ),
      onUnknownRoute: my_router.Router.onUnknownRoute,
      home: const my_router.HomeScreen(),
    );
  }
}

修改自定义的 Router.dart 文件

确保你的 Router 类使用的是自己的命名空间:

// lib/alearn_demo/router_demo/router.dart

import 'package:flutter/material.dart';

class Router {
  // 你的路由表和跳转方法...
}

✅ 解决方案二:使用 hide 隐藏冲突类

如果只需要使用 Flutter 的 MaterialApp 等组件,但不需要框架的 Router 类,可以通过 hide 隐藏它。

修改导入语句

// 隐藏 Flutter 中的 Router 类
import 'package:flutter/material.dart' hide Router;

// 导入自定义的 Router 类
import 'package:my_app/alearn_demo/router_demo/router.dart';

✅ 补充说明

  1. 为什么会出现冲突?

    • Flutter 的 MaterialApp 会隐式导入 material.dart,而 material.dart 会引入 Router 类(用于新路由系统)。如果你的代码中也定义了一个 Router 类,就会发生冲突。
  2. 推荐做法

    • 如果你使用的是 Flutter 2.5+ 的 新路由系统(基于 Router 类),则需要保留 Flutter 的 Router,并重命名你的自定义路由管理类(如 MyRouter)。
    • 如果你使用的是传统的 Navigator 路由(基于 pushNamed),可以安全地隐藏 Flutter 的 Router 类。

✅ 修改后的完整示例

main.dart

// 隐藏 Flutter 的 Router 类
import 'package:flutter/material.dart' hide Router;

// 导入自定义的 Router 类
import 'package:my_app/alearn_demo/router_demo/router.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Router Demo',
      initialRoute: '/',
      onGenerateRoute: (settings) => MaterialPageRoute(
        builder: (context) => Router.routeTable[settings.name]!(context),
      ),
      onUnknownRoute: Router.onUnknownRoute,
      home: const HomeScreen(),
    );
  }
}

Router.dart

// lib/alearn_demo/router_demo/router.dart

import 'package:flutter/material.dart';

class Router {
  static final Map<String, WidgetBuilder> routeTable = {
    '/': (context) => const HomeScreen(),
    '/detail': (context) => DetailScreen(),
  };

  static Future<T?> pushNamed<T extends Object?>(
    BuildContext context,
    String routeName,
  ) {
    return Navigator.pushNamed<T>(context, routeName);
  }

  // 其他方法...
}

✅ 总结

问题原因解决方案
Router 类冲突Flutter 框架和自定义代码中都定义了 Router使用 as 重命名导入或 hide 隐藏冲突类
推荐做法使用传统 Navigator 路由时隐藏 Flutter 的 Router
新路由系统需求使用 Flutter 的 Router重命名自定义类(如 MyRouter

通过以上方法,你可以轻松解决命名冲突问题,并保持代码的清晰性。