Flutter 路由导航完全指南

引言

路由导航是任何移动应用的核心功能之一。Flutter 提供了强大而灵活的路由导航系统,支持多种导航方式。本文将深入探讨 Flutter 路由导航的各种用法和高级技巧。

基础概念回顾

路由类型

  • 命名路由: 通过名称导航
  • 匿名路由: 直接使用 Navigator.push
  • 嵌套路由: 在父路由中嵌套子路由

核心组件

  • Navigator: 管理路由栈
  • Route: 单个路由对象
  • MaterialPageRoute: Material风格路由
  • CupertinoPageRoute: iOS风格路由

高级技巧一:基础导航

基本导航

// 导航到新页面
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SecondPage()),
);

// 导航并返回数据
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SecondPage()),
);

// 弹出当前页面
Navigator.pop(context);

// 弹出并返回数据
Navigator.pop(context, 'result');

命名路由

// 注册路由
MaterialApp(
  routes: {
    '/': (context) => const HomePage(),
    '/second': (context) => const SecondPage(),
  },
);

// 使用命名路由导航
Navigator.pushNamed(context, '/second');

// 带参数的命名路由
Navigator.pushNamed(context, '/second', arguments: 'Hello');

// 获取参数
final args = ModalRoute.of(context)?.settings.arguments as String;

高级技巧二:路由管理

路由配置

class AppRouter {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => const HomePage());
      case '/second':
        return MaterialPageRoute(builder: (_) => const SecondPage());
      case '/detail':
        final id = settings.arguments as int;
        return MaterialPageRoute(builder: (_) => DetailPage(id: id));
      default:
        return MaterialPageRoute(
          builder: (_) => const NotFoundPage(),
        );
    }
  }
}

// 使用
MaterialApp(
  onGenerateRoute: AppRouter.generateRoute,
);

路由守卫

class AuthGuard extends StatelessWidget {
  final Widget child;

  const AuthGuard({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    final isLoggedIn = AuthService.instance.isLoggedIn;
    
    if (!isLoggedIn) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        Navigator.pushReplacementNamed(context, '/login');
      });
      return const SplashScreen();
    }
    
    return child;
  }
}

// 使用
MaterialApp(
  routes: {
    '/home': (context) => const AuthGuard(child: HomePage()),
  },
);

高级技巧三:页面过渡动画

自定义页面路由

class SlideRightRoute extends PageRouteBuilder {
  final Widget page;

  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) =>
              page,
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) =>
              SlideTransition(
            position: Tween<Offset>(
              begin: const Offset(1.0, 0.0),
              end: Offset.zero,
            ).animate(animation),
            child: child,
          ),
        );
}

// 使用
Navigator.push(context, SlideRightRoute(page: const SecondPage()));

淡入淡出动画

class FadeRoute extends PageRouteBuilder {
  final Widget page;

  FadeRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) =>
              FadeTransition(
            opacity: animation,
            child: child,
          ),
        );
}

缩放动画

class ScaleRoute extends PageRouteBuilder {
  final Widget page;

  ScaleRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) =>
              ScaleTransition(
            scale: Tween<double>(begin: 0.5, end: 1.0).animate(
              CurvedAnimation(parent: animation, curve: Curves.easeOut),
            ),
            child: child,
          ),
        );
}

高级技巧四:嵌套导航

Tab导航

class TabNavigator extends StatelessWidget {
  const TabNavigator({super.key, required this.navigatorKey});

  final GlobalKey<NavigatorState> navigatorKey;

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      initialRoute: '/',
      onGenerateRoute: (settings) {
        return MaterialPageRoute(
          builder: (context) => const TabPage(),
        );
      },
    );
  }
}

// 使用
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        body: TabBarView(
          children: [
            TabNavigator(navigatorKey: GlobalKey()),
            TabNavigator(navigatorKey: GlobalKey()),
            TabNavigator(navigatorKey: GlobalKey()),
          ],
        ),
        bottomNavigationBar: const TabBar(
          tabs: [
            Tab(icon: Icon(Icons.home)),
            Tab(icon: Icon(Icons.search)),
            Tab(icon: Icon(Icons.person)),
          ],
        ),
      ),
    );
  }
}

实战案例:路由管理类

class AppNavigator {
  static void goToHome(BuildContext context) {
    Navigator.pushNamedAndRemoveUntil(
      context,
      '/home',
      (route) => false,
    );
  }

