Flutter Navigator 深度学习教程

目录

  1. Navigator 基础概念
  2. 核心类和接口
  3. 路由管理 API
  4. 声明式导航 (Pages API)
  5. 状态恢复
  6. 高级特性
  7. 最佳实践
  8. 实战示例

1. Navigator 基础概念

1.1 什么是 Navigator?

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

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

1.2 核心概念

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

2. 核心类和接口

2.1 Route 类

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;    // 是否在栈中
}

2.2 NavigatorState 类

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

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);
}

2.3 NavigatorObserver(导航观察者)

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
// 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(替换当前路由)
// 替换当前路由,常用于登录后跳转
Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomePage(),
  ),
);

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

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

3.2 Pop 操作

基础 Pop
// 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(返回到某个条件)
// 返回到首页
Navigator.popUntil(context, ModalRoute.withName('/'));

// 返回到满足条件的路由
Navigator.popUntil(context, (route) {
  return route.settings.name == '/target' || route.isFirst;
});

3.3 获取 Navigator

// 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 的声明式编程范式。

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 类

// 自定义 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(过渡委托)

自定义路由过渡行为:

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 启用状态恢复

// 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

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

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. 高级特性

6.1 嵌套 Navigator

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 动画控制:

// 提供 HeroController
HeroControllerScope(
  controller: HeroController(),
  child: Navigator(
    // ...
  ),
)

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

6.3 RoutePopDisposition

控制路由弹出行为:

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 用户手势追踪

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

7. 最佳实践

7.1 路由命名规范

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 参数传递

// 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 错误处理

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 等待返回结果

// 推入页面并等待结果
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 条件导航

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

8. 实战示例

8.1 完整的应用导航结构

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 带动画的自定义路由

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 底部弹窗导航

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 对话框作为路由

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 带状态恢复的完整示例

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 进行路由埋点统计
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]