flutter-implementing-navigation-and-routing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implementing Navigation and Routing in Flutter

在Flutter中实现导航与路由

Contents

目录

Core Concepts

核心概念

  • Routes: In Flutter, screens and pages are referred to as routes. A route is simply a widget. This is equivalent to an
    Activity
    in Android or a
    ViewController
    in iOS.
  • Navigator vs. Router:
    • Use
      Navigator
      (Imperative) for small applications without complex deep linking requirements. It manages a stack of
      Route
      objects.
    • Use
      Router
      (Declarative) for applications with advanced navigation, web URL synchronization, and specific deep linking requirements.
  • Deep Linking: Allows an app to open directly to a specific location based on a URL. Supported on iOS, Android, and Web. Web requires no additional setup.
  • Named Routes: Avoid using named routes (
    MaterialApp.routes
    and
    Navigator.pushNamed
    ) for most applications. They have rigid deep linking behavior and do not support the browser forward button. Use a routing package like
    go_router
    instead.
  • 路由(Routes):在Flutter中,屏幕和页面被称为routes。一个路由本质上是一个Widget,相当于Android中的
    Activity
    或iOS中的
    ViewController
  • Navigator vs. Router
    • 对于没有复杂深度链接需求的小型应用,使用
      Navigator
      (命令式)。它管理一个
      Route
      对象栈。
    • 对于需要高级导航、Web URL同步和特定深度链接需求的应用,使用
      Router
      (声明式)。
  • 深度链接(Deep Linking):允许应用直接通过URL打开到指定页面。支持iOS、Android和Web平台,Web平台无需额外配置。
  • 命名路由(Named Routes):大多数应用应避免使用命名路由(
    MaterialApp.routes
    Navigator.pushNamed
    ),因为它们的深度链接行为较为僵化,且不支持浏览器前进按钮。建议使用
    go_router
    这类路由包替代。

Implementing Imperative Navigation

实现命令式导航

Use the
Navigator
widget to push and pop routes using platform-specific transition animations (
MaterialPageRoute
or
CupertinoPageRoute
).
使用
Navigator
widget,通过平台特定的过渡动画(
MaterialPageRoute
CupertinoPageRoute
)来推送和弹出路由。

Pushing and Popping

推送与弹出

  • Navigate to a new route using
    Navigator.push(context, route)
    .
  • Return to the previous route using
    Navigator.pop(context)
    .
  • Use
    Navigator.pushReplacement()
    to replace the current route, or
    Navigator.pushAndRemoveUntil()
    to clear the stack based on a condition.
  • 使用
    Navigator.push(context, route)
    跳转到新路由。
  • 使用
    Navigator.pop(context)
    返回上一个路由。
  • 使用
    Navigator.pushReplacement()
    替换当前路由,或使用
    Navigator.pushAndRemoveUntil()
    根据条件清空路由栈。

Passing and Returning Data

数据传递与返回

  • Sending Data: Pass data directly into the constructor of the destination widget. Alternatively, pass data via the
    settings: RouteSettings(arguments: data)
    parameter of the
    PageRoute
    and extract it using
    ModalRoute.of(context)!.settings.arguments
    .
  • Returning Data: Pass the return value to the
    pop
    method:
    Navigator.pop(context, resultData)
    . Await the result on the pushing side:
    final result = await Navigator.push(...)
    .
  • 发送数据:直接将数据传入目标Widget的构造函数。或者通过
    PageRoute
    settings: RouteSettings(arguments: data)
    参数传递数据,并使用
    ModalRoute.of(context)!.settings.arguments
    提取数据。
  • 返回数据:将返回值传入
    pop
    方法:
    Navigator.pop(context, resultData)
    。在发起跳转的一侧等待结果:
    final result = await Navigator.push(...)

Implementing Declarative Navigation

实现声明式导航

For apps requiring deep linking, web URL support, or complex routing, implement the
Router
API via a declarative routing package like
go_router
.
  • Switch from
    MaterialApp
    to
    MaterialApp.router
    .
  • Define a router configuration that parses route paths and configures the
    Navigator
    automatically.
  • Navigate using package-specific APIs (e.g.,
    context.go('/path')
    ).
  • Page-backed vs. Pageless Routes: Declarative routes are page-backed (deep-linkable). Imperative pushes (e.g., dialogs, bottom sheets) are pageless. Removing a page-backed route automatically removes all subsequent pageless routes.
对于需要深度链接、Web URL支持或复杂路由的应用,通过
go_router
这类声明式路由包实现
Router
API。
  • MaterialApp
    替换为
    MaterialApp.router
  • 定义路由配置,自动解析路由路径并配置
    Navigator
  • 使用包特定的API进行导航(例如
    context.go('/path')
    )。
  • 基于页面的路由 vs. 无页面路由:声明式路由是基于页面的(可深度链接)。命令式推送的内容(如对话框、底部弹窗)是无页面的。移除基于页面的路由时,会自动移除所有后续的无页面路由。

Implementing Nested Navigation

实现嵌套导航

Implement nested navigation to manage a sub-flow of screens (e.g., a multi-step setup process or persistent bottom navigation tabs) independently from the top-level global navigator.
  • Instantiate a new
    Navigator
    widget inside the host widget.
  • Assign a
    GlobalKey<NavigatorState>
    to the nested
    Navigator
    to control it programmatically.
  • Implement the
    onGenerateRoute
    callback within the nested
    Navigator
    to resolve sub-routes.
  • Intercept hardware back button presses using
    PopScope
    to prevent the top-level navigator from popping the entire nested flow prematurely.
实现嵌套导航可以独立于顶层全局导航器管理子流程页面(例如多步骤设置流程或持久化底部导航标签页)。
  • 在宿主Widget内部实例化一个新的
    Navigator
    widget。
  • 为嵌套的
    Navigator
    分配
    GlobalKey<NavigatorState>
    ,以便通过代码控制它。
  • 在嵌套的
    Navigator
    中实现
    onGenerateRoute
    回调,用于解析子路由。
  • 使用
    PopScope
    拦截硬件返回按钮的按下事件,防止顶层导航器过早弹出整个嵌套流程。

Workflows

工作流程

Workflow: Standard Screen Transition

工作流程:标准页面过渡

Copy this checklist to track progress when implementing a basic screen transition:
  • Create the destination widget (Route).
  • Define required data parameters in the destination widget's constructor.
  • Implement
    Navigator.push()
    in the source widget.
  • Wrap the destination widget in a
    MaterialPageRoute
    or
    CupertinoPageRoute
    .
  • Implement
    Navigator.pop()
    in the destination widget to return.
复制以下清单,追踪实现基本页面过渡的进度:
  • 创建目标Widget(路由)。
  • 在目标Widget的构造函数中定义所需的数据参数。
  • 在源Widget中实现
    Navigator.push()
  • 将目标Widget包裹在
    MaterialPageRoute
    CupertinoPageRoute
    中。
  • 在目标Widget中实现
    Navigator.pop()
    以返回。

Workflow: Implementing Deep-Linkable Routing

工作流程:实现可深度链接的路由

Use this conditional workflow when setting up app-wide routing:
  • If the app is simple and requires no deep linking:
    • Use standard
      MaterialApp
      and
      Navigator.push()
      .
  • If the app requires deep linking, web support, or complex flows:
    • Add the
      go_router
      package.
    • Change
      MaterialApp
      to
      MaterialApp.router
      .
    • Define the
      GoRouter
      configuration with all top-level routes.
    • Replace
      Navigator.push()
      with
      context.go()
      or
      context.push()
      .
设置应用全局路由时,使用以下条件化工作流程:
  • 如果应用简单且无需深度链接:
    • 使用标准的
      MaterialApp
      Navigator.push()
  • 如果应用需要深度链接、Web支持或复杂流程:
    • 添加
      go_router
      包。
    • MaterialApp
      改为
      MaterialApp.router
    • 定义包含所有顶层路由的
      GoRouter
      配置。
    • context.go()
      context.push()
      替代
      Navigator.push()

Workflow: Creating a Nested Navigation Flow

工作流程:创建嵌套导航流程

Run this workflow when building a multi-step sub-flow (e.g., IoT device setup):
  • Define string constants for the nested route paths.
  • Create a
    GlobalKey<NavigatorState>
    in the host widget's state.
  • Return a
    Navigator
    widget in the host's
    build
    method, passing the key.
  • Implement
    onGenerateRoute
    in the nested
    Navigator
    to map string paths to specific step widgets.
  • Wrap the host
    Scaffold
    in a
    PopScope
    to handle back-button interceptions (e.g., prompting "Are you sure you want to exit setup?").
  • Use
    navigatorKey.currentState!.pushNamed()
    to advance steps within the flow.
构建多步骤子流程(例如IoT设备设置)时,运行以下工作流程:
  • 为嵌套路由路径定义字符串常量。
  • 在宿主Widget的状态中创建
    GlobalKey<NavigatorState>
  • 在宿主的
    build
    方法中返回
    Navigator
    widget,并传入该Key。
  • 在嵌套的
    Navigator
    中实现
    onGenerateRoute
    ,将字符串路径映射到特定步骤的Widget。
  • 将宿主
    Scaffold
    包裹在
    PopScope
    中,处理返回按钮拦截(例如提示“确定要退出设置吗?”)。
  • 使用
    navigatorKey.currentState!.pushNamed()
    在流程中推进步骤。

Examples

示例

Example: Passing Data via Constructor (Imperative)

示例:通过构造函数传递数据(命令式)

dart
// 1. Define the data model
class Todo {
  final String title;
  final String description;
  const Todo(this.title, this.description);
}

// 2. Source Screen
class TodosScreen extends StatelessWidget {
  final List<Todo> todos;
  const TodosScreen({super.key, required this.todos});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            onTap: () {
              // Push and pass data via constructor
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DetailScreen(todo: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

// 3. Destination Screen
class DetailScreen extends StatelessWidget {
  final Todo todo;
  const DetailScreen({super.key, required this.todo});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
dart
// 1. Define the data model
class Todo {
  final String title;
  final String description;
  const Todo(this.title, this.description);
}

// 2. Source Screen
class TodosScreen extends StatelessWidget {
  final List<Todo> todos;
  const TodosScreen({super.key, required this.todos});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            onTap: () {
              // Push and pass data via constructor
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DetailScreen(todo: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

// 3. Destination Screen
class DetailScreen extends StatelessWidget {
  final Todo todo;
  const DetailScreen({super.key, required this.todo});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}

Example: Nested Navigation Flow

示例:嵌套导航流程

dart
class SetupFlow extends StatefulWidget {
  final String initialRoute;
  const SetupFlow({super.key, required this.initialRoute});

  
  State<SetupFlow> createState() => _SetupFlowState();
}

class _SetupFlowState extends State<SetupFlow> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  void _exitSetup() => Navigator.of(context).pop();

  
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, _) async {
        if (didPop) return;
        // Intercept back button to prevent accidental exit
        _exitSetup(); 
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Setup')),
        body: Navigator(
          key: _navigatorKey,
          initialRoute: widget.initialRoute,
          onGenerateRoute: _onGenerateRoute,
        ),
      ),
    );
  }

  Route<Widget> _onGenerateRoute(RouteSettings settings) {
    Widget page;
    switch (settings.name) {
      case 'step1':
        page = StepOnePage(
          onComplete: () => _navigatorKey.currentState!.pushNamed('step2'),
        );
        break;
      case 'step2':
        page = StepTwoPage(onComplete: _exitSetup);
        break;
      default:
        throw StateError('Unexpected route name: ${settings.name}!');
    }

    return MaterialPageRoute(
      builder: (context) => page,
      settings: settings,
    );
  }
}
dart
class SetupFlow extends StatefulWidget {
  final String initialRoute;
  const SetupFlow({super.key, required this.initialRoute});

  
  State<SetupFlow> createState() => _SetupFlowState();
}

class _SetupFlowState extends State<SetupFlow> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  void _exitSetup() => Navigator.of(context).pop();

  
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, _) async {
        if (didPop) return;
        // Intercept back button to prevent accidental exit
        _exitSetup(); 
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Setup')),
        body: Navigator(
          key: _navigatorKey,
          initialRoute: widget.initialRoute,
          onGenerateRoute: _onGenerateRoute,
        ),
      ),
    );
  }

  Route<Widget> _onGenerateRoute(RouteSettings settings) {
    Widget page;
    switch (settings.name) {
      case 'step1':
        page = StepOnePage(
          onComplete: () => _navigatorKey.currentState!.pushNamed('step2'),
        );
        break;
      case 'step2':
        page = StepTwoPage(onComplete: _exitSetup);
        break;
      default:
        throw StateError('Unexpected route name: ${settings.name}!');
    }

    return MaterialPageRoute(
      builder: (context) => page,
      settings: settings,
    );
  }
}