  static void goToLogin(BuildContext context) {
    Navigator.pushNamed(context, '/login');
  }

  static Future<void> goToDetail(BuildContext context, int id) async {
    await Navigator.pushNamed(
      context,
      '/detail',
      arguments: id,
    );
  }

  static void goBack(BuildContext context) {
    Navigator.pop(context);
  }

  static void goBackWithResult(BuildContext context, dynamic result) {
    Navigator.pop(context, result);
  }
}

// 使用
AppNavigator.goToHome(context);
AppNavigator.goToDetail(context, 123);

实战案例:动态路由

class DynamicRoute extends StatelessWidget {
  const DynamicRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dynamic Route')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 动态生成路由
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DynamicDetailPage(),
                settings: const RouteSettings(
                  name: '/dynamic-detail',
                  arguments: {'id': 1, 'name': 'Dynamic'},
                ),
              ),
            );
          },
          child: const Text('Go to Dynamic Detail'),
        ),
      ),
    );
  }
}

实战案例:路由监听

class RouteObserverService extends RouteObserver<PageRoute<dynamic>> {
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPush(route, previousRoute);
    if (route is PageRoute) {
      print('Pushed: ${route.settings.name}');
      AnalyticsService.logPageView(route.settings.name ?? '');
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPop(route, previousRoute);
    if (route is PageRoute) {
      print('Popped: ${route.settings.name}');
    }
  }
}

// 使用
MaterialApp(
  navigatorObservers: [RouteObserverService()],
);

实战案例:Deep Link 处理

class DeepLinkHandler {
  static void handleDeepLink(Uri uri) {
    final path = uri.path;
    final params = uri.queryParameters;

    switch (path) {
      case '/detail':
        final id = int.parse(params['id'] ?? '0');
        // 导航到详情页
        break;
      case '/search':
        final query = params['q'] ?? '';
        // 导航到搜索页
        break;
      default:
        // 导航到首页
        break;
    }
  }
}

// 在 main 中处理
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 处理初始 deep link
  final initialLink = await getInitialLink();
  if (initialLink != null) {
    DeepLinkHandler.handleDeepLink(Uri.parse(initialLink));
  }
  
  // 监听后续 deep link
  linkStream.listen((link) {
    if (link != null) {
      DeepLinkHandler.handleDeepLink(Uri.parse(link));
    }
  });
  
  runApp(const MyApp());
}

常见问题与解决方案

Q1:如何返回数据?

A:使用 Navigator.pop(context, result)

// 目标页面
Navigator.pop(context, 'result');

// 调用方
final result = await Navigator.push(context, route);

Q2:如何清除路由栈?

A:使用 pushNamedAndRemoveUntil

Navigator.pushNamedAndRemoveUntil(
  context,
  '/home',
  (route) => false, // 移除所有路由
);

Q3:如何传递复杂参数?

A:使用 arguments:

// 传递
Navigator.pushNamed(context, '/detail', arguments: {'id': 1, 'data': {...}});

// 获取
final args = ModalRoute.of(context)?.settings.arguments as Map;

最佳实践

1. 集中管理路由

// routes.dart
abstract class Routes {
  static const home = '/';
  static const login = '/login';
  static const detail = '/detail';
}

// 使用
Navigator.pushNamed(context, Routes.detail);

2. 使用类型安全的参数

class DetailArguments {
  final int id;
  final String name;

  DetailArguments({required this.id, required this.name});
}

// 传递
Navigator.pushNamed(
  context,
  Routes.detail,
  arguments: DetailArguments(id: 1, name: 'Test'),
);

// 获取
final args = ModalRoute.of(context)?.settings.arguments as DetailArguments;

3. 使用 Navigator 扩展

extension NavigatorExtension on BuildContext {
  void pushNamed(String routeName, {Object? arguments}) {
    Navigator.pushNamed(this, routeName, arguments: arguments);
  }

  void pop([Object? result]) {
    Navigator.pop(this, result);
  }
}

// 使用
context.pushNamed(Routes.home);
context.pop();

总结

Flutter 的路由导航系统非常强大和灵活。通过本文的学习,你应该能够:

  1. 使用基础导航方法
  2. 配置和管理命名路由
  3. 创建自定义页面过渡动画
  4. 实现嵌套导航
  5. 处理 Deep Link

掌握这些技巧,能够帮助你创建更加流畅和用户友好的导航体验。

更多推荐