Flutter Navigator 深度学习

目录

  1. [Navigator 基础概念](#Navigator 基础概念 "#1-navigator-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5")
  2. 核心类和接口
  3. [路由管理 API](#路由管理 API "#3-%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86-api")
  4. [声明式导航 (Pages API)](#声明式导航 (Pages API) "#4-%E5%A3%B0%E6%98%8E%E5%BC%8F%E5%AF%BC%E8%88%AA-pages-api")
  5. 状态恢复
  6. 高级特性
  7. 最佳实践
  8. 实战示例

Navigator 是 Flutter 中管理路由栈的核心 Widget,它维护一个基于栈的路由历史记录,支持路由的推入(push)和弹出(pop)操作。

dart 复制代码
// Navigator 的基本结构
Navigator(
  pages: <Page<dynamic>>[],           // 声明式页面列表
  onPopPage: (route, result) => true, // 页面弹出回调
  initialRoute: '/',                  // 初始路由
  onGenerateRoute: (settings) {},     // 路由生成器
  observers: [],                      // 路由观察者
)

1.2 核心概念

Route(路由)
  • 路由是对屏幕或页面的抽象
  • 包含视觉呈现和过渡动画
  • 可以返回结果值
RouteSettings(路由设置)
dart 复制代码
class RouteSettings {
  const RouteSettings({
    this.name,        // 路由名称,如 "/settings"
    this.arguments,   // 传递给路由的参数
  });
  
  final String? name;
  final Object? arguments;
}
Page(页面)
  • Page 是 RouteSettings 的子类
  • 用于声明式导航
  • 支持状态恢复

2. 核心类和接口

2.1 Route 类

dart 复制代码
abstract class Route<T> {
  // 路由所属的 Navigator
  NavigatorState? get navigator;
  
  // 路由的设置信息
  RouteSettings get settings;
  
  // Overlay 条目列表
  List<OverlayEntry> get overlayEntries;
  
  // 生命周期方法
  void install() {}
  TickerFuture didPush() {}
  void didAdd() {}
  bool didPop(T? result) {}
  void didComplete(T? result) {}
  
  // 状态查询
  bool get isCurrent;   // 是否是栈顶路由
  bool get isFirst;     // 是否是栈底路由
  bool get isActive;    // 是否在栈中
}

NavigatorState 是 Navigator 的状态类,提供了所有导航操作的方法:

dart 复制代码
class NavigatorState extends State<Navigator> {
  // 命令式 API - push 系列
  Future<T?> push<T>(Route<T> route);
  Future<T?> pushNamed<T>(String routeName, {Object? arguments});
  Future<T?> pushReplacement<T, TO>(Route<T> newRoute, {TO? result});
  Future<T?> pushAndRemoveUntil<T>(Route<T> newRoute, RoutePredicate predicate);
  
  // 命令式 API - pop 系列
  void pop<T>([T? result]);
  void popUntil(RoutePredicate predicate);
  Future<bool> maybePop<T>([T? result]);
  bool canPop();
  
  // 可恢复 API
  String restorablePush<T>(RestorableRouteBuilder<T> routeBuilder, {Object? arguments});
  String restorablePushNamed<T>(String routeName, {Object? arguments});
  
  // 路由替换
  void replace<T>({required Route<dynamic> oldRoute, required Route<T> newRoute});
  void replaceRouteBelow<T>({required Route<dynamic> anchorRoute, required Route<T> newRoute});
  
  // 路由移除
  void removeRoute(Route<dynamic> route);
  void removeRouteBelow(Route<dynamic> anchorRoute);
}
dart 复制代码
class NavigatorObserver {
  NavigatorState? get navigator;
  
  // 观察路由变化
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {}
  
  // 观察用户手势
  void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  void didStopUserGesture() {}
}

3. 路由管理 API

3.1 Push 操作

基础 Push
dart 复制代码
// 1. 使用 Route 对象
Navigator.push(
  context,
  MaterialPageRoute<void>(
    builder: (BuildContext context) => const MyPage(),
  ),
);

// 2. 使用命名路由
Navigator.pushNamed(
  context,
  '/settings',
  arguments: {'userId': 123},
);

// 3. 可恢复 Push(支持状态恢复)
@pragma('vm:entry-point')
static Route<void> _myRouteBuilder(BuildContext context, Object? arguments) {
  return MaterialPageRoute<void>(
    builder: (context) => MyPage(data: arguments),
  );
}

Navigator.restorablePush(context, _myRouteBuilder, arguments: myData);
PushReplacement(替换当前路由)
dart 复制代码
// 替换当前路由,常用于登录后跳转
Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomePage(),
  ),
);

// 命名路由版本
Navigator.pushReplacementNamed(context, '/home', result: 'login_success');
PushAndRemoveUntil(移除直到某条件)
dart 复制代码
// 推入新路由并移除所有之前的路由
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => const HomePage()),
  (Route<dynamic> route) => false, // 移除所有路由
);

// 推入新路由并保留到某个路由
Navigator.pushNamedAndRemoveUntil(
  context,
  '/home',
  ModalRoute.withName('/login'), // 保留到 /login 路由
);

3.2 Pop 操作

基础 Pop
dart 复制代码
// 1. 简单返回
Navigator.pop(context);

// 2. 返回数据
Navigator.pop(context, 'result_data');

// 3. 安全返回(检查是否可以返回)
if (Navigator.canPop(context)) {
  Navigator.pop(context);
}

// 4. 尝试返回(如果不能返回则不执行)
final bool didPop = await Navigator.maybePop(context);
if (didPop) {
  print('已返回');
} else {
  print('无法返回,可能是根路由');
}
PopUntil(返回到某个条件)
dart 复制代码
// 返回到首页
Navigator.popUntil(context, ModalRoute.withName('/'));

// 返回到满足条件的路由
Navigator.popUntil(context, (route) {
  return route.settings.name == '/target' || route.isFirst;
});
dart 复制代码
// 1. 获取最近的 Navigator
final navigator = Navigator.of(context);

// 2. 获取根 Navigator(跳过嵌套的 Navigator)
final rootNavigator = Navigator.of(context, rootNavigator: true);

// 3. 安全获取 Navigator(可能返回 null)
final navigator = Navigator.maybeOf(context);
if (navigator != null) {
  navigator.pop();
}

4. 声明式导航 (Pages API)

4.1 基本概念

声明式导航使用 pages 属性来管理路由栈,更符合 Flutter 的声明式编程范式。

dart 复制代码
class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final List<Page> _pages = [];
  
  @override
  void initState() {
    super.initState();
    _pages.add(MaterialPage(
      key: const ValueKey('HomePage'),
      child: HomePage(
        onNavigate: _handleNavigate,
      ),
    ));
  }
  
  void _handleNavigate(String destination) {
    setState(() {
      _pages.add(MaterialPage(
        key: ValueKey(destination),
        child: DetailPage(destination: destination),
      ));
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Navigator(
        pages: List.of(_pages),
        onPopPage: (route, result) {
          if (!route.didPop(result)) {
            return false;
          }
          setState(() {
            _pages.remove(route.settings);
          });
          return true;
        },
      ),
    );
  }
}

4.2 Page 类

dart 复制代码
// 自定义 Page
class MyCustomPage extends Page {
  const MyCustomPage({
    required this.child,
    super.key,
    super.name,
    super.arguments,
    super.restorationId,
  });
  
  final Widget child;
  
  @override
  Route createRoute(BuildContext context) {
    return MaterialPageRoute(
      settings: this,
      builder: (context) => child,
    );
  }
}

4.3 TransitionDelegate(过渡委托)

自定义路由过渡行为:

dart 复制代码
class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
  @override
  Iterable<RouteTransitionRecord> resolve({
    required List<RouteTransitionRecord> newPageRouteHistory,
    required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
    required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
  }) {
    final List<RouteTransitionRecord> results = [];
    
    // 所有进入的路由不使用动画
    for (final pageRoute in newPageRouteHistory) {
      if (pageRoute.isWaitingForEnteringDecision) {
        pageRoute.markForAdd(); // 无动画添加
      }
      results.add(pageRoute);
    }
    
    // 所有退出的路由不使用动画
    for (final exitingPageRoute in locationToExitingPageRoute.values) {
      if (exitingPageRoute.isWaitingForExitingDecision) {
        exitingPageRoute.markForRemove(); // 无动画移除
      }
      results.add(exitingPageRoute);
    }
    
    return results;
  }
}

// 使用自定义 TransitionDelegate
Navigator(
  pages: pages,
  onPopPage: onPopPage,
  transitionDelegate: NoAnimationTransitionDelegate(),
)

5. 状态恢复

5.1 启用状态恢复

dart 复制代码
// 1. 为 Navigator 提供 restorationScopeId
Navigator(
  restorationScopeId: 'main_navigator',
  // ...
)

// 2. 为 Page 提供 restorationId
MaterialPage(
  key: ValueKey('detail_page'),
  restorationId: 'detail_page_1',
  child: DetailPage(),
)

// 3. 使用可恢复的命令式 API
final String routeId = Navigator.restorablePushNamed(
  context,
  '/settings',
  arguments: {'section': 'privacy'},
);

5.2 RestorableRouteFuture

用于跟踪可恢复路由的返回值:

dart 复制代码
class _MyPageState extends State<MyPage> with RestorationMixin {
  final RestorableRouteFuture<int> _counterRouteFuture = 
    RestorableRouteFuture<int>(
      onPresent: (NavigatorState navigator, Object? arguments) {
        return navigator.restorablePush(
          _buildCounterRoute,
          arguments: arguments,
        );
      },
      onComplete: (int result) {
        setState(() {
          _counter = result;
        });
      },
    );
  
  @override
  String get restorationId => 'my_page';
  
  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_counterRouteFuture, 'counter_route');
  }
  
  void _showCounter() {
    _counterRouteFuture.present(_counter);
  }
  
  @pragma('vm:entry-point')
  static Route<int> _buildCounterRoute(
    BuildContext context,
    Object? arguments,
  ) {
    return MaterialPageRoute<int>(
      builder: (context) => CounterPage(
        initialValue: arguments as int,
      ),
    );
  }
}

6. 高级特性

dart 复制代码
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Navigator(
        // 嵌套的 Navigator
        onGenerateRoute: (settings) {
          return MaterialPageRoute(
            builder: (context) {
              switch (settings.name) {
                case '/tab1':
                  return Tab1Page();
                case '/tab2':
                  return Tab2Page();
                default:
                  return Tab1Page();
              }
            },
          );
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: (index) {
          // 使用嵌套 Navigator
          Navigator.of(context).pushReplacementNamed(
            index == 0 ? '/tab1' : '/tab2',
          );
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Tab 1'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Tab 2'),
        ],
      ),
    );
  }
}

6.2 HeroController

Hero 动画控制:

dart 复制代码
// 提供 HeroController
HeroControllerScope(
  controller: HeroController(),
  child: Navigator(
    // ...
  ),
)

// 使用 Hero 动画
Hero(
  tag: 'profile_image',
  child: Image.network(imageUrl),
)

6.3 RoutePopDisposition

控制路由弹出行为:

dart 复制代码
class MyRoute<T> extends PageRoute<T> {
  @override
  RoutePopDisposition get popDisposition {
    if (hasUnsavedChanges) {
      return RoutePopDisposition.doNotPop; // 阻止返回
    }
    if (isFirstRoute) {
      return RoutePopDisposition.bubble; // 传递给上层
    }
    return RoutePopDisposition.pop; // 正常返回
  }
  
  @override
  void onPopInvoked(bool didPop) {
    if (!didPop && hasUnsavedChanges) {
      // 显示确认对话框
      showUnsavedChangesDialog();
    }
  }
}

6.4 用户手势追踪

dart 复制代码
class MyNavigatorObserver extends NavigatorObserver {
  @override
  void didStartUserGesture(
    Route<dynamic> route,
    Route<dynamic>? previousRoute,
  ) {
    print('用户开始滑动返回手势');
    // 可以暂停动画、禁用某些功能等
  }
  
  @override
  void didStopUserGesture() {
    print('用户手势结束');
    // 恢复功能
  }
}

7. 最佳实践

7.1 路由命名规范

dart 复制代码
class AppRoutes {
  // 使用常量定义路由名称
  static const String home = '/';
  static const String login = '/login';
  static const String profile = '/profile';
  static const String settings = '/settings';
  static const String settingsPrivacy = '/settings/privacy';
  
  // 使用工厂方法生成带参数的路由
  static String userProfile(String userId) => '/user/$userId';
}

// 使用
Navigator.pushNamed(context, AppRoutes.profile);
Navigator.pushNamed(context, AppRoutes.userProfile('123'));

7.2 参数传递

dart 复制代码
// 1. 使用 arguments 参数
Navigator.pushNamed(
  context,
  '/detail',
  arguments: DetailPageArguments(
    id: '123',
    title: 'My Title',
  ),
);

// 在目标页面接收
class DetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as DetailPageArguments;
    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      // ...
    );
  }
}

// 2. 使用构造函数(推荐)
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(
      id: '123',
      title: 'My Title',
    ),
  ),
);

7.3 错误处理

dart 复制代码
MaterialApp(
  onGenerateRoute: (settings) {
    // 处理已知路由
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => HomePage());
      case '/profile':
        return MaterialPageRoute(builder: (_) => ProfilePage());
    }
    return null;
  },
  onUnknownRoute: (settings) {
    // 处理未知路由
    return MaterialPageRoute(
      builder: (_) => NotFoundPage(routeName: settings.name),
    );
  },
)

7.4 等待返回结果

dart 复制代码
// 推入页面并等待结果
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(
    builder: (context) => SelectionPage(),
  ),
);

if (result != null) {
  print('用户选择了: $result');
} else {
  print('用户取消了选择');
}

// 在 SelectionPage 中返回结果
Navigator.pop(context, 'selected_value');

7.5 条件导航

dart 复制代码
void navigateToNextPage() {
  if (!isLoggedIn) {
    Navigator.pushNamed(context, '/login');
    return;
  }
  
  if (!hasCompletedProfile) {
    Navigator.pushNamed(context, '/complete_profile');
    return;
  }
  
  Navigator.pushNamed(context, '/dashboard');
}

8. 实战示例

8.1 完整的应用导航结构

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: 'Navigator Demo',
      initialRoute: '/',
      routes: {
        '/': (context) => const HomePage(),
        '/login': (context) => const LoginPage(),
        '/profile': (context) => const ProfilePage(),
      },
      onGenerateRoute: (settings) {
        // 处理动态路由
        if (settings.name?.startsWith('/user/') ?? false) {
          final userId = settings.name!.split('/').last;
          return MaterialPageRoute(
            builder: (context) => UserPage(userId: userId),
          );
        }
        return null;
      },
      onUnknownRoute: (settings) {
        return MaterialPageRoute(
          builder: (context) => NotFoundPage(),
        );
      },
      navigatorObservers: [
        MyNavigatorObserver(),
      ],
    );
  }
}

8.2 带动画的自定义路由

dart 复制代码
class SlideRightRoute extends PageRouteBuilder {
  final Widget page;
  
  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;
            
            final tween = Tween(begin: begin, end: end)
                .chain(CurveTween(curve: curve));
            final offsetAnimation = animation.drive(tween);
            
            return SlideTransition(
              position: offsetAnimation,
              child: child,
            );
          },
          transitionDuration: const Duration(milliseconds: 300),
        );
}

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

8.3 底部弹窗导航

dart 复制代码
void showBottomSheetRoute(BuildContext context) {
  Navigator.push(
    context,
    ModalBottomSheetRoute(
      builder: (context) => Container(
        height: 400,
        child: Column(
          children: [
            ListTile(
              title: Text('选项 1'),
              onTap: () => Navigator.pop(context, '选项 1'),
            ),
            ListTile(
              title: Text('选项 2'),
              onTap: () => Navigator.pop(context, '选项 2'),
            ),
          ],
        ),
      ),
    ),
  );
}

8.4 对话框作为路由

dart 复制代码
Future<bool?> showConfirmDialog(BuildContext context) {
  return Navigator.push<bool>(
    context,
    DialogRoute(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('确认'),
        content: Text('确定要删除吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: Text('取消'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: Text('确定'),
          ),
        ],
      ),
    ),
  );
}

// 使用
final confirmed = await showConfirmDialog(context);
if (confirmed == true) {
  deleteItem();
}

8.5 带状态恢复的完整示例

dart 复制代码
class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with RestorationMixin {
  final RestorableInt _counter = RestorableInt(0);
  
  late RestorableRouteFuture<int> _detailRouteFuture;
  
  @override
  void initState() {
    super.initState();
    _detailRouteFuture = RestorableRouteFuture<int>(
      onPresent: (navigator, arguments) {
        return navigator.restorablePush(
          _buildDetailRoute,
          arguments: arguments,
        );
      },
      onComplete: (result) {
        setState(() {
          _counter.value = result;
        });
      },
    );
  }
  
  @override
  String get restorationId => 'home_page';
  
  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_counter, 'counter');
    registerForRestoration(_detailRouteFuture, 'detail_route');
  }
  
  @override
  void dispose() {
    _counter.dispose();
    _detailRouteFuture.dispose();
    super.dispose();
  }
  
  @pragma('vm:entry-point')
  static Route<int> _buildDetailRoute(
    BuildContext context,
    Object? arguments,
  ) {
    return MaterialPageRoute<int>(
      builder: (context) => DetailPage(
        initialValue: arguments as int,
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Counter: ${_counter.value}'),
            ElevatedButton(
              onPressed: () => _detailRouteFuture.present(_counter.value),
              child: Text('Go to Detail'),
            ),
          ],
        ),
      ),
    );
  }
}

总结

Navigator 是 Flutter 导航系统的核心,掌握以下要点:

  1. 命令式 API :适合简单场景,使用 pushpop 等方法
  2. 声明式 API :适合复杂应用,使用 pages 属性管理路由栈
  3. 状态恢复 :使用 restorablePush 系列方法和 RestorableRouteFuture
  4. 路由观察 :使用 NavigatorObserver 监听路由变化
  5. 嵌套导航:合理使用多个 Navigator 构建复杂的导航结构

在 Mind App 项目中,建议:

  • 使用命名路由管理主要页面
  • 结合 GetX 路由管理简化代码
  • 为重要流程启用状态恢复
  • 使用 NavigatorObserver 进行路由埋点统计
相关推荐
阿东在coding3 小时前
Flutter Navigator 知识架构思维导图
aigc
春末的南方城市7 小时前
清华&字节开源HuMo: 打造多模态可控的人物视频,输入文字、图片、音频,生成电影级的视频,Demo、代码、模型、数据全开源。
人工智能·深度学习·机器学习·计算机视觉·aigc
PetterHillWater8 小时前
Claude Code V2集成KAT-Coder
aigc
Mintopia8 小时前
AIGC 训练数据的隐私保护技术:联邦学习在 Web 场景的落地
前端·javascript·aigc
这里有鱼汤9 小时前
从DeepSeek到Kronos,3个原因告诉你:Kronos如何颠覆传统量化预测
后端·python·aigc
EdisonZhou10 小时前
多Agent协作入门:基于A2A协议的Agent通信(中)
aigc·agent·.net core
嘟嘟MD18 小时前
程序员副业 | 2025年9月复盘
后端·aigc
墨风如雪21 小时前
就它了!Claude Sonnet 4.5:AI编程与智能体的新王牌
aigc
墨风如雪21 小时前
AI视频革命奇点:Sora 2的数字幻境
aigc