flutter-implementing-navigation-and-routing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseImplementing 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 in Android or a
Activityin iOS.ViewController - Navigator vs. Router:
- Use (Imperative) for small applications without complex deep linking requirements. It manages a stack of
Navigatorobjects.Route - Use (Declarative) for applications with advanced navigation, web URL synchronization, and specific deep linking requirements.
Router
- Use
- 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 (and
MaterialApp.routes) for most applications. They have rigid deep linking behavior and do not support the browser forward button. Use a routing package likeNavigator.pushNamedinstead.go_router
- 路由(Routes):在Flutter中,屏幕和页面被称为routes。一个路由本质上是一个Widget,相当于Android中的或iOS中的
Activity。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 widget to push and pop routes using platform-specific transition animations ( or ).
NavigatorMaterialPageRouteCupertinoPageRoute使用 widget,通过平台特定的过渡动画(或)来推送和弹出路由。
NavigatorMaterialPageRouteCupertinoPageRoutePushing and Popping
推送与弹出
- Navigate to a new route using .
Navigator.push(context, route) - Return to the previous route using .
Navigator.pop(context) - Use to replace the current route, or
Navigator.pushReplacement()to clear the stack based on a condition.Navigator.pushAndRemoveUntil()
- 使用跳转到新路由。
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 parameter of the
settings: RouteSettings(arguments: data)and extract it usingPageRoute.ModalRoute.of(context)!.settings.arguments - Returning Data: Pass the return value to the method:
pop. Await the result on the pushing side:Navigator.pop(context, resultData).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 API via a declarative routing package like .
Routergo_router- Switch from to
MaterialApp.MaterialApp.router - Define a router configuration that parses route paths and configures the automatically.
Navigator - 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支持或复杂路由的应用,通过这类声明式路由包实现 API。
go_routerRouter- 将替换为
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 widget inside the host widget.
Navigator - Assign a to the nested
GlobalKey<NavigatorState>to control it programmatically.Navigator - Implement the callback within the nested
onGenerateRouteto resolve sub-routes.Navigator - Intercept hardware back button presses using to prevent the top-level navigator from popping the entire nested flow prematurely.
PopScope
实现嵌套导航可以独立于顶层全局导航器管理子流程页面(例如多步骤设置流程或持久化底部导航标签页)。
- 在宿主Widget内部实例化一个新的widget。
Navigator - 为嵌套的分配
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 in the source widget.
Navigator.push() - Wrap the destination widget in a or
MaterialPageRoute.CupertinoPageRoute - Implement in the destination widget to return.
Navigator.pop()
复制以下清单,追踪实现基本页面过渡的进度:
- 创建目标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 and
MaterialApp.Navigator.push()
- Use standard
- If the app requires deep linking, web support, or complex flows:
- Add the package.
go_router - Change to
MaterialApp.MaterialApp.router - Define the configuration with all top-level routes.
GoRouter - Replace with
Navigator.push()orcontext.go().context.push()
- Add the
设置应用全局路由时,使用以下条件化工作流程:
- 如果应用简单且无需深度链接:
- 使用标准的和
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 in the host widget's state.
GlobalKey<NavigatorState> - Return a widget in the host's
Navigatormethod, passing the key.build - Implement in the nested
onGenerateRouteto map string paths to specific step widgets.Navigator - Wrap the host in a
Scaffoldto handle back-button interceptions (e.g., prompting "Are you sure you want to exit setup?").PopScope - Use to advance steps within the flow.
navigatorKey.currentState!.pushNamed()
构建多步骤子流程(例如IoT设备设置)时,运行以下工作流程:
- 为嵌套路由路径定义字符串常量。
- 在宿主Widget的状态中创建。
GlobalKey<NavigatorState> - 在宿主的方法中返回
buildwidget,并传入该Key。Navigator - 在嵌套的中实现
Navigator,将字符串路径映射到特定步骤的Widget。onGenerateRoute - 将宿主包裹在
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,
);
}
}