Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[完成翻译] src/content/cookbook/effects/nested-nav.md #1486

Merged
merged 8 commits into from
Aug 26, 2024
133 changes: 133 additions & 0 deletions src/content/cookbook/effects/nested-nav.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ top-level `Navigator` widget. The list would be very long,
and many of these routes would
be better handled nested within another widget.

应用程序随着时间的推移会累积几十甚至上百个 routes。
其中有些 routes 作为 top-level (global) routes 是合理的。
例如,"/"、"profile"、"contact"、"social_feed"
这些都可能是应用中的 top-level routes 。
但是,想象一下,如果你在 top-level `Navigator` widget 中定义了所有可能的 routes ,
那么这个列表会非常长,其中很多 routes 更适合嵌套在另一个 widget 中处理。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Consider an Internet of Things (IoT) setup flow for a wireless
light bulb that you control with your app.
This setup flow consists of 4 pages:
Expand All @@ -32,21 +39,43 @@ in the setup flow. This delegation of navigation facilitates
greater local control, which is
generally preferable when developing software.

想象一个用于无线灯泡的物联网 (IoT) 设置流程,
你可以通过应用程序来控制这个灯泡。
这个设置流程包括4个页面:
寻找附近的灯泡、选择你要添加的灯泡、添加灯泡、然后完成设置。
你可以在 top-level `Navigator` widget 中协调这些操作。
然而,更合理的做法是,
在你的 `SetupFlow` widget 中定义一个嵌套的 `Navigator` widget,
并让这个嵌套的 `Navigator` 负责管理设置流程中的这4个页面。
这种导航方式有助于实现更大程度上的本地控制,
这在软件开发中通常是更可取的。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The following animation shows the app's behavior:

下面的动画展示了应用程序的行为:

![Gif showing the nested "setup" flow](/assets/images/docs/cookbook/effects/NestedNavigator.gif){:.site-mobile-screenshot}

In this recipe, you implement a four-page IoT setup
flow that maintains its own navigation nested beneath
the top-level `Navigator` widget.

在这个教程中,你将实现一个包含四个页面的物联网 (IoT) 设置流程,
该流程的 Navigation 将嵌套在 top-level `Navigator` widget 之下,并自行管理。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

## Prepare for navigation

## 准备 Navigation
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

This IoT app has two top-level screens,
along with the setup flow. Define these
route names as constants so that they can
be referenced within code.

这个物联网 (IoT) 应用程序有两个 top-level 屏幕,
以及一个设置流程。
将这些 routes 名称定义为常量,以便在代码中引用它们。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/main.dart (routes)"?>
```dart
const routeHome = '/';
Expand All @@ -68,6 +97,13 @@ that a route name is intended for the setup flow without
recognizing all the individual pages associated with
the setup flow.

主屏幕和设置屏幕的 routes 是使用静态名称引用的。
然而,设置流程中的页面是通过两个路径组合来生成它们的 routes 名称的:
首先是一个 `/setup/` 前缀,然后是具体页面的名称。
通过将这两个路径组合在一起,
你的 `Navigator` 可以判断出 routes 名称是为设置流程设计的,
而无需识别与设置流程相关的所有单个页面。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The top-level `Navigator` isn't responsible for identifying
individual setup flow pages. Therefore, your top-level
`Navigator` needs to parse the incoming route name to
Expand All @@ -76,9 +112,19 @@ means that you can't use the `routes` property of your top-level
`Navigator`. Instead, you must provide a function for the
`onGenerateRoute` property.

Top-level `Navigator` 不负责识别具体的设置流程页面。
因此, top-level `Navigator` 需要解析传入的 routes 名称,
以识别设置流程的前缀。
由于需要解析 routes 名称,
不能使用 top-level `Navigator` 的 `routes` 属性。
相反,你必须为 `onGenerateRoute` 属性提供一个函数。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Implement `onGenerateRoute` to return the appropriate widget
for each of the three top-level paths.

实现 `onGenerateRoute` 函数,
以便为三个 top-level paths 中的每一个返回相应的 widget。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/main.dart (OnGenerateRoute)"?>
```dart
onGenerateRoute: (settings) {
Expand Down Expand Up @@ -115,9 +161,18 @@ This splitting of the route name is what allows the top-level
`Navigator` to be agnostic toward the various subroutes
within the setup flow.

请注意,home 和 settings routes 是与精确的 routes name 匹配的。
然而,设置流程的 routes 条件只检查前缀。
如果 routes name 包含设置流程的前缀,那么 routes name 的其余部分将被忽略,
并传递给 `SetupFlow` widget 进行处理。
Routes name 的这种拆分使 top-level `Navigator` 可以不关注设置流程中的各个 subroutes 。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Create a stateful widget called `SetupFlow` that
accepts a route name.

创建一个名为 `SetupFlow` 的 statefull widget,
该 widget 接受一个 routes name 作为参数。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/setupflow.dart (SetupFlow)" replace="/@override\n*.*\n\s*return const SizedBox\(\);\n\s*}/\/\/.../g"?>
```dart
class SetupFlow extends StatefulWidget {
Expand All @@ -139,13 +194,20 @@ class SetupFlowState extends State<SetupFlow> {

## Display an app bar for the setup flow

## 为设置流程显示一个应用栏
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The setup flow displays a persistent app bar
that appears across all pages.

设置流程显示一个持久的应用栏,该应用栏会在所有页面上保持显示。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Return a `Scaffold` widget from your `SetupFlow`
widget's `build()` method,
and include the desired `AppBar` widget.

在你的 `SetupFlow` widget 的 `build()` 方法中返回一个 `Scaffold` widget,
并包含所需的 `AppBar` widget。

<?code-excerpt "lib/setupflow2.dart (SetupFlow2)"?>
```dart
@override
Expand All @@ -169,10 +231,18 @@ exiting the flow causes the user to lose all progress.
Therefore, the user is prompted to confirm whether they
want to exit the setup flow.

应用栏显示一个返回箭头,
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved
当返回箭头被按下时,会退出设置流程。
然而,退出流程会导致用户丢失所有进度。
因此,系统会提示用户确认是否真的想要退出设置流程。

Prompt the user to confirm exiting the setup flow,
and ensure that the prompt appears when the user
presses the hardware back button on Android.

提示用户确认是否退出设置流程,
并确保在用户按下 Android 硬件返回按钮时也会出现该提示。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/prompt_user.dart (PromptUser)"?>
```dart
Future<void> _onExitPressed() async {
Expand Down Expand Up @@ -251,20 +321,37 @@ If the user presses **Leave**, then the setup flow pops itself
from the top-level navigation stack.
If the user presses **Stay**, then the action is ignored.

当用户点击应用栏中的返回箭头或按下 Android 硬件返回按钮时,
会弹出一个警告对话框,确认用户是否要离开设置流程。
如果用户点击 **Leave**,则设置流程会从 top-level navigation stack 中弹出。
如果用户点击 **Stay**,则忽略该操作。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

You might notice that the `Navigator.pop()`
is invoked by both the **Leave** and
**Stay** buttons. To be clear,
this `pop()` action pops the alert dialog off
the navigation stack, not the setup flow.

你可能会注意到,
**Leave** 和 **Stay** 按钮都会调用 `Navigator.pop()`。
需要明确的是,这个 `pop()` 操作是将警告对话框从 navigation stack 中弹出,
而不是设置流程。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

## Generate nested routes

## 生成 nested routes
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The setup flow's job is to display the appropriate
page within the flow.

设置流程的任务是显示流程中的适当页面。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Add a `Navigator` widget to `SetupFlow`,
and implement the `onGenerateRoute` property.

在 `SetupFlow` 中添加一个 `Navigator` widget ,
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved
并实现 `onGenerateRoute` 属性。

<?code-excerpt "lib/add_navigator.dart (AddNavigator)"?>
```dart
final _navigatorKey = GlobalKey<NavigatorState>();
Expand Down Expand Up @@ -338,6 +425,10 @@ which includes the route's `name`.
Based on that route name,
one of four flow pages is returned.

`_onGenerateRoute` 函数的工作方式与 top-level `Navigator` 相同。
一个 `RouteSettings` 对象会传递给函数,其中包含 route's `name` 。
根据该 routes name ,将返回四个流程页面之一。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The first page, called `find_devices`,
waits a few seconds to simulate network scanning.
After the wait period, the page invokes its callback.
Expand All @@ -348,6 +439,13 @@ Therefore, in `_onDiscoveryComplete`, the `_navigatorKey`
instructs the nested `Navigator` to navigate to the
`select_device` page.

第一页称为 `find_devices`,等待几秒钟以模拟网络扫描。
在等待时间结束后,页面会调用其回调函数。
在这种情况下,回调函数是 `_onDiscoveryComplete`。
设置流程识别到设备发现完成后,应该显示设备选择页面。
因此,在 `_onDiscoveryComplete` 中,
`_navigatorKey` 指示嵌套的 `Navigator` 导航到 `select_device` 页面。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

The `select_device` page asks the user to select a
device from a list of available devices. In this recipe,
only one device is presented to the user.
Expand All @@ -358,6 +456,13 @@ should be shown. Therefore, in `_onDeviceSelected`,
the `_navigatorKey` instructs the nested `Navigator`
to navigate to the `"connecting"` page.

`select_device` 页面要求用户从可用设备列表中选择一个设备。
在这个示例中,只向用户展示了一个设备。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved
当用户点击设备时,`onDeviceSelected` 回调被调用。
设置流程识别到设备选择后,应该显示连接页面。
因此,在 `_onDeviceSelected` 中,
`_navigatorKey` 指示嵌套的 `Navigator` 导航到 `"connecting"` 页面。

The `connecting` page works the same way as the
`find_devices` page. The `connecting` page waits
for a few seconds and then invokes its callback.
Expand All @@ -368,27 +473,55 @@ in `_onConnectionEstablished`, the `_navigatorKey`
instructs the nested `Navigator` to navigate to the
`finished` page.

`connecting` 页面与 `find_devices` 页面工作方式相同。
`connecting` 页面等待几秒钟,然后调用其回调函数。
在这种情况下,回调函数是 `_onConnectionEstablished`。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved
设置流程识别到连接建立后,应该显示最终页面。
因此,在 `_onConnectionEstablished` 中,
`_navigatorKey` 指示嵌套的 `Navigator` 导航到 `finished` 页面。

The `finished` page provides the user with a **Finish**
button. When the user taps **Finish**,
the `_exitSetup` callback is invoked, which pops the entire
setup flow off the top-level `Navigator` stack,
taking the user back to the home screen.

`finished` 页面提供了一个 **Finish** 按钮。
当用户点击 **Finish** 时,`_exitSetup` 回调被调用,
这将从 top-level `Navigator` stack 中弹出整个设置流程,将用户带回到首页。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

Congratulations!
You implemented nested navigation with four subroutes.

恭喜你!你实现了具有四个 subroutes 的 nested navigation 。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

## Interactive example

## 交互示例

Run the app:

运行应用程序:

* On the **Add your first bulb** screen,
click the FAB, shown with a plus sign, **+**.
This brings you to the **Select a nearby device**
screen. A single bulb is listed.

在 **添加你的第一个灯泡** 屏幕上,
点击带有加号 **+** 的 FAB。
这会将你带到 **选择附近设备** 屏幕。
屏幕上画了一个灯泡。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

* Click the listed bulb. A **Finished!** screen appears.

按下画着的灯泡。屏幕上出现 **结束!** 。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

* Click the **Finished** button to return to the
first screen.

按下 **结束** 按钮返回第一页。
AmosHuKe marked this conversation as resolved.
Show resolved Hide resolved

<?code-excerpt "lib/main.dart"?>
```dartpad title="Flutter nested navigation hands-on example in DartPad" run="true"
import 'package:flutter/material.dart';
Expand Down