diff --git a/sites/docs/lib/_sass/_site.scss b/sites/docs/lib/_sass/_site.scss index c400cf86d5..05c6683407 100644 --- a/sites/docs/lib/_sass/_site.scss +++ b/sites/docs/lib/_sass/_site.scss @@ -54,3 +54,6 @@ // Must be imported last to ensure that // the print overrides take priority over earlier defined styles. @use 'base/print-overrides'; + +// flutter.cn +@use 'flutter-cn/alert-ai-translation'; \ No newline at end of file diff --git a/sites/docs/lib/_sass/components/_alert.scss b/sites/docs/lib/_sass/components/_alert.scss index d0da054468..197676b49a 100644 --- a/sites/docs/lib/_sass/components/_alert.scss +++ b/sites/docs/lib/_sass/components/_alert.scss @@ -65,4 +65,4 @@ aside.alert { border-color: var(--site-alert-error-color); --alert-title-color: var(--site-alert-error-color); } -} +} \ No newline at end of file diff --git a/sites/docs/lib/_sass/flutter-cn/_alert-ai-translation.scss b/sites/docs/lib/_sass/flutter-cn/_alert-ai-translation.scss new file mode 100644 index 0000000000..758d945d21 --- /dev/null +++ b/sites/docs/lib/_sass/flutter-cn/_alert-ai-translation.scss @@ -0,0 +1,59 @@ +aside.alert.alert-ai-translation { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.85rem 1rem; + border-left: solid 0.25rem transparent; + border-image: linear-gradient(160deg, + var(--site-alert-info-color), + var(--site-alert-tip-color)) 1; + background: + linear-gradient(120deg, + oklch(from var(--site-alert-info-color) l c h / 0.08), + oklch(from var(--site-alert-tip-color) l c h / 0.05)); + + .ai-translation-glyph { + display: inline-flex; + align-items: center; + justify-content: center; + flex: none; + inline-size: 1.6rem; + block-size: 1.6rem; + border-radius: 50%; + background: linear-gradient(150deg, + var(--site-alert-info-color), + var(--site-alert-tip-color)); + + .material-symbols { + font-size: 0.9rem; + line-height: 1; + color: #fff; + user-select: none; + } + } + + .ai-translation-body { + min-inline-size: 0; + } + + .ai-translation-title { + margin: 0 0 0.15rem; + font-family: var(--site-ui-fontFamily); + font-size: 0.9rem; + font-weight: 500; + -webkit-font-smoothing: antialiased; + color: var(--site-base-fgColor); + } + + .ai-translation-desc { + margin: 0; + font-size: 0.9rem; + line-height: 1.6; + color: var(--site-base-fgColor); + + a { + font-weight: 500; + white-space: nowrap; + } + } +} \ No newline at end of file diff --git a/sites/docs/lib/src/components/common/flutter_cn/ai_translation_notice.dart b/sites/docs/lib/src/components/common/flutter_cn/ai_translation_notice.dart new file mode 100644 index 0000000000..70df818b01 --- /dev/null +++ b/sites/docs/lib/src/components/common/flutter_cn/ai_translation_notice.dart @@ -0,0 +1,76 @@ +import 'package:jaspr/dom.dart'; +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_content/jaspr_content.dart'; + +import '../../../utils/page_source_info.dart'; + +/// A notice displayed at the top of pages whose Chinese translation +/// was produced (in whole or in part) by AI. +/// +/// It invites readers to review the translation and +/// contribute improvements on GitHub. +final class AiTranslationNotice extends StatelessComponent { + const AiTranslationNotice({super.key}); + + @override + Component build(BuildContext context) { + final page = context.page; + final sourceInfo = page.sourceInfo; + final issueUrl = sourceInfo.issueUrl; + final pageSource = sourceInfo.sourceUrl; + + return aside( + classes: 'alert alert-ai-translation', + attributes: {'role': 'note', 'data-nosnippet': 'true'}, + [ + const span( + classes: 'ai-translation-glyph', + attributes: {'aria-hidden': 'true', 'translate': 'no'}, + [ + span(classes: 'material-symbols', [.text('auto_awesome')]), + ], + ), + div(classes: 'ai-translation-body', [ + const p(classes: 'ai-translation-title', [ + .text('本页内容由 AI 翻译'), + ]), + p(classes: 'ai-translation-desc', [ + const .text('本页译文在人工校订的基础上借助 AI 完成。'), + const .text('如果你发现任何不准确或可以改进的地方,'), + if (pageSource case final pageSource?) + a( + href: pageSource, + target: Target.blank, + attributes: { + 'rel': 'noopener', + 'title': '在 GitHub 上查看并校阅本页译文', + }, + [const .text('欢迎到 GitHub 参与校阅')], + ) + else + const a( + href: 'https://github.com/cfug/flutter.cn', + target: Target.blank, + attributes: { + 'rel': 'noopener', + 'title': '在 GitHub 上参与文档校阅', + }, + [.text('欢迎到 GitHub 参与校阅')], + ), + const .text(' 或者 '), + a( + href: issueUrl, + attributes: { + 'title': '为本页面内容提出建议', + 'target': '_blank', + 'rel': 'noopener', + }, + [.text(pageSource == null ? '为本页面内容提出建议' : '为本页面内容提出建议')], + ), + const .text(',与大家一起把它完善得更好。'), + ]), + ]), + ], + ); + } +} diff --git a/sites/docs/lib/src/layouts/doc_layout.dart b/sites/docs/lib/src/layouts/doc_layout.dart index 3597483936..c0b25669bd 100644 --- a/sites/docs/lib/src/layouts/doc_layout.dart +++ b/sites/docs/lib/src/layouts/doc_layout.dart @@ -6,6 +6,7 @@ import 'package:jaspr/dom.dart'; import 'package:jaspr/jaspr.dart'; import 'package:jaspr_content/jaspr_content.dart'; +import '../components/common/flutter_cn/ai_translation_notice.dart'; import '../components/common/page_header.dart'; import '../components/common/prev_next.dart'; import '../components/layout/banner.dart'; @@ -52,6 +53,10 @@ class DocLayout extends FlutterDocsLayout { final pageTitle = pageData['title'] as String; final pageDescription = (pageData['description'] as String?)?.trim(); + final aiTranslated = + (pageData['ai-translated'] as bool?) ?? + (pageData['aiTranslated'] as bool?) ?? + false; final showBanner = (pageData['showBanner'] as bool?) ?? (siteData['showBanner'] as bool?) ?? @@ -96,6 +101,9 @@ class DocLayout extends FlutterDocsLayout { (pageData['showBreadcrumbs'] as bool? ?? true), ), + /// flutter.cn + if (aiTranslated) const AiTranslationNotice(), + child, PrevNext( diff --git a/sites/docs/src/_includes/docs/add-to-app/android-initial-route-cached-engine.md b/sites/docs/src/_includes/docs/add-to-app/android-initial-route-cached-engine.md index 18ddb766a4..c9b3eabdcf 100644 --- a/sites/docs/src/_includes/docs/add-to-app/android-initial-route-cached-engine.md +++ b/sites/docs/src/_includes/docs/add-to-app/android-initial-route-cached-engine.md @@ -1,17 +1,28 @@ The concept of an initial route is available when configuring a `FlutterActivity` or a `FlutterFragment` with a new `FlutterEngine`. + +在配置带有新 `FlutterEngine` 的 `FlutterActivity` 或 `FlutterFragment` 时, +可以使用 initial route(初始路由)这一概念。 + However, `FlutterActivity` and `FlutterFragment` don't offer the concept of an initial route when using a cached engine. + +但是,在使用缓存 engine 时,`FlutterActivity` 和 `FlutterFragment` 不提供 initial route 这一概念。 + This is because a cached engine is expected to already be running Dart code, which means it's too late to configure the initial route. +这是因为缓存 engine 预期已经在运行 Dart 代码,这意味着配置 initial route 为时已晚。 + Developers that would like their cached engine to begin with a custom initial route can configure their cached `FlutterEngine` to use a custom initial route just before executing the Dart entrypoint. The following example demonstrates the use of an initial route with a cached engine: +希望缓存 engine 以自定义 initial route 开始的开发者,可以在执行 Dart entrypoint 之前,为缓存的 `FlutterEngine` 配置自定义 initial route。以下示例演示了如何在缓存 engine 中使用 initial route: + @@ -67,9 +78,16 @@ By setting the initial route of the navigation channel, the associated `FlutterEngine` displays the desired route upon initial execution of the `runApp()` Dart function. +通过设置 navigation channel 的 initial route, +关联的 `FlutterEngine` 会在首次执行 `runApp()` Dart 函数时显示所需路由。 + Changing the initial route property of the navigation channel after the initial execution of `runApp()` has no effect. Developers who would like to use the same `FlutterEngine` between different `Activity`s and `Fragment`s and switch the route between those displays need to set up a method channel and explicitly instruct their Dart code to change `Navigator` routes. + +在首次执行 `runApp()` 之后更改 navigation channel 的 initial route 属性不会生效。 +如果开发者希望在不同的 `Activity` 和 `Fragment` 之间复用同一个 `FlutterEngine`, +并在这些界面之间切换路由,则需要设置 method channel,并显式指示 Dart 代码更改 `Navigator` 路由。 diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-cocoapods.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-cocoapods.md index 446d9fd9ef..0d886dd152 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-cocoapods.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-cocoapods.md @@ -1,40 +1,66 @@ ### Use CocoaPods and the Flutter SDK {:#method-a .no_toc} +### 使用 CocoaPods 与 Flutter SDK {:#method-a .no_toc} + #### Approach {:#method-a-approach} +#### 方法 {:#method-a-approach} + This first method uses CocoaPods to embed the Flutter modules. CocoaPods manages dependencies for Swift projects, including Flutter code and plugins. Each time Xcode builds the app, CocoaPods embeds the Flutter modules. +第一种方法使用 CocoaPods 嵌入 Flutter 模块。 +CocoaPods 管理 Swift 项目的依赖,包括 Flutter 代码与 plugin。 +每次 Xcode 构建 app 时,CocoaPods 都会嵌入 Flutter 模块。 + This allows rapid iteration with the most up-to-date version of your Flutter module without running additional commands outside of Xcode. +这样你可以在 Xcode 之外无需运行额外命令,即可用 Flutter 模块的最新版本快速迭代。 + To learn more about CocoaPods, consult the [CocoaPods getting started guide][]. +要了解更多 CocoaPods 信息,请参阅 [CocoaPods 入门指南][CocoaPods getting started guide]。 + #### Watch the video +#### 观看视频 + If watching a video helps you learn, this video covers adding Flutter to an iOS app: +若观看视频有助于学习,本视频介绍如何将 Flutter 添加到 iOS app: + #### Requirements {:#method-a-reqs} +#### 要求 {:#method-a-reqs} + Every developer working on your project must have a local version of the Flutter SDK and CocoaPods installed. +参与项目的每位开发者都必须在本地安装 Flutter SDK 和 CocoaPods。 + #### Example project structure {:#method-a-structure} +#### 示例项目结构 {:#method-a-structure} + This section assumes that your existing app and the Flutter module reside in sibling directories. If you have a different directory structure, adjust the relative paths. The example directory structure resembles the following: +本节假定现有 app 与 Flutter 模块位于同级目录。 +若目录结构不同,请调整相对路径。 +示例目录结构如下: + - my_flutter/ @@ -48,22 +74,35 @@ The example directory structure resembles the following: #### Update your Podfile +#### 更新你的 Podfile + Add your Flutter modules to your Podfile configuration file. This section presumes you called your Swift app `MyApp`. +将 Flutter 模块添加到你的 Podfile 配置文件。 +本节假定你的 Swift app 名为 `MyApp`。 + 1. _(Optional)_ If your existing app lacks a `Podfile` config file, navigate to the root of your app directory. Use the `pod init` command to create the `Podfile` file. + **(可选)** 若现有 app 没有 `Podfile` 配置文件,请进入 app 目录根目录,使用 `pod init` 命令创建 `Podfile` 文件。 + :::tip If the `pod init` command errors, check that you're on the latest version of CocoaPods. + + 若 `pod init` 命令报错,请确认你使用的是最新版 CocoaPods。 ::: 1. Update your `Podfile` config file. + 更新你的 `Podfile` 配置文件。 + 1. Add the following lines after the `platform` declaration. + 在 `platform` 声明之后添加以下行。 + ```ruby title="MyApp/Podfile" flutter_application_path = '../my_flutter' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') @@ -74,6 +113,8 @@ This section presumes you called your Swift app `MyApp`. `install_all_flutter_pods(flutter_application_path)` method. Add these calls after the settings in the previous step. + 对于每个需要嵌入 Flutter 的 [Podfile target][Podfile target],添加对 `install_all_flutter_pods(flutter_application_path)` 方法的调用。在上一步设置之后添加这些调用。 + ```ruby title="MyApp/Podfile" target 'MyApp' do install_all_flutter_pods(flutter_application_path) @@ -84,6 +125,8 @@ This section presumes you called your Swift app `MyApp`. add a call to `flutter_post_install(installer)`. This block should be the last block in the `Podfile` config file. + 在 `Podfile` 的 `post_install` 块中,添加对 `flutter_post_install(installer)` 的调用。该块应是 `Podfile` 配置文件中的最后一个块。 + ```ruby title="MyApp/Podfile" post_install do |installer| flutter_post_install(installer) if defined?(flutter_post_install) @@ -92,36 +135,64 @@ This section presumes you called your Swift app `MyApp`. To review an example `Podfile`, consult this [Flutter Podfile sample][]. +要查看 `Podfile` 示例,请参阅此 [Flutter Podfile 示例][Flutter Podfile sample]。 + #### Embed your frameworks +#### 嵌入你的 framework + At build time, Xcode packages your Dart code, each Flutter plugin, and the Flutter engine into their own `*.xcframework` bundles. CocoaPod's `podhelper.rb` script then embeds these `*.xcframework` bundles into your project. +构建时,Xcode 会将 Dart 代码、每个 Flutter plugin 以及 Flutter engine 分别打包为各自的 `*.xcframework` bundle。 +随后 CocoaPods 的 `podhelper.rb` 脚本将这些 `*.xcframework` bundle 嵌入你的项目。 + * `Flutter.xcframework` contains the Flutter engine. + + `Flutter.xcframework` 包含 Flutter engine。 + * `App.xcframework` contains the compiled Dart code for this project. + + `App.xcframework` 包含本项目的已编译 Dart 代码。 + * `.xcframework` contains one Flutter plugin. + `.xcframework` 包含一个 Flutter plugin。 + To embed the Flutter engine, your Dart code, and your Flutter plugins into your iOS app, complete the following procedure. +要将 Flutter engine、Dart 代码和 Flutter plugin 嵌入 iOS app,请完成以下步骤。 + 1. Refresh your Flutter plugins. + 刷新 Flutter plugin。 + If you change the Flutter dependencies in the `pubspec.yaml` file, run `flutter pub get` in your Flutter module directory. This refreshes the list of plugins that the `podhelper.rb` script reads. + 若更改了 `pubspec.yaml` 中的 Flutter 依赖,请在 Flutter 模块目录中运行 `flutter pub get`。 + 这会刷新 `podhelper.rb` 脚本读取的 plugin 列表。 + ```console flutter pub get ``` 1. Embed the plugins and frameworks with CocoaPods. + 使用 CocoaPods 嵌入 plugin 与 framework。 + 1. Navigate to your iOS app project at `/path/to/MyApp/MyApp`. + 进入 iOS app 项目目录 `/path/to/MyApp/MyApp`。 + 1. Use the `pod install` command. + 使用 `pod install` 命令。 + ```console pod install ``` @@ -129,44 +200,73 @@ into your iOS app, complete the following procedure. Your iOS app's **Debug** and **Release** build configurations embed the corresponding [Flutter components for that build mode][build-modes]. + iOS app 的 **Debug** 与 **Release** 构建配置会嵌入对应[该构建模式的 Flutter 组件][build-modes]。 + 1. Build the project. + 构建项目。 + 1. Open `MyApp.xcworkspace` in Xcode. + 在 Xcode 中打开 `MyApp.xcworkspace`。 + Verify that you're opening `MyApp.xcworkspace` and not opening `MyApp.xcodeproj`. The `.xcworkspace` file has the CocoaPod dependencies, the `.xcodeproj` doesn't. + 请确认打开的是 `MyApp.xcworkspace`,而不是 `MyApp.xcodeproj`。 + `.xcworkspace` 文件包含 CocoaPods 依赖,`.xcodeproj` 则没有。 + 1. Select **Product** > **Build** or press Cmd + B. + 选择 **Product** > **Build**,或按 Cmd + B。 + #### Set LLDB Init File +#### 设置 LLDB Init File + :::warning Set your scheme to use Flutter's LLDB Init File. Without this file, debugging on an iOS 26 or later device may crash. + +请将 scheme 设置为使用 Flutter 的 LLDB Init File。没有此文件时,在 iOS 26 及更高版本设备上调试可能会崩溃。 ::: 1. Generate Flutter LLDB files. + 生成 Flutter LLDB 文件。 + 1. Within your flutter application, run the following: + 在 Flutter 应用中运行以下命令: + ```console flutter build ios --config-only ``` This will generate the LLDB files in the `.ios/Flutter/ephemeral` directory. + 这会在 `.ios/Flutter/ephemeral` 目录中生成 LLDB 文件。 + 1. Set the LLDB Init File. + 设置 LLDB Init File。 + 1. Go to **Product > Scheme > Edit Scheme**. + 前往 **Product > Scheme > Edit Scheme**。 + 1. Select the **Run** section in the left side bar. + 在左侧边栏选择 **Run** 部分。 + 1. Set the **LLDB Init File** using the same relative path to your Flutter application as you put in your Podfile in the **Update your Podfile** section. + 设置 **LLDB Init File**,使用与 **Update your Podfile**(更新你的 Podfile)一节中 Podfile 相同的、指向 Flutter 应用的相对路径。 + ```console $(SRCROOT)/../my_flutter/.ios/Flutter/ephemeral/flutter_lldbinit ``` @@ -175,9 +275,13 @@ on an iOS 26 or later device may crash. LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. + 若 scheme 已有 **LLDB Init File**,可将 Flutter 的 LLDB 文件加入其中。Flutter LLDB Init File 的路径必须相对于项目 LLDB Init File 的位置。 + For example, if your LLDB file is located at `/path/to/MyApp/.lldbinit`, you would add the following: + 例如,若 LLDB 文件位于 `/path/to/MyApp/.lldbinit`,可添加以下内容: + ```console command source --relative-to-command-file "../my_flutter/.ios/Flutter/ephemeral/flutter_lldbinit" ``` diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-framework-directory-tree.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-framework-directory-tree.md index 652d8feca1..89aee3cf56 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-framework-directory-tree.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-framework-directory-tree.md @@ -1,14 +1,20 @@ The following example assumes that you want to generate the frameworks to `/path/to/MyApp/Flutter/`. +以下示例假定你要将 framework 生成到 `/path/to/MyApp/Flutter/`。 + ```console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ ``` Run this _every time_ you change code in your Flutter module. +**每次** 更改 Flutter 模块中的代码时都要运行此命令。 + The resulting project structure should resemble this directory tree. +生成的项目结构应与此目录树类似。 + - /path/to/MyApp/ @@ -37,4 +43,9 @@ located in the same directory. Mixing `.xcframework` imports from different directories (like `Profile/Flutter.xcframework` with `Debug/App.xcframework`) causes runtime crashes. + +始终使用位于同一目录中的 `Flutter.xcframework` 和 `App.xcframework` bundle。 +混用来自不同目录的 `.xcframework` 导入 +(例如将 `Profile/Flutter.xcframework` 与 `Debug/App.xcframework` 搭配使用) +会导致运行时崩溃。 ::: diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-frameworks.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-frameworks.md index a0b39ed8be..de7101c056 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-frameworks.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-frameworks.md @@ -1,37 +1,68 @@ ### Link and Embed frameworks in Xcode {:#method-b .no_toc} +### 在 Xcode 中链接并嵌入 framework {:#method-b .no_toc} + #### Approach {:#method-b-approach} +#### 方法 {:#method-b-approach} + In this second method, edit your existing Xcode project, generate the necessary frameworks, and embed them in your app. Flutter generates iOS frameworks for Flutter itself, for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings. +在第二种方法中,编辑你现有的 Xcode 项目,生成所需的 framework,并将其嵌入 app。 +Flutter 会为 Flutter 自身、你已编译的 Dart 代码以及每个 Flutter plugin 生成 iOS framework。 +嵌入这些 framework 并更新现有应用的构建设置。 + #### Requirements {:#method-b-reqs} +#### 要求 {:#method-b-reqs} + No additional software or hardware requirements are needed for this method. Use this method in the following use cases: +此方法不需要额外的软件或硬件。 +在以下用例中使用此方法: + * Members of your team can't install the Flutter SDK and CocoaPods + + 团队成员无法安装 Flutter SDK 和 CocoaPods + * You don't want to use CocoaPods as a dependency manager in existing iOS apps + 你不想在现有 iOS app 中使用 CocoaPods 作为依赖管理器 + #### Limitations {:#method-b-limits} +#### 限制 {:#method-b-limits} + {% render "docs/add-to-app/ios-project/limits-common-deps.md" %} #### Example project structure {:#method-b-structure} +#### 示例项目结构 {:#method-b-structure} + {% render "docs/add-to-app/ios-project/embed-framework-directory-tree.md" %} #### Procedures +#### 步骤 + How you link, embed, or both the generated frameworks into your existing app in Xcode depends on the type of framework. +在 Xcode 中将生成的 framework 链接、嵌入或同时执行两者,取决于 framework 的类型。 + * Link and embed dynamic frameworks. + + 链接并嵌入动态 framework。 + * Link static frameworks. [Never embed them][static-framework]. + 链接静态 framework。[切勿嵌入它们][static-framework]。 + {% render "docs/add-to-app/ios-project/link-and-embed.md" %} [static-framework]: https://developer.apple.com/library/archive/technotes/tn2435/_index.html diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-split.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-split.md index ff7c8b35a3..54b33cf70d 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-split.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/embed-split.md @@ -1,7 +1,11 @@ ### Use frameworks in Xcode and Flutter framework as podspec {:#method-c .no_toc} +### 在 Xcode 中使用 framework,并将 Flutter framework 作为 podspec {:#method-c .no_toc} + #### Approach {:#method-c-approach} +#### 方法 {:#method-c-approach} + This method generates Flutter as a CocoaPods podspec instead of distributing the large `Flutter.xcframework` to other developers, machines, or continuous integration systems. @@ -9,30 +13,54 @@ Flutter still generates iOS frameworks for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings. +此方法将 Flutter 生成为 CocoaPods podspec,而不是将大型 `Flutter.xcframework` 分发给其他开发者、机器或持续集成系统。 +Flutter 仍会为已编译的 Dart 代码以及每个 Flutter plugin 生成 iOS framework。 +嵌入这些 framework 并更新现有应用的构建设置。 + #### Requirements {:#method-c-reqs} +#### 要求 {:#method-c-reqs} + No additional software or hardware requirements are needed for this method. Use this method in the following use cases: +此方法不需要额外的软件或硬件。 +在以下用例中使用此方法: + * Members of your team can't install the Flutter SDK and CocoaPods + + 团队成员无法安装 Flutter SDK 和 CocoaPods + * You don't want to use CocoaPods as a dependency manager in existing iOS apps + 你不想在现有 iOS app 中使用 CocoaPods 作为依赖管理器 + #### Limitations {:#method-c-limits} +#### 限制 {:#method-c-limits} + {% render "docs/add-to-app/ios-project/limits-common-deps.md" %} This method only works with the `beta` or `stable` [release channels][]. +此方法仅适用于 `beta` 或 `stable` [发布渠道][release channels]。 + [release channels]: /install/upgrade#switching-flutter-channels #### Example project structure {:#method-c-structure} +#### 示例项目结构 {:#method-c-structure} + {% render "docs/add-to-app/ios-project/embed-framework-directory-tree.md" %} #### Add Flutter engine to your Podfile +#### 将 Flutter engine 添加到你的 Podfile + Host apps using CocoaPods can add the Flutter engine to their Podfile. +使用 CocoaPods 的宿主 app 可以将 Flutter engine 添加到其 Podfile。 + ```ruby title="MyApp/Podfile" pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/[![build mode]!]/Flutter.podspec' ``` @@ -41,8 +69,13 @@ pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/[![build mode]!]/Flutter.pods You must hard code the `[build mode]` value. For example, use `Debug` if you need to use `flutter attach` and `Release` when you're ready to ship. + +你必须硬编码 `[build mode]` 值。 +例如,若需要使用 `flutter attach` 请使用 `Debug`,准备发布时使用 `Release`。 ::: #### Link and embed app and plugin frameworks +#### 链接并嵌入 app 与 plugin framework + {% render "docs/add-to-app/ios-project/link-and-embed.md" %} diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/limits-common-deps.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/limits-common-deps.md index 793865e9e8..c89fb432cc 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/limits-common-deps.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/limits-common-deps.md @@ -6,13 +6,24 @@ errors result. These errors include issues like `Multiple commands produce 'CommonDependency.framework'`. +Flutter 无法处理[与 xcframework 的公共依赖][common]。 +如果宿主 app 与 Flutter 模块的 plugin 都定义了相同的 pod 依赖,而你又通过此选项集成 Flutter 模块,就会产生错误。 +这些错误包括诸如 `Multiple commands produce +'CommonDependency.framework'` 之类的问题。 + To work around this issue, link every plugin source in its `podspec` file from the Flutter module to the host app's `Podfile`. Link the source instead of the plugins' `xcframework` framework. The next section explains how to [produce that framework][ios-framework]. +要解决此问题,请将 Flutter 模块中每个 plugin 在其 `podspec` 文件里的源码链接到宿主 app 的 `Podfile`。 +应链接源码,而不是 plugin 的 `xcframework` framework。 +下一节说明如何[生成该 framework][ios-framework]。 + To prevent the error that occurs when common dependencies exist, use `flutter build ios-framework` with the `--no-plugins` flag. +为防止出现公共依赖导致的错误,请使用带 `--no-plugins` 标志的 `flutter build ios-framework`。 + [common]: https://github.com/flutter/flutter/issues/130220 [ios-framework]: https://github.com/flutter/flutter/issues/114692 diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/link-and-embed.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/link-and-embed.md index b8ca21d091..07b0d47677 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/link-and-embed.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/link-and-embed.md @@ -1,53 +1,91 @@ Flutter plugins might produce [static or dynamic frameworks][]. Link static frameworks, [_never_ embed them][static-framework]. +Flutter plugin 可能生成 [静态或动态 framework][static or dynamic frameworks]。 +请链接静态 framework,[**切勿** 嵌入它们][static-framework]。 + If you embed a static framework into your iOS app, you can't publish that app to the App Store. Publishing fails with a `Found an unexpected Mach-O header code` archive error. +若将静态 framework 嵌入 iOS app,则无法将该 app 发布到 App Store。 +发布时会因 `Found an unexpected Mach-O header code` 归档错误而失败。 + ##### Link all frameworks +##### 链接所有 framework + To link the necessary frameworks, follow this procedure. +要链接所需 framework,请按以下步骤操作。 + 1. Choose the frameworks to link. + 选择要链接的 framework。 + 1. In the **Project Navigator**, click on your project. + 在 **Project Navigator** 中点击你的项目。 + 1. Click the **Build Phases** tab. + 点击 **Build Phases** 标签页。 + 1. Expand **Link Binary With Libraries**. + 展开 **Link Binary With Libraries**。 + 1. Click **+** (plus sign). + 点击 **+**(加号)。 + 1. Click **Add Other...** then **Add Files...**. + 点击 **Add Other...**,然后点击 **Add Files...**。 + 1. From the **Choose frameworks and libraries to add:** dialog box, navigate to the `/path/to/MyApp/Flutter/Release/` directory. + 在 **Choose frameworks and libraries to add:** 对话框中,导航到 `/path/to/MyApp/Flutter/Release/` 目录。 + 1. Command-click the frameworks in that directory then click **Open**. + 按住 Command 键点击该目录中的 framework,然后点击 **Open**。 + 1. Update the paths to the libraries to account for build modes. + 更新库路径以适配构建模式。 + 1. Launch the Finder. + 打开 Finder。 + 1. Navigate to the `/path/to/MyApp/` directory. + 导航到 `/path/to/MyApp/` 目录。 + 1. Right-click on `MyApp.xcodeproj` and select **Show Package Contents**. + 右键点击 `MyApp.xcodeproj`,选择 **Show Package Contents**。 + 1. Open `project.pbxproj` with Xcode. The file opens in Xcode's text editor. This also locks **Project Navigator** until you close the text editor. + 用 Xcode 打开 `project.pbxproj`。文件会在 Xcode 文本编辑器中打开。在关闭文本编辑器之前,**Project Navigator** 也会被锁定。 + 1. Find the lines that resemble the following text in the `/* Begin PBXFileReference section */`. + 在 `/* Begin PBXFileReference section */` 中查找类似以下内容的行。 + ```text 312885572C1A441C009F74FF /* Flutter.xcframework */ = { isa = PBXFileReference; @@ -70,6 +108,8 @@ To link the necessary frameworks, follow this procedure. and change it to `$(CONFIGURATION)`. Also wrap the path in quotation marks. + 将上一步中高亮的 `Release` 文本改为 `$(CONFIGURATION)`,并用引号包裹路径。 + ```text 312885572C1A441C009F74FF /* Flutter.xcframework */ = { isa = PBXFileReference; @@ -90,87 +130,146 @@ To link the necessary frameworks, follow this procedure. 1. Update the search paths. + 更新搜索路径。 + 1. Click the **Build Settings** tab. + 点击 **Build Settings** 标签页。 + 1. Navigate to **Search Paths** + 导航到 **Search Paths** + 1. Double-click to the right of **Framework Search Paths**. + 双击 **Framework Search Paths** 右侧。 + 1. In the combo box, click **+** (plus sign). + 在组合框中点击 **+**(加号)。 + 1. Type `$(inherited)`. and press Enter. + 输入 `$(inherited)`,然后按 Enter。 + 1. Click **+** (plus sign). + 点击 **+**(加号)。 + 1. Type `$(PROJECT_DIR)/Flutter/$(CONFIGURATION)/` and press Enter. + 输入 `$(PROJECT_DIR)/Flutter/$(CONFIGURATION)/`,然后按 Enter。 + After linking the frameworks, they should display in the **Frameworks, Libraries, and Embedded Content** section of your target's **General** settings. +链接 framework 后,它们应显示在 target **General** 设置的 **Frameworks, Libraries, and Embedded Content** 部分。 + ##### Embed the dynamic frameworks +##### 嵌入动态 framework + To embed your dynamic frameworks, complete the following procedure. +要嵌入动态 framework,请完成以下步骤。 + 1. Navigate to **General** > **Frameworks, Libraries, and Embedded Content**. + 导航到 **General** > **Frameworks, Libraries, and Embedded Content**。 + 1. Click on each of your dynamic frameworks and select **Embed & Sign**. + 点击每个动态 framework,选择 **Embed & Sign**。 + Don't include any static frameworks, including `FlutterPluginRegistrant.xcframework`. + 不要包含任何静态 framework,包括 `FlutterPluginRegistrant.xcframework`。 + 1. Click the **Build Phases** tab. + 点击 **Build Phases** 标签页。 + 1. Expand **Embed Frameworks**. Your dynamic frameworks should display in that section. + 展开 **Embed Frameworks**。动态 framework 应显示在该部分。 + 1. Build the project. + 构建项目。 + 1. Open `MyApp.xcworkspace` in Xcode. + 在 Xcode 中打开 `MyApp.xcworkspace`。 + Verify that you're opening `MyApp.xcworkspace` and not opening `MyApp.xcodeproj`. The `.xcworkspace` file has the CocoaPod dependencies, the `.xcodeproj` doesn't. + 请确认打开的是 `MyApp.xcworkspace`,而不是 `MyApp.xcodeproj`。 + `.xcworkspace` 文件包含 CocoaPods 依赖,`.xcodeproj` 则没有。 + 1. Select **Product** > **Build** or press Cmd + B. + 选择 **Product** > **Build**,或按 Cmd + B。 + #### Set LLDB Init File +#### 设置 LLDB Init File + :::warning Set your scheme to use Flutter's LLDB Init File. Without this file, debugging on an iOS 26 or later device may crash. + +请将 scheme 设置为使用 Flutter 的 LLDB Init File。没有此文件时,在 iOS 26 及更高版本设备上调试可能会崩溃。 ::: 1. Generate Flutter LLDB files. + 生成 Flutter LLDB 文件。 + 1. Within your flutter application, re-run `flutter build ios-framework` if you haven't already: + 在 Flutter 应用中,若尚未运行,请重新执行 `flutter build ios-framework`: + ```console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ ``` This will generate the LLDB files in the `/path/to/MyApp/Flutter/` directory. + 这会在 `/path/to/MyApp/Flutter/` 目录中生成 LLDB 文件。 + 1. Set the LLDB Init File. + 设置 LLDB Init File。 + 1. Go to **Product > Scheme > Edit Scheme**. + 前往 **Product > Scheme > Edit Scheme**。 + 1. Select the **Run** section in the left side bar. + 在左侧边栏选择 **Run** 部分。 + 1. Set the **LLDB Init File** to the following: + 将 **LLDB Init File** 设置为以下内容: + ```console $(PROJECT_DIR)/Flutter/flutter_lldbinit ``` @@ -179,9 +278,13 @@ on an iOS 26 or later device may crash. LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. + 若 scheme 已有 **LLDB Init File**,可将 Flutter 的 LLDB 文件加入其中。Flutter LLDB Init File 的路径必须相对于项目 LLDB Init File 的位置。 + For example, if your LLDB file is located at `/path/to/MyApp/.lldbinit`, you would add the following: + 例如,若 LLDB 文件位于 `/path/to/MyApp/.lldbinit`,可添加以下内容: + ```console command source --relative-to-command-file "Flutter/flutter_lldbinit" ``` diff --git a/sites/docs/src/_includes/docs/add-to-app/ios-project/local-network-privacy-permissions.md b/sites/docs/src/_includes/docs/add-to-app/ios-project/local-network-privacy-permissions.md index 3775c94d4a..2bb30c0469 100644 --- a/sites/docs/src/_includes/docs/add-to-app/ios-project/local-network-privacy-permissions.md +++ b/sites/docs/src/_includes/docs/add-to-app/ios-project/local-network-privacy-permissions.md @@ -3,9 +3,15 @@ On iOS 14 and later, enable the Dart multicast DNS service in the This adds [debugging functionalities such as hot-reload and DevTools][] using `flutter attach`. +在 iOS 14 及更高版本上,请在 iOS app 的 **Debug** 版本中启用 Dart 多播 DNS 服务。 +这样可通过 `flutter attach` 使用[热重载和 DevTools 等调试功能][debugging functionalities such as hot-reload and DevTools]。 + :::warning Never enable this service in the **Release** version of your app. The Apple App Store might reject your app. + +切勿在 app 的 **Release** 版本中启用此服务。 +Apple App Store 可能会拒绝你的 app。 ::: To set local network privacy permissions only in the Debug version of your app, @@ -16,98 +22,178 @@ you can do so through Xcode or text editor. The following instructions assume the default **Debug** and **Release**. Adjust the names as needed depending on your app's build configurations. +若仅在 app 的 Debug 版本中设置本地网络隐私权限,请为每个构建配置创建单独的 `Info.plist`。 +SwiftUI 项目一开始可能没有 `Info.plist` 文件。 +如需创建属性列表,可通过 Xcode 或文本编辑器完成。 +以下说明假定使用默认的 **Debug** 和 **Release**。 +请根据 app 的构建配置按需调整名称。 + 1. Create a new property list. + 创建新的属性列表。 + 1. Open your project in Xcode. + 在 Xcode 中打开项目。 + 1. In the **Project Navigator**, click on the project name. + 在 **Project Navigator** 中点击项目名称。 + 1. From the **Targets** list in the Editor pane, click on your app. + 在编辑器窗格的 **Targets** 列表中点击你的 app。 + 1. Click the **Info** tab. + 点击 **Info** 标签页。 + 1. Expand **Custom iOS Target Properties**. + 展开 **Custom iOS Target Properties**。 + 1. Right-click on the list and select **Add Row**. + 右键点击列表,选择 **Add Row**。 + 1. From the dropdown menu, select **Bonjour Services**. This creates a new property list in the project directory called `Info`. This displays as `Info.plist` in the Finder. + 在下拉菜单中选择 **Bonjour Services**。 + 这会在项目目录中创建一个名为 `Info` 的新属性列表。在 Finder 中显示为 `Info.plist`。 + 1. Rename the `Info.plist` to `Info-Debug.plist` + 将 `Info.plist` 重命名为 `Info-Debug.plist` + 1. Click on **Info** file in the project list at the left. + 在左侧项目列表中点击 **Info** 文件。 + 1. In the **Identity and Type** panel at the right, change the **Name** from `Info.plist` to `Info-Debug.plist`. + 在右侧 **Identity and Type** 面板中,将 **Name** 从 `Info.plist` 改为 `Info-Debug.plist`。 + 1. Create a Release property list. + 创建 Release 属性列表。 + 1. In the **Project Navigator**, click on `Info-Debug.plist`. + 在 **Project Navigator** 中点击 `Info-Debug.plist`。 + 1. Select **File** > **Duplicate...**. You can also press Cmd + Shift + S. + 选择 **File** > **Duplicate...**。 + 也可按 Cmd + Shift + S。 + 1. In the dialog box, set the **Save As:** field to `Info-Release.plist` and click **Save**. + 在对话框中将 **Save As:** 设为 `Info-Release.plist`,然后点击 **Save**。 + 1. Add the necessary properties to the **Debug** property list. + 向 **Debug** 属性列表添加必要属性。 + 1. In the **Project Navigator**, click on `Info-Debug.plist`. + 在 **Project Navigator** 中点击 `Info-Debug.plist`。 + 1. Add the String value `_dartVmService._tcp` to the **Bonjour Services** array. + 向 **Bonjour Services** 数组添加字符串值 `_dartVmService._tcp`。 + 1. _(Optional)_ To set your desired customized permission dialog text, add the key **Privacy - Local Network Usage Description**. + **(可选)** 若要设置自定义权限对话框文案,请添加键 **Privacy - Local Network Usage Description**。 + 1. Set the target to use different property lists for different build modes. + 设置 target 在不同构建模式下使用不同属性列表。 + 1. In the **Project Navigator**, click on your project. + 在 **Project Navigator** 中点击你的项目。 + 1. Click the **Build Settings** tab. + 点击 **Build Settings** 标签页。 + 1. Click **All** and **Combined** sub-tabs. + 点击 **All** 和 **Combined** 子标签页。 + 1. In the Search box, type `plist`. This limits the settings to those that include property lists. + 在搜索框中输入 `plist`,将设置限定为与属性列表相关的项。 + 1. Scroll through the list until you see **Packaging**. + 滚动列表直至看到 **Packaging**。 + 1. Click on the **Info.plist File** setting. + 点击 **Info.plist File** 设置。 + 1. Change the **Info.plist File** value from `path/to/Info.plist` to `path/to/Info-$(CONFIGURATION).plist`. + 将 **Info.plist File** 的值从 `path/to/Info.plist` 改为 `path/to/Info-$(CONFIGURATION).plist`。 + This resolves to the path **Info-Debug.plist** in **Debug** and **Info-Release.plist** in **Release**. + 在 **Debug** 中解析为 **Info-Debug.plist**,在 **Release** 中解析为 **Info-Release.plist**。 + 1. Remove the **Release** property list from the **Build Phases**. + 从 **Build Phases** 中移除 **Release** 属性列表。 + 1. In the **Project Navigator**, click on your project. + 在 **Project Navigator** 中点击你的项目。 + 1. Click the **Build Phases** tab. + 点击 **Build Phases** 标签页。 + 1. Expand **Copy Bundle Resources**. + 展开 **Copy Bundle Resources**。 + 1. If this list includes `Info-Release.plist`, click on it and then click the **-** (minus sign) under it to remove the property list from the resources list. + 若列表包含 `Info-Release.plist`,请点击它,再点击下方 **-**(减号)将其从资源列表中移除。 + 1. The first Flutter screen your Debug app loads prompts for local network permission. + Debug app 加载的第一个 Flutter 界面会提示本地网络权限。 + Click **OK**. + 点击 **OK**。 + _(Optional)_ To grant permission before the app loads, enable **Settings > Privacy > Local Network > Your App**. -[debugging functionalities such as hot-reload and DevTools]: /add-to-app/debugging \ No newline at end of file + **(可选)** 若要在 app 加载前授予权限,请启用 **Settings > Privacy > Local Network > Your App**。 + +[debugging functionalities such as hot-reload and DevTools]: /add-to-app/debugging diff --git a/sites/docs/src/_includes/docs/android-ios-figure-pair.md b/sites/docs/src/_includes/docs/android-ios-figure-pair.md index fccfaaabb3..f8958f23bb 100644 --- a/sites/docs/src/_includes/docs/android-ios-figure-pair.md +++ b/sites/docs/src/_includes/docs/android-ios-figure-pair.md @@ -1,10 +1,10 @@
- {{alt}} on Android + {{alt}} on Android
Android
- {{alt}} on iOS + {{alt}} on iOS
iOS
-
\ No newline at end of file + diff --git a/sites/docs/src/_includes/docs/debug/debug-android-attach-process.md b/sites/docs/src/_includes/docs/debug/debug-android-attach-process.md index bcf881a0fc..fabf1af5e9 100644 --- a/sites/docs/src/_includes/docs/debug/debug-android-attach-process.md +++ b/sites/docs/src/_includes/docs/debug/debug-android-attach-process.md @@ -1,19 +1,31 @@ 1. Click the **Attach debugger to Android process** button. + (![Tiny green bug superimposed with a light grey arrow](/assets/images/docs/testing/debugging/native/android-studio/attach-process-button.png)) + + 点击 **Attach debugger to Android process**(将调试器附加到 Android 进程)按钮。 (![Tiny green bug superimposed with a light grey arrow](/assets/images/docs/testing/debugging/native/android-studio/attach-process-button.png)) :::tip If this button doesn't appear in the **Projects** menu bar, verify that you opened Flutter _application_ project but _not a Flutter plugin_. + + 若 **Projects** 菜单栏中没有此按钮,请确认你打开的是 Flutter **application** 项目, + 而 **不是 Flutter plugin**。 ::: 1. The **process** dialog displays one entry for each connected device. Select **show all processes** to display available processes for each device. + **process** 对话框会为每个已连接设备显示一项。 + 选择 **show all processes**(显示所有进程)可显示各设备上的可用进程。 + 1. Choose the process to which you want to attach. For this guide, select the `com.example.my_app` process using the **Emulator Pixel_5_API_33**. + 选择要附加的进程。 + 本指南请选择 **Emulator Pixel_5_API_33** 上的 `com.example.my_app` 进程。 + {% comment %} @atsansone - 2023-07-24 @@ -35,6 +47,8 @@ 1. Locate the tab for **Android Debugger** in the **Debug** pane. + 在 **Debug** 窗格中找到 **Android Debugger** 标签页。 + 1. In the **Project** pane, expand **my_app_android** > **android** > @@ -44,9 +58,20 @@ **java** > **io.flutter plugins**. + 在 **Project** 窗格中展开 + **my_app_android** > + **android** > + **app** > + **src** > + **main** > + **java** > + **io.flutter plugins**。 + 1. Double click **GeneratedProjectRegistrant** to open the Java code in the **Edit** pane. + 双击 **GeneratedProjectRegistrant**,在 **Edit** 窗格中打开 Java 代码。 + {% comment %} !['The Android Project view highlighting the GeneratedPluginRegistrant.java file.'](/assets/images/docs/testing/debugging/native/android-studio/debug-open-java-code.png){:width="100%"}
@@ -61,6 +86,9 @@ with the same process. Use either, or both, to set breakpoints, examine stack, resume execution and the like. In other words, debug! +完成此流程后,Dart 与 Android 调试器都会与同一进程交互。 +你可以使用其中任一或两者来设置断点、查看堆栈、恢复执行等。换句话说,开始调试吧! + {% comment %} ![The Dart debug pane with two breakpoints set in `lib/main.dart`](/assets/images/docs/testing/debugging/native/dart-debugger.png){:width="100%"}
diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-android.md b/sites/docs/src/_includes/docs/debug/debug-flow-android.md index 173d7a21a6..0978fcfc50 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-android.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-android.md @@ -1,8 +1,12 @@ #### Build the Android version of the Flutter app in the Terminal +#### 在终端中构建 Flutter app 的 Android 版本 + To generate the needed Android platform dependencies, run the `flutter build` command. +要生成所需的 Android 平台依赖,请运行 `flutter build` 命令。 + ```console flutter build appbundle --debug ``` @@ -14,25 +18,37 @@ Running Gradle task 'bundleDebug'... 27.1s - + + #### Start debugging with VS Code first {:#from-vscode-to-android-studio} +#### 先从 VS Code 开始调试 {:#from-vscode-to-android-studio} + If you use VS Code to debug most of your code, start with this section. +若你主要用 VS Code 调试代码,请从本节开始。 + {% render "docs/debug/debug-flow-vscode-as-start.md" %} #### Attach to the Flutter process in Android Studio +#### 在 Android Studio 中附加到 Flutter 进程 + {% render "docs/debug/debug-android-attach-process.md" %} - + + #### Start debugging with Android Studio first {:#from-android-studio} +#### 先从 Android Studio 开始调试 {:#from-android-studio} + If you use Android Studio to debug most of your code, start with this section. +若你主要用 Android Studio 调试代码,请从本节开始。 + {% render "docs/debug/debug-flow-androidstudio-as-start.md" %} {% render "docs/debug/debug-android-attach-process.md" %} diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-androidstudio-as-start.md b/sites/docs/src/_includes/docs/debug/debug-flow-androidstudio-as-start.md index fd205008c2..c539a192fa 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-androidstudio-as-start.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-androidstudio-as-start.md @@ -2,18 +2,37 @@ **File** > **Open...** and choose the `my_app` directory. + 要打开 Flutter app 目录,请前往 + **File** > + **Open...**,选择 `my_app` 目录。 + 1. Open the `lib/main.dart` file. + 打开 `lib/main.dart` 文件。 + 1. Choose a virtual Android device. Go to the toolbar, open the leftmost dropdown menu, and click on **Open Android Emulator: \**. + 选择虚拟 Android 设备。 + 在工具栏打开最左侧下拉菜单,点击 **Open Android Emulator: \**。 + You can choose any installed emulator that's doesn't include `arm64`. + 你可以选择任何已安装且名称中不包含 `arm64` 的模拟器。 + 1. From that same menu, select the virtual Android device. + 在同一菜单中选择该虚拟 Android 设备。 + 1. From the toolbar, click **Run 'main.dart'**. + 在工具栏点击 **Run 'main.dart'**。 + You can also press Ctrl + Shift + R. + 也可按 Ctrl + Shift + R。 + After the app displays in the emulator, continue to the next step. + + app 在模拟器中显示后,继续下一步。 diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-ios.md b/sites/docs/src/_includes/docs/debug/debug-flow-ios.md index 1eaf7dfae8..e507e3dee2 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-ios.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-ios.md @@ -1,8 +1,12 @@ #### Build the iOS version of the Flutter app in the Terminal +#### 在终端中构建 Flutter app 的 iOS 版本 + To generate the needed iOS platform dependencies, run the `flutter build` command. +要生成所需的 iOS 平台依赖,请运行 `flutter build` 命令。 + ```console $ flutter build ios --config-only --no-codesign --debug ``` @@ -13,14 +17,21 @@ Building com.example.myApp for device (ios)... ``` - + + #### Start debugging with VS Code first {:#vscode-ios} +#### 先从 VS Code 开始调试 {:#vscode-ios} + If you use VS Code to debug most of your code, start with this section. +若你主要用 VS Code 调试代码,请从本节开始。 + ##### Start the Dart debugger in VS Code +##### 在 VS Code 中启动 Dart 调试器 + {% render "docs/debug/debug-flow-vscode-as-start.md" %} {% if add == 'launch' %} @@ -29,28 +40,50 @@ If you use VS Code to debug most of your code, start with this section. ##### Attach to the Flutter process in Xcode +##### 在 Xcode 中附加到 Flutter 进程 + To attach to the Flutter app in Xcode: +要在 Xcode 中附加到 Flutter app: + 1. Go to **Debug** > **Attach to Process**. + 前往 **Debug** > **Attach to Process**。 + 1. Select **Runner**. It should be at the top of the **Attach to Process** menu under the **Likely Targets** heading. + 选择 **Runner**。 + 它应位于 **Attach to Process** 菜单 **Likely Targets** 标题下的顶部。 + - + + #### Start debugging with Xcode first {:#xcode-ios} +#### 先从 Xcode 开始调试 {:#xcode-ios} + If you use Xcode to debug most of your code, start with this section. +若你主要用 Xcode 调试代码,请从本节开始。 + ##### Start the Xcode debugger +##### 启动 Xcode 调试器 + 1. Open `ios/Runner.xcworkspace` from your Flutter app directory. + 从 Flutter app 目录打开 `ios/Runner.xcworkspace`。 + 1. Select the correct device using the **Scheme** menu in the toolbar. + 使用工具栏中的 **Scheme** 菜单选择正确的设备。 + If you have no preference, choose **iPhone Pro 14**. + 若无偏好,请选择 **iPhone Pro 14**。 + {% comment %} ![Selecting iPhone 14 in the Scheme menu in the Xcode toolbar](/assets/images/docs/testing/debugging/native/xcode/select-device.png){:width="100%"}
@@ -62,6 +95,8 @@ If you use Xcode to debug most of your code, start with this section. 1. Run this Runner as a normal app in Xcode. + 在 Xcode 中将此 Runner 作为普通 app 运行。 + {% comment %} ![Start button in Xcode interface](/assets/images/docs/testing/debugging/native/xcode/run-app.png)
@@ -74,6 +109,9 @@ If you use Xcode to debug most of your code, start with this section. When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: + 运行完成后,Xcode 底部的 **Debug** 区域会显示包含 Dart VM 服务 URI 的消息, + 类似以下输出: + ```console 2023-07-12 14:55:39.966191-0500 Runner[58361:53017145] flutter: The Dart VM service is listening on @@ -82,18 +120,32 @@ If you use Xcode to debug most of your code, start with this section. 1. Copy the Dart VM service URI. + 复制 Dart VM 服务 URI。 + ##### Attach to the Dart VM in VS Code +##### 在 VS Code 中附加到 Dart VM + 1. To open the command palette, go to **View** > **Command Palette...** + 要打开命令面板,请前往 + **View** > + **Command Palette...** + You can also press Cmd + Shift + P. + 也可按 Cmd + Shift + P。 + 1. Type `debug`. + 输入 `debug`。 + 1. Click the **Debug: Attach to Flutter on Device** command. + 点击 **Debug: Attach to Flutter on Device** 命令。 + {% comment %} !['Running the Debug: Attach to Flutter on Device command in VS Code.'](/assets/images/docs/testing/debugging/vscode-ui/screens/attach-flutter-process-menu.png){:width="100%"} {% endcomment %} @@ -101,6 +153,9 @@ If you use Xcode to debug most of your code, start with this section. 1. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press Enter. + 在 **Paste an VM Service URI** 框中粘贴从 Xcode 复制的 URI, + 然后按 Enter。 + {% comment %} ![Alt text](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-add-attach-uri-filled.png) {% endcomment %} diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-macos.md b/sites/docs/src/_includes/docs/debug/debug-flow-macos.md index aec1d722e6..51030e87e9 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-macos.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-macos.md @@ -1,8 +1,12 @@ #### Build the macOS version of the Flutter app in the Terminal +#### 在终端中构建 Flutter app 的 macOS 版本 + To generate the needed macOS platform dependencies, run the `flutter build` command. +要生成所需的 macOS 平台依赖,请运行 `flutter build` 命令。 + ```console flutter build macos --debug ``` @@ -12,35 +16,59 @@ Building macOS application... ``` - + + #### Start debugging with VS Code first {:#vscode-macos} +#### 先从 VS Code 开始调试 {:#vscode-macos} + ##### Start the debugger in VS Code +##### 在 VS Code 中启动调试器 + {% render "docs/debug/debug-flow-vscode-as-start.md" %} ##### Attach to the Flutter process in Xcode +##### 在 Xcode 中附加到 Flutter 进程 + 1. To attach to the Flutter app, go to **Debug** > **Attach to Process** > **Runner**. + 要附加到 Flutter app,请前往 + **Debug** > + **Attach to Process** > + **Runner**。 + **Runner** should be at the top of the **Attach to Process** menu under the **Likely Targets** heading. + **Runner** 应位于 **Attach to Process** 菜单 + **Likely Targets** 标题下的顶部。 + - + + #### Start debugging with Xcode first {:#xcode-macos} +#### 先从 Xcode 开始调试 {:#xcode-macos} + ##### Start the debugger in Xcode +##### 在 Xcode 中启动调试器 + 1. Open `macos/Runner.xcworkspace` from your Flutter app directory. + 从 Flutter app 目录打开 `macos/Runner.xcworkspace`。 + 1. Run this Runner as a normal app in Xcode. + 在 Xcode 中将此 Runner 作为普通 app 运行。 + {% comment %} ![Start button in Xcode interface](/assets/images/docs/testing/debugging/native/xcode/run-app.png)
@@ -53,6 +81,9 @@ Building macOS application... When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: + 运行完成后,Xcode 底部的 **Debug** 区域会显示包含 Dart VM 服务 URI 的消息, + 类似以下输出: + ```console 2023-07-12 14:55:39.966191-0500 Runner[58361:53017145] flutter: The Dart VM service is listening on @@ -61,16 +92,28 @@ Building macOS application... 1. Copy the Dart VM service URI. + 复制 Dart VM 服务 URI。 + ##### Attach to the Dart VM in VS Code +##### 在 VS Code 中附加到 Dart VM + 1. To open the command palette, go to **View** > **Command Palette...** + 要打开命令面板,请前往 **View** > **Command Palette...** + You can also press Cmd + Shift + P. + 也可按 Cmd + Shift + P。 + 1. Type `debug`. + 输入 `debug`。 + 1. Click the **Debug: Attach to Flutter on Device** command. + 点击 **Debug: Attach to Flutter on Device** 命令。 + {% comment %} !['Running the Debug: Attach to Flutter on Device command in VS Code.'](/assets/images/docs/testing/debugging/vscode-ui/screens/attach-flutter-process-menu.png){:width="100%"} {% endcomment %} @@ -78,6 +121,9 @@ Building macOS application... 1. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press Enter. + 在 **Paste an VM Service URI** 框中粘贴从 Xcode 复制的 URI, + 然后按 Enter。 + {% comment %} ![Alt text](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-add-attach-uri-filled.png) {% endcomment %} diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-vscode-as-start.md b/sites/docs/src/_includes/docs/debug/debug-flow-vscode-as-start.md index 598f72f88f..32313665de 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-vscode-as-start.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-vscode-as-start.md @@ -2,24 +2,44 @@ **File** > **Open Folder...** and choose the `my_app` directory. + 要打开 Flutter app 目录,请前往 + **File** > + **Open Folder...**,选择 `my_app` 目录。 + 1. Open the `lib/main.dart` file. + 打开 `lib/main.dart` 文件。 + 1. If you can build an app for more than one device, you must select the device first. + 若可为多种设备构建 app,须先选择设备。 + Go to **View** > **Command Palette...** + 前往 + **View** > + **Command Palette...** + You can also press Ctrl / Cmd + Shift + P. + 也可按 Ctrl / Cmd + Shift + P。 + 1. Type `flutter select`. + 输入 `flutter select`。 + 1. Click the **Flutter: Select Device** command. + 点击 **Flutter: Select Device** 命令。 + 1. Choose your target device. + 选择目标设备。 + 1. Click the debug icon (![VS Code's bug icon to trigger the debugging mode of a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the **Debug** pane and launches the app. @@ -28,9 +48,23 @@ The debugger takes longer to launch the first time. Subsequent launches start faster. + 点击调试图标 + (![VS Code's bug icon to trigger the debugging mode of a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png))。 + 这会打开 **Debug** 窗格并启动 app。 + 等待 app 在设备上启动,且调试窗格显示 **Connected**。 + 首次启动调试器耗时更长,之后启动会更快。 + This Flutter app contains two buttons: + 此 Flutter app 包含两个按钮: + - **Launch in browser**: This button opens this page in the default browser of your device. + + **Launch in browser**:此按钮会在设备的默认浏览器中打开本页。 + - **Launch in app**: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. + + **Launch in app**:此按钮会在 app 内打开本页。 + 仅适用于 iOS 或 Android。桌面 app 会启动浏览器。 diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-windows.md b/sites/docs/src/_includes/docs/debug/debug-flow-windows.md index 7210dd6dc9..7b2e14b10b 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-windows.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-windows.md @@ -1,8 +1,12 @@ #### Build the Windows version of the Flutter app in PowerShell or the Command Prompt +#### 在 PowerShell 或命令提示符中构建 Flutter app 的 Windows 版本 + To generate the needed Windows platform dependencies, run the `flutter build` command. +要生成所需的 Windows 平台依赖,请运行 `flutter build` 命令。 + ```console C:\> flutter build windows --debug ``` @@ -13,14 +17,21 @@ Building Windows application... 31.4s ``` - + + #### Start debugging with VS Code first {:#vscode-windows} +#### 先从 VS Code 开始调试 {:#vscode-windows} + If you use VS Code to debug most of your code, start with this section. +若你主要用 VS Code 调试代码,请从本节开始。 + ##### Start the debugger in VS Code +##### 在 VS Code 中启动调试器 + {% render "docs/debug/debug-flow-vscode-as-start.md" %} {% comment %} @@ -34,15 +45,26 @@ If you use VS Code to debug most of your code, start with this section. ##### Attach to the Flutter process in Visual Studio +##### 在 Visual Studio 中附加到 Flutter 进程 + 1. To open the project solution file, go to **File** > **Open** > **Project/Solution…** + 要打开项目解决方案文件,请前往 + **File** > + **Open** > + **Project/Solution…** + You can also press Ctrl + Shift + O. + 也可按 Ctrl + Shift + O。 + 1. Choose the `build/windows/my_app.sln` file in your Flutter app directory. + 在 Flutter app 目录中选择 `build/windows/my_app.sln` 文件。 + {% comment %} ![Open Project/Solution dialog box in Visual Studio 2022 with my_app.sln file selected.](/assets/images/docs/testing/debugging/native/visual-studio/choose-solution.png){:width="100%"}
@@ -55,38 +77,62 @@ If you use VS Code to debug most of your code, start with this section. 1. Go to **Debug** > **Attach to Process**. + 前往 **Debug** > **Attach to Process**。 + You can also press Ctrl + Alt + P. + 也可按 Ctrl + Alt + P。 + 1. From the **Attach to Process** dialog box, choose `my_app.exe`. + 在 **Attach to Process** 对话框中选择 `my_app.exe`。 + {% comment %} ![Selecting my_app from the Attach to Process dialog box](/assets/images/docs/testing/debugging/native/visual-studio/attach-to-process-dialog.png){:width="100%"} {% endcomment %} Visual Studio starts monitoring the Flutter app. + Visual Studio 开始监视 Flutter app。 + {% comment %} ![Visual Studio debugger running and monitoring the Flutter app](/assets/images/docs/testing/debugging/native/visual-studio/debugger-active.png){:width="100%"} {% endcomment %} - + + #### Start debugging with Visual Studio first +#### 先从 Visual Studio 开始调试 + If you use Visual Studio to debug most of your code, start with this section. +若你主要用 Visual Studio 调试代码,请从本节开始。 + ##### Start the local Windows debugger +##### 启动本地 Windows 调试器 + 1. To open the project solution file, go to **File** > **Open** > **Project/Solution…** + 要打开项目解决方案文件,请前往 + **File** > + **Open** > + **Project/Solution…** + You can also press Ctrl + Shift + O. + 也可按 Ctrl + Shift + O。 + 1. Choose the `build/windows/my_app.sln` file in your Flutter app directory. + 在 Flutter app 目录中选择 `build/windows/my_app.sln` 文件。 + {% comment %} ![Open Project/Solution dialog box in Visual Studio 2022 with my_app.sln file selected.](/assets/images/docs/testing/debugging/native/visual-studio/choose-solution.png){:width="100%"}
@@ -101,31 +147,55 @@ If you use Visual Studio to debug most of your code, start with this section. In the **Solution Explorer**, right-click on `my_app` and select **Set as Startup Project**. + 将 `my_app` 设为启动项目。 + 在 **Solution Explorer** 中右键点击 `my_app`,选择 **Set as Startup Project**。 + 1. Click **Local Windows Debugger** to start debugging. + 点击 **Local Windows Debugger** 开始调试。 + You can also press F5. + 也可按 F5。 + When the Flutter app has started, a console window displays a message with the Dart VM service URI. It resembles the following response: + Flutter app 启动后,控制台窗口会显示包含 Dart VM 服务 URI 的消息, + 类似以下输出: + ```console flutter: The Dart VM service is listening on http://127.0.0.1:62080/KPHEj2qPD1E=/ ``` 1. Copy the Dart VM service URI. + 复制 Dart VM 服务 URI。 + ##### Attach to the Dart VM in VS Code +##### 在 VS Code 中附加到 Dart VM + 1. To open the command palette, go to **View** > **Command Palette...** + 要打开命令面板,请前往 + **View** > + **Command Palette...** + You can also press Cmd + Shift + P. + 也可按 Cmd + Shift + P。 + 1. Type `debug`. + 输入 `debug`。 + 1. Click the **Debug: Attach to Flutter on Device** command. + 点击 **Debug: Attach to Flutter on Device** 命令。 + {% comment %} !['Running the Debug: Attach to Flutter on Device command in VS Code.'](/assets/images/docs/testing/debugging/vscode-ui/screens/attach-flutter-process-menu.png){:width="100%"} {% endcomment %} @@ -133,6 +203,9 @@ If you use Visual Studio to debug most of your code, start with this section. 1. In the **Paste an VM Service URI** box, paste the URI you copied from Visual Studio and press Enter. + 在 **Paste an VM Service URI** 框中粘贴从 Visual Studio 复制的 URI, + 然后按 Enter。 + {% comment %} ![Alt text](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-add-attach-uri-filled.png) {% endcomment %} diff --git a/sites/docs/src/_includes/docs/debug/debug-flow-xcode-as-start.md b/sites/docs/src/_includes/docs/debug/debug-flow-xcode-as-start.md index 3827463bbc..2c1267777a 100644 --- a/sites/docs/src/_includes/docs/debug/debug-flow-xcode-as-start.md +++ b/sites/docs/src/_includes/docs/debug/debug-flow-xcode-as-start.md @@ -1,12 +1,21 @@ ##### Start the Xcode debugger {:.no_toc} +##### 启动 Xcode 调试器 +{:.no_toc} + 1. Open `ios/Runner.xcworkspace` from your Flutter app directory. + 从 Flutter app 目录打开 `ios/Runner.xcworkspace`。 + 1. Select the correct device using the **Scheme** menu in the toolbar. + 使用工具栏中的 **Scheme** 菜单选择正确的设备。 + If you have no preference, choose **iPhone Pro 14**. + 若无偏好,请选择 **iPhone Pro 14**。 + {% comment %} ![Selecting iPhone 14 in the Scheme menu in the Xcode toolbar](/assets/images/docs/testing/debugging/native/xcode/select-device.png){:width="100%"}
{:.figure-caption} @@ -16,6 +25,8 @@ 1. Run this Runner as a normal app in Xcode. + 在 Xcode 中将此 Runner 作为普通 app 运行。 + {% comment %} ![Start button in Xcode interface](/assets/images/docs/testing/debugging/native/xcode/run-app.png)
{:.figure-caption} @@ -26,6 +37,9 @@ When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: + 运行完成后,Xcode 底部的 **Debug** 区域会显示包含 Dart VM 服务 URI 的消息, + 类似以下输出: + ```console 2023-07-12 14:55:39.966191-0500 Runner[58361:53017145] flutter: The Dart VM service is listening on @@ -34,19 +48,34 @@ 1. Copy the Dart VM service URI. + 复制 Dart VM 服务 URI。 + ##### Attach to the Dart VM in VS Code {:.no_toc} +##### 在 VS Code 中附加到 Dart VM +{:.no_toc} + 1. To open the command palette, go to **View** > **Command Palette...** + 要打开命令面板,请前往 + **View** > + **Command Palette...** + You can also press Cmd + Shift + P. + 也可按 Cmd + Shift + P。 + 1. Type `debug`. + 输入 `debug`。 + 1. Click the **Debug: Attach to Flutter on Device** command. + 点击 **Debug: Attach to Flutter on Device** 命令。 + {% comment %} !['Running the Debug: Attach to Flutter on Device command in VS Code.'](/assets/images/docs/testing/debugging/vscode-ui/screens/attach-flutter-process-menu.png){:width="100%"} {% endcomment %} @@ -54,6 +83,9 @@ 1. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press Enter. + 在 **Paste an VM Service URI** 框中粘贴从 Xcode 复制的 URI, + 然后按 Enter。 + {% comment %} ![Alt text](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-add-attach-uri-filled.png) {% endcomment %} diff --git a/sites/docs/src/_includes/docs/debug/vscode-flutter-attach-json.md b/sites/docs/src/_includes/docs/debug/vscode-flutter-attach-json.md index 739a5c4d77..0ceed54de0 100644 --- a/sites/docs/src/_includes/docs/debug/vscode-flutter-attach-json.md +++ b/sites/docs/src/_includes/docs/debug/vscode-flutter-attach-json.md @@ -1,27 +1,46 @@ ##### Enable automatic attachment +##### 启用自动附加 + You can configure VS Code to attach to your Flutter module project whenever you start debugging. To enable this feature, create a `.vscode/launch.json` file in your Flutter module project. +你可以配置 VS Code,使其在你开始调试时自动附加到 Flutter 模块项目。 +要启用此功能,请在 Flutter 模块项目中创建 `.vscode/launch.json` 文件。 + 1. Go to **View** > **Run**. + 前往 **View** > **Run**。 + You can also press Ctrl / Cmd + Shift + D. + 也可按 Ctrl / Cmd + Shift + D。 + VS Code displays the **Run and Debug** sidebar. + VS Code 会显示 **Run and Debug** 侧边栏。 + 1. In this sidebar, click **create a launch.json file**. + 在此侧边栏中点击 **create a launch.json file**。 + VS Code displays the **Select debugger** menu at the top. + VS Code 会在顶部显示 **Select debugger** 菜单。 + 1. Select **Dart & Flutter**. + 选择 **Dart & Flutter**。 + VS Code creates then opens the `.vscode/launch.json` file. + VS Code 会创建并打开 `.vscode/launch.json` 文件。 +
- Expand to see an example launch.json file + Expand to see an example launch.json file展开查看 launch.json 示例文件 ```json { @@ -56,4 +75,9 @@ create a `.vscode/launch.json` file in your Flutter module project. 1. To attach, go to **Run** > **Start Debugging**. + 要附加,请前往 **Run** > + **Start Debugging**。 + You can also press F5. + + 也可按 F5。 diff --git a/sites/docs/src/_includes/docs/get-started/setup-next-steps.html b/sites/docs/src/_includes/docs/get-started/setup-next-steps.html index 604ec7b269..156e3f0ff9 100644 --- a/sites/docs/src/_includes/docs/get-started/setup-next-steps.html +++ b/sites/docs/src/_includes/docs/get-started/setup-next-steps.html @@ -2,27 +2,28 @@
+ alt="A representation of Flutter on multiple devices." + title="多部设备上的 Flutter 示意图。">
- Build for other platforms + Build for other platforms面向其他平台构建
@@ -31,27 +32,28 @@
+ alt="Dash helping you explore Flutter learning resources." + title="Dash 助你探索 Flutter 学习资源。">
- Learn Flutter development + Learn Flutter development学习 Flutter 开发
  • - Learn the fundamentals + Learn the fundamentals学习基础知识
  • - Discover Flutter widgets + Discover Flutter widgets探索 Flutter widget
  • - Explore learning resources + Explore learning resources浏览学习资源
  • - Learn Dart programming + Learn Dart programming学习 Dart 编程
  • @@ -62,34 +64,35 @@
    + alt="Keep up to date with Flutter" + title="及时了解 Flutter 动态">
    - Stay up to date with Flutter + Stay up to date with Flutter及时了解 Flutter 动态
    • - Update Flutter + Update Flutter更新 Flutter
    • - Find out what's new + Find out what's new了解新特性
    • - Check out the blog + Check out the blog查看博客
    • - Subscribe on YouTube + Subscribe on YouTube在 YouTube 订阅
    • - Follow on Bluesky + Follow on Bluesky在 Bluesky 关注
    • diff --git a/sites/docs/src/_includes/docs/install/path/chromeos.md b/sites/docs/src/_includes/docs/install/path/chromeos.md index 66676083b1..94132993fe 100644 --- a/sites/docs/src/_includes/docs/install/path/chromeos.md +++ b/sites/docs/src/_includes/docs/install/path/chromeos.md @@ -3,23 +3,46 @@ The following steps assume you've already [turned on Linux support][chromeos-linux] and that you're using Bash or the default shell on ChromeOS. +以下步骤假定 +你已 [开启 Linux 支持][chromeos-linux],并且 +你使用的是 ChromeOS 上的 Bash 或默认 shell。 + If you're using a different shell besides the default or Bash, follow the [add to path instructions for Linux][linux-path]{: target="_blank"} instead. + +若你使用的不是默认 shell 或 Bash,请改为按照 +[Linux 的添加到 PATH 说明][linux-path]{: target="_blank"} 操作。 ::: 1.

      Determine your Flutter SDK installation location

      +

      确定 Flutter SDK 安装位置

      + Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

      Add the Flutter SDK bin to your path

      +

      将 Flutter SDK 的 bin 添加到你的 PATH

      + To add the `bin` directory of your Flutter installation to your `PATH`: + 要将 Flutter 安装目录下的 `bin` 添加到 `PATH`: + 1. Copy the following command. + + 复制以下命令。 + 1. Replace `` with the path to your Flutter SDK install. + + 将 `` 替换为你的 Flutter SDK 安装路径。 + 1. Run the edited command in your preferred terminal. + 在你偏好的终端中运行编辑后的命令。 + ```console $ echo 'export PATH=":$PATH"' >> ~/.bash_profile ``` @@ -28,21 +51,35 @@ If you're using a different shell besides the default or Bash, follow the `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bash_profile ``` 1.

      Apply your changes

      +

      应用你的更改

      + To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. + 要应用此更改并访问 `flutter` 工具, + 请在终端应用和 IDE 中关闭并重新打开所有已打开的 Zsh 会话。 + 1.

      Validate your setup

      +

      验证你的配置

      + To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools. + 为确保你已成功将 SDK 添加到 `PATH`, + 请在你偏好的终端中打开 Zsh 会话, + 然后尝试运行 `flutter` 和 `dart` 工具。 + ```console $ flutter --version $ dart --version @@ -51,6 +88,9 @@ If you're using a different shell besides the default or Bash, follow the If either command isn't found, check out [Flutter installation troubleshooting][troubleshoot]. + 若任一命令未找到, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {: .steps} [chromeos-linux]: https://support.google.com/chromebook/answer/9145439 diff --git a/sites/docs/src/_includes/docs/install/path/linux.md b/sites/docs/src/_includes/docs/install/path/linux.md index 30867120f2..4ba869e0d2 100644 --- a/sites/docs/src/_includes/docs/install/path/linux.md +++ b/sites/docs/src/_includes/docs/install/path/linux.md @@ -1,30 +1,54 @@ 1.

      Determine your Flutter SDK installation location

      +

      确定 Flutter SDK 安装位置

      + Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

      Determine your default shell

      +

      确定你的默认 shell

      + If you don't know what shell you use, check which shell starts when you open a new console window. + 若你不确定自己使用哪种 shell, + 可查看打开新控制台窗口时启动的是哪一种。 + ```console $ echo $SHELL ``` 1.

      Add the Flutter SDK bin to your path

      +

      将 Flutter SDK 的 bin 添加到你的 PATH

      + To add the `bin` directory of your Flutter installation to your `PATH`: + 要将 Flutter 安装目录下的 `bin` 添加到 `PATH`: + 1. Expand the instructions for your default shell. + + 展开与你默认 shell 对应的说明。 + 1. Copy the provided command. + + 复制提供的命令。 + 1. Replace `` with the path to your Flutter SDK install. + + 将 `` 替换为你的 Flutter SDK 安装路径。 + 1. Run the edited command in your preferred terminal with that shell. + 在使用该 shell 的你偏好的终端中运行编辑后的命令。 +
      - Expand for bash instructions + 展开 bash 相关说明 ```console $ echo 'export PATH="/bin:$PATH"' >> ~/.bashrc @@ -34,6 +58,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bashrc ``` @@ -41,7 +68,7 @@
      - Expand for zsh instructions + 展开 zsh 相关说明 ```console $ echo 'export PATH="/bin:$PATH"' >> ~/.zshenv @@ -51,6 +78,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.zshenv ``` @@ -58,7 +88,7 @@
      - Expand for fish instructions + 展开 fish 相关说明 ```console $ fish_add_path -g -p /bin @@ -68,6 +98,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ fish_add_path -g -p ~/develop/flutter/bin ``` @@ -75,7 +108,7 @@
      - Expand for csh instructions + 展开 csh 相关说明 ```console $ echo 'setenv PATH "/bin:$PATH"' >> ~/.cshrc @@ -85,6 +118,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.cshrc ``` @@ -92,7 +128,7 @@
      - Expand for tcsh instructions + 展开 tcsh 相关说明 ```console $ echo 'setenv PATH "/bin:$PATH"' >> ~/.tcshrc @@ -102,6 +138,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.tcshrc ``` @@ -109,7 +148,7 @@
      - Expand for ksh instructions + 展开 ksh 相关说明 ```console $ echo 'export PATH="/bin:$PATH"' >> ~/.profile @@ -119,6 +158,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile ``` @@ -126,7 +168,7 @@
      - Expand for sh instructions + 展开 sh 相关说明 ```console $ echo 'export PATH="/bin:$PATH"' >> ~/.profile @@ -136,6 +178,9 @@ `develop/flutter` folder inside your user directory, you'd run the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可运行: + ```console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile ``` @@ -144,15 +189,26 @@ 1.

      Apply your changes

      +

      应用你的更改

      + To apply this change and get access to the `flutter` tool, close and reopen all open shell sessions in your terminal apps and IDEs. + 要应用此更改并访问 `flutter` 工具, + 请在终端应用和 IDE 中关闭并重新打开所有已打开的 shell 会话。 + 1.

      Validate your setup

      +

      验证你的配置

      + To ensure you successfully added the SDK to your `PATH`, open your preferred terminal with your default shell, then try running the `flutter` and `dart` tools. + 为确保你已成功将 SDK 添加到 `PATH`, + 请使用默认 shell 打开你偏好的终端, + 然后尝试运行 `flutter` 和 `dart` 工具。 + ```console $ flutter --version $ dart --version @@ -161,6 +217,9 @@ If either command isn't found, check out [Flutter installation troubleshooting][troubleshoot]. + 若任一命令未找到, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {: .steps} [troubleshoot]: /install/troubleshoot diff --git a/sites/docs/src/_includes/docs/install/path/macos.md b/sites/docs/src/_includes/docs/install/path/macos.md index 6b14a42db7..2e0b2b22e0 100644 --- a/sites/docs/src/_includes/docs/install/path/macos.md +++ b/sites/docs/src/_includes/docs/install/path/macos.md @@ -2,29 +2,53 @@ The following steps assume you're using the [default shell][zsh-mac] on macOS, Zsh. +以下步骤假定你在 macOS 上使用 +[默认 shell][zsh-mac] Zsh。 + If you use another shell besides Zsh, check out [this tutorial on setting your PATH][other-path]. + +若你使用的不是 Zsh, +请参阅 [这篇设置 PATH 的教程][other-path]。 ::: 1.

      Determine your Flutter SDK installation location

      +

      确定 Flutter SDK 安装位置

      + Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

      Open or create the Zsh environment variable file

      +

      打开或创建 Zsh 环境变量文件

      + If it exists, open the [Zsh environment variable file][zsh-files] `~/.zprofile` in your preferred text editor. If it doesn't exist, create the `~/.zprofile` file. + 若存在,请在你偏好的文本编辑器中打开 + [Zsh 环境变量文件][zsh-files] `~/.zprofile`。 + 若不存在,请创建 `~/.zprofile` 文件。 + 1.

      Add the Flutter SDK bin to your path

      +

      将 Flutter SDK 的 bin 添加到你的 PATH

      + At the end of your `~/.zprofile` file, use the built-in `export` command to update the `PATH` variable to include the `bin` directory of your Flutter installation. + 在 `~/.zprofile` 文件末尾, + 使用内置 `export` 命令更新 `PATH` 变量, + 使其包含 Flutter 安装目录下的 `bin`。 + Replace `` with the path to your Flutter SDK installation. + 将 `` 替换为你的 Flutter SDK 安装路径。 + ```bash export PATH="/bin:$PATH" ``` @@ -33,25 +57,43 @@ check out [this tutorial on setting your PATH][other-path]. `develop/flutter` folder inside your user directory, you'd add the following to the file: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹,可向文件添加: + ```bash export PATH="$HOME/develop/flutter/bin:$PATH" ``` 1.

      Save your changes

      +

      保存你的更改

      + Save, then close, the `~/.zprofile` file you edited. + 保存并关闭你编辑的 `~/.zprofile` 文件。 + 1.

      Apply your changes

      +

      应用你的更改

      + To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. + 要应用此更改并访问 `flutter` 工具, + 请在终端应用和 IDE 中关闭并重新打开所有已打开的 Zsh 会话。 + 1.

      Validate your setup

      +

      验证你的配置

      + To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools. + 为确保你已成功将 SDK 添加到 `PATH`, + 请在你偏好的终端中打开 Zsh 会话, + 然后尝试运行 `flutter` 和 `dart` 工具。 + ```console $ flutter --version $ dart --version @@ -60,6 +102,9 @@ check out [this tutorial on setting your PATH][other-path]. If either command isn't found, check out [Flutter installation troubleshooting][troubleshoot]. + 若任一命令未找到, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {: .steps} [zsh-mac]: https://support.apple.com/en-us/102360 diff --git a/sites/docs/src/_includes/docs/install/path/windows.md b/sites/docs/src/_includes/docs/install/path/windows.md index cd5b797481..12262b452e 100644 --- a/sites/docs/src/_includes/docs/install/path/windows.md +++ b/sites/docs/src/_includes/docs/install/path/windows.md @@ -1,11 +1,17 @@ 1.

      Determine your Flutter SDK installation location

      +

      确定 Flutter SDK 安装位置

      + Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

      Navigate to the environment variables settings

      +

      进入环境变量设置

      + 1. Press Windows + Pause. 按下快捷键 Windows + Pause。 @@ -34,67 +40,115 @@ 1.

      Add the Flutter SDK bin to your path

      +

      将 Flutter SDK 的 bin 添加到你的 PATH

      + 1. In the **User variables for (username)** section of the **Environment Variables** dialog, look for the **Path** entry. + 在 **环境变量** 对话框的 **(用户名)的用户变量** 区域中, + 查找 **Path** 条目。 + 1. If the **Path** entry exists, double-click it. + 若 **Path** 条目已存在,请双击它。 + The **Edit Environment Variable** dialog should open. + 此时应会打开 **编辑环境变量** 对话框。 + 1. Double-click inside an empty row. + 双击空白行。 + 1. Type the path to the `bin` directory of your Flutter installation. + 输入 Flutter 安装目录下 `bin` 的路径。 + For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop\flutter` 文件夹,则应输入: + ```plaintext %USERPROFILE%\develop\flutter\bin ``` 1. Click the Flutter entry you added to select it. + 点击你添加的 Flutter 条目以将其选中。 + 1. Click **Move Up** until the Flutter entry sits at the top of the list. + 点击 **上移**,直到 Flutter 条目位于列表顶部。 + 1. To confirm your changes, click **OK** three times. + 要确认更改,请点击三次 **确定**。 + {: type="a"} 1. If the entry doesn't exist, click **New...**. + 若该条目不存在,请点击 **新建…**。 + The **Edit Environment Variable** dialog should open. + 此时应会打开 **编辑环境变量** 对话框。 + 1. In the **Variable Name** box, type `Path`. + 在 **变量名** 框中输入 `Path`。 + 1. In the **Variable Value** box, type the path to the `bin` directory of your Flutter installation. + 在 **变量值** 框中, + 输入 Flutter 安装目录下 `bin` 的路径。 + For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following: + 例如,若你将 Flutter 下载到用户目录下的 + `develop\flutter` 文件夹,则应输入: + ```plaintext %USERPROFILE%\develop\flutter\bin ``` 1. To confirm your changes, click **OK** three times. + 要确认更改,请点击三次 **确定**。 + {: type="a"} 1.

      Apply your changes

      +

      应用你的更改

      + To apply this change and get access to the `flutter` tool, close and reopen all open command prompts, sessions in your terminal apps, and IDEs. + 要应用此更改并访问 `flutter` 工具, + 请关闭并重新打开所有已打开的命令提示符、 + 终端应用中的会话以及 IDE。 + 1.

      Validate your setup

      +

      验证你的配置

      + To ensure you successfully added the SDK to your `PATH`, open command prompt or your preferred terminal app, then try running the `flutter` and `dart` tools. + 为确保你已成功将 SDK 添加到 `PATH`, + 请打开命令提示符或你偏好的终端应用, + 然后尝试运行 `flutter` 和 `dart` 工具。 + ```console $ flutter --version $ dart --version @@ -103,6 +157,9 @@ If either command isn't found, check out [Flutter installation troubleshooting][troubleshoot]. + 若任一命令未找到, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {: .steps} [troubleshoot]: /install/troubleshoot diff --git a/sites/docs/src/_includes/docs/install/quick.md b/sites/docs/src/_includes/docs/install/quick.md index f650ef92b3..3cb0148f26 100644 --- a/sites/docs/src/_includes/docs/install/quick.md +++ b/sites/docs/src/_includes/docs/install/quick.md @@ -2,50 +2,95 @@ Learn how to use any Code OSS-based editor, such as VS Code, to set up your Flutter development environment and test drive Flutter's developer experience. +了解如何使用基于 Code OSS 的编辑器(例如 VS Code) +配置 Flutter 开发环境并 +试跑 Flutter 的开发体验。 + If you've developed with Flutter before, or you prefer to use a different editor or IDE, you can follow the [custom setup instructions][] instead. -:::note What you'll achieve +若你此前已使用 Flutter 开发过, +或你更倾向使用其他编辑器或 IDE, +可改为按照 [自定义配置说明][custom setup instructions] 操作。 + + +:::note 你将达成的目标 - Install the software prerequisites for Flutter. + + 安装 Flutter 的软件必备项。 + - Use VS Code to download and install Flutter. + + 使用 VS Code 下载并安装 Flutter。 + - Create a new Flutter app from a sample template. + + 从示例模板创建新的 Flutter 应用。 + - Try out Flutter development features like stateful hot reload. + 体验 Flutter 开发功能,例如 stateful hot reload。 + ::: [custom setup instructions]: /install/custom ## Confirm your development platform {: #dev-platform} +## 确认你的开发平台 + The instructions on this page are configured to cover installing and trying out Flutter on a **Windows**{:.selected-os-text} device. +本页说明默认针对在 **Windows**{:.selected-os-text} 设备上安装并试跑 Flutter。 + If you'd like to follow the instructions for a different OS, please select one of the following. +若要查看其他操作系统的说明, +请选择以下选项之一。 + ## Download prerequisite software {: #download-prerequisites} +## 下载必备软件 + For the smoothest Flutter setup, first install the following tools. +为了最顺畅地完成 Flutter 配置, +请先安装以下工具。 + 1.

      Set up Linux support

      +

      配置 Linux 支持

      + If you haven't set up Linux support on your Chromebook before, [Turn on Linux support][chromeos-linux]. + 若你此前未在 Chromebook 上配置 Linux 支持, + 请参阅 [开启 Linux 支持][chromeos-linux]。 + If you've already turned on Linux support, ensure it's up to date following the [Fix problems with Linux][chromeos-linux-update] instructions. + 若你已开启 Linux 支持, + 请按照 [修复 Linux 相关问题][chromeos-linux-update] 的说明确保其为最新状态。 + 1.

      Download and install prerequisite packages

      +

      下载并安装必备 package

      + Using `apt-get` or your preferred installation mechanism, install the latest versions of the following packages: + 使用 `apt-get` 或你偏好的安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -56,6 +101,9 @@ first install the following tools. If you want to use `apt-get`, install these packages using the following commands: + 若要使用 `apt-get`, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -63,26 +111,44 @@ first install the following tools. 1.

      Download and install Visual Studio Code

      +

      下载并安装 Visual Studio Code

      + To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice. + 你也可以安装并使用任何其他支持 VS Code 扩展的 + 基于 Code OSS 的编辑器。 + 若你选择这样做,本文余下部分中的 VS Code + 均指你所选的编辑器。 + {: .steps .chromeos-only} 1.

      Install git

      +

      安装 git

      + **If you already have git installed, skip to the next step: Download and install Visual Studio Code.** + **若你已安装 git,请跳至下一步:下载并安装 Visual Studio Code。** + There are a few ways to install git on your Mac, but the way we recommend is by using XCode. This will be important when you target your builds for iOS or macOS. + 在 Mac 上安装 git 有多种方式, + 但我们推荐使用 XCode。 + 当你面向 iOS 或 macOS 构建时,这一点很重要。 + ```console $ xcode-select --install ``` @@ -91,42 +157,78 @@ first install the following tools. a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**. + 若你尚未安装这些工具, + 应会弹出对话框确认你是否要安装。 + 点击 **Install**,安装完成后点击 **Done**。 + 1.

      Download and install Visual Studio Code

      +

      下载并安装 Visual Studio Code

      + To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice. + 你也可以安装并使用任何其他支持 VS Code 扩展的 + 基于 Code OSS 的编辑器。 + 若你选择这样做,本文余下部分中的 VS Code + 均指你所选的编辑器。 + {: .steps .macos-only} 1.

      Install Git for Windows

      +

      为 Windows 安装 Git

      + Download and install the latest version of [Git for Windows][]. + 下载并安装最新版本的 [Git for Windows][]。 + For help installing or troubleshooting Git, reference the [Git documentation][git-install]. + 有关安装或排查 Git 问题的帮助, + 请参阅 [Git 文档][git-install]。 + 1.

      Download and install Visual Studio Code

      +

      下载并安装 Visual Studio Code

      + To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice. + 你也可以安装并使用任何其他支持 VS Code 扩展的 + 基于 Code OSS 的编辑器。 + 若你选择这样做,本文余下部分中的 VS Code + 均指你所选的编辑器。 + {: .steps .windows-only} 1.

      Download and install prerequisite packages

      +

      下载并安装必备 package

      + Using your preferred package manager or mechanism, install the latest versions of the following packages: + 使用你偏好的 package 管理器或安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -137,6 +239,9 @@ first install the following tools. On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: + 在基于 Debian 且使用 `apt-get` 的发行版(例如 Ubuntu)上, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -144,14 +249,24 @@ first install the following tools. 1.

      Download and install Visual Studio Code

      +

      下载并安装 Visual Studio Code

      + To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice. + 你也可以安装并使用任何其他支持 VS Code 扩展的 + 基于 Code OSS 的编辑器。 + 若你选择这样做,本文余下部分中的 VS Code + 均指你所选的编辑器。 + {: .steps .linux-only} [chromeos-linux]: https://support.google.com/chromebook/answer/9145439 @@ -162,48 +277,90 @@ first install the following tools. ## Install and set up Flutter {: #install} +## 安装并配置 Flutter + Now that you've installed Git and VS Code, follow these steps to use VS Code to install and set up Flutter. -:::note Download manually +现在你已经安装了 Git 和 VS Code, +请按照以下步骤使用 VS Code 安装并配置 Flutter。 + + +:::note 手动下载 If you prefer to manually install Flutter, follow the instructions in [Install Flutter manually][]. + +若你更倾向手动安装 Flutter, +请按照 [手动安装 Flutter][Install Flutter manually] 中的说明操作。 ::: 1.

      Launch VS Code

      +

      启动 VS Code

      + If not already open, open VS Code by searching for it with Spotlight or opening it manually from the directory where it's installed. + 若尚未打开,可通过 Spotlight 搜索或从安装目录手动打开 VS Code。 + 1.

      Add the Flutter extension to VS Code

      +

      向 VS Code 添加 Flutter 扩展

      + To add the Dart and Flutter extensions to VS Code, visit the [Flutter extension's marketplace page][flutter-vscode], then click **Install**. If prompted by your browser, allow it to open VS Code. + 要向 VS Code 添加 Dart 和 Flutter 扩展, + 请访问 [Flutter 扩展的市场页面][flutter-vscode], + 然后点击 **Install**。 + 若浏览器提示,请允许其打开 VS Code。 + 1.

      Install Flutter with VS Code

      +

      使用 VS Code 安装 Flutter

      + 1. Open the command palette in VS Code. + 在 VS Code 中打开命令面板。 + Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. + 依次选择 **View** > **Command Palette**, + 或按 Cmd/Ctrl + + Shift + P。 + 1. In the command palette, type `flutter`. + 在命令面板中输入 `flutter`。 + 1. Select **Flutter: New Project**. + 选择 **Flutter: New Project**。 + 1. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**. + VS Code 会提示你在计算机上定位 Flutter SDK。 + 选择 **Download SDK**。 + 1. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter. + 当 **Select Folder for Flutter SDK** 对话框显示时, + 选择你希望安装 Flutter 的位置。 + 1. Click **Clone Flutter**. + 点击 **Clone Flutter**。 + While downloading Flutter, VS Code displays this pop-up notification: + 下载 Flutter 时,VS Code 会显示以下弹出通知: + ```console Downloading the Flutter SDK. This may take a few minutes. ``` @@ -212,36 +369,64 @@ follow the instructions in [Install Flutter manually][]. If you suspect that the download has hung, click **Cancel** then start the installation again. + 此下载可能需要几分钟。 + 若你怀疑下载已卡住,请点击 **Cancel**, + 然后重新开始安装。 + 1. Click **Add SDK to PATH**. + 点击 **Add SDK to PATH**。 + When successful, a notification displays: + 成功时,会显示以下通知: + ```console The Flutter SDK was added to your PATH ``` 1. VS Code might display a Google Analytics notice. + VS Code 可能会显示 Google Analytics 通知。 + If you agree, click **OK**. + 若你同意,请点击 **OK**。 + 1. To ensure that Flutter is available in all terminals: + 要确保 Flutter 在所有终端中可用: + 1. Close, then reopen all terminal windows. + + 关闭并重新打开所有终端窗口。 + 1. Restart VS Code. + 重启 VS Code。 + {:type="a"} :::note The VS Code setup process might check for Android Studio, which can result in a warning if it's not installed. You can safely ignore this if you're targeting other platforms (like web, iOS, or macOS), as the installation will still succeed. Afterward, run `flutter doctor` to verify your installation. + + VS Code 配置过程可能会检查 Android Studio,若未安装可能会显示警告。 + 若你面向其他平台(例如 Web、iOS 或 macOS),可安全忽略此警告,安装仍会成功。 + 之后请运行 `flutter doctor` 验证你的安装。 ::: 1.

      Troubleshoot installation issues

      +

      排查安装问题

      + If you encounter any issues during installation, check out [Flutter installation troubleshooting][troubleshoot]. + 若安装过程中遇到任何问题, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {:.steps} [Install Flutter manually]: /install/manual diff --git a/sites/docs/src/_includes/docs/main-api.md b/sites/docs/src/_includes/docs/main-api.md index f707b16c79..a37ff51b5c 100644 --- a/sites/docs/src/_includes/docs/main-api.md +++ b/sites/docs/src/_includes/docs/main-api.md @@ -12,6 +12,10 @@ The following link(s) take you to the [latest docs][] on the master channel. You can find the docs for the stable channel at [api.flutter.dev][]. + +以下链接会带你前往 master 渠道的 +[最新文档][latest docs]。 +稳定渠道的文档见 [api.flutter.dev][]。 ::: [api.flutter.dev]: {{site.api}} diff --git a/sites/docs/src/_includes/docs/resource-links/ffi-video-resources.md b/sites/docs/src/_includes/docs/resource-links/ffi-video-resources.md index ce5481a0fd..e578207212 100644 --- a/sites/docs/src/_includes/docs/resource-links/ffi-video-resources.md +++ b/sites/docs/src/_includes/docs/resource-links/ffi-video-resources.md @@ -1,10 +1,19 @@ ## Other Resources +## 其他资源 + To learn more about C interoperability, check out these videos: +若要进一步了解 C 互操作性,可观看以下视频: + - [C interoperability with Dart FFI] + + [使用 Dart FFI 实现 C 互操作性][C interoperability with Dart FFI] + - [How to Use Dart FFI to Build a Retro Audio Player] + [如何使用 Dart FFI 构建复古音频播放器][How to Use Dart FFI to Build a Retro Audio Player] + [C interoperability with Dart FFI]: {{site.yt.watch}}?v=2MMK7YoFgaA [How to Use Dart FFI to Build a Retro Audio Player]: {{site.yt.watch}}?v=05Wn2oM_nWw diff --git a/sites/docs/src/_includes/docs/swift-package-manager/how-to-enable-disable.md b/sites/docs/src/_includes/docs/swift-package-manager/how-to-enable-disable.md index 752fbf3eb9..7cf0d53155 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/how-to-enable-disable.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/how-to-enable-disable.md @@ -1,16 +1,25 @@ ## How to turn on Swift Package Manager +## 如何开启 Swift Package Manager + Flutter's Swift Package Manager support is turned off by default. To turn it on: +默认情况下,Flutter 的 Swift Package Manager 支持处于关闭状态。 +要开启它: + 1. Upgrade to the latest Flutter SDK: + 升级到最新的 Flutter SDK: + ```sh flutter upgrade ``` 1. Turn on the Swift Package Manager feature: + 开启 Swift Package Manager 功能: + ```sh flutter config --enable-swift-package-manager ``` @@ -25,19 +34,41 @@ To use an older Flutter version, you will need to [remove Swift Package Manager integration][removeSPM] from the app. +使用 Flutter CLI 运行应用会 [迁移项目][addSPM] 以添加 +Swift Package Manager 集成。 +这会让你的项目下载 +你的 Flutter 插件所依赖的 Swift 包。 +集成了 Swift Package Manager 的应用需要 Flutter 3.24 或更高版本。 +若要使用较旧的 Flutter 版本, +你需要从应用中 [移除 Swift Package Manager 集成][removeSPM]。 + Flutter falls back to CocoaPods for dependencies that do not support Swift Package Manager yet. +对于尚不支持 Swift +Package Manager 的依赖,Flutter 会回退到 CocoaPods。 + ## How to turn off Swift Package Manager -:::secondary Plugin authors +## 如何关闭 Swift Package Manager + + +:::secondary 插件作者 Plugin authors need to turn on and off Flutter's Swift Package Manager support for testing. App developers do not need to disable Swift Package Manager support, unless they are running into issues. +插件作者需要为测试而开启或关闭 Flutter 的 Swift Package Manager +支持。 +应用开发者无需禁用 Swift Package Manager 支持, +除非遇到问题。 + If you find a bug in Flutter's Swift Package Manager support, [open an issue][]. + +如果你在 Flutter 的 Swift Package Manager 支持中发现 bug, +[提交 issue][open an issue]。 ::: Disabling Swift Package Manager causes Flutter to use CocoaPods for all @@ -47,11 +78,23 @@ To remove Swift Package Manager integration completely from your project, follow the [How to remove Swift Package Manager integration][removeSPM] instructions. +禁用 Swift Package Manager 会导致 Flutter 对所有 +依赖都使用 CocoaPods。 +不过,Swift Package Manager 仍会集成在你的项目中。 +若要从项目中完全移除 Swift Package Manager 集成, +请按照 [如何移除 Swift Package Manager 集成][removeSPM] +说明操作。 + ### Turn off for a single project +### 为单个项目关闭 + In the project's `pubspec.yaml` file, under the `flutter` section, set `enable-swift-package-manager` to `false` in the `config` subsection. +在项目的 `pubspec.yaml` 文件中,于 `flutter` 小节下的 `config` 子小节里, +将 `enable-swift-package-manager` 设为 `false`。 + ```yaml title="pubspec.yaml" # The following section is specific to Flutter packages. flutter: @@ -61,25 +104,41 @@ flutter: This turns off Swift Package Manager for all contributors to this project. -:::note Migrating from deprecated syntax +这会为参与该项目的所有贡献者关闭 Swift Package Manager。 + + +:::note 从弃用语法迁移 If you were previously using `disable-swift-package-manager: true`, update your `pubspec.yaml` to use the new `config` section format shown above. The old syntax is deprecated and will produce an error in Flutter 3.38 and later. + +如果你之前使用的是 `disable-swift-package-manager: true`, +请将 `pubspec.yaml` 更新为上述新的 `config` 小节格式。 +旧语法已弃用,在 Flutter 3.38 及更高版本中将产生错误。 ::: ### Turn off globally for all projects +### 为所有项目全局关闭 + Run the following command: +运行以下命令: + ```sh flutter config --no-enable-swift-package-manager ``` This turns off Swift Package Manager for the current user. +这会为当前用户关闭 Swift Package Manager。 + If a project is incompatible with Swift Package Manager, all contributors need to run this command. +如果某个项目与 Swift Package Manager 不兼容,所有贡献者 +都需要运行此命令。 + [addSPM]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-add-swift-package-manager-integration [removeSPM]: /packages-and-plugins/swift-package-manager/for-app-developers#how-to-remove-swift-package-manager-integration [open an issue]: {{site.github}}/flutter/flutter/issues/new?template=2_bug.yml diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project-manually.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project-manually.md index 6ee3276b80..7c9f97a86a 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project-manually.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project-manually.md @@ -2,59 +2,120 @@ Once you [turn on Swift Package Manager][], the Flutter CLI tries to migrate your project to use Swift Package Manager the next time you run your app using the CLI. +[开启 Swift Package Manager][turn on Swift Package Manager] 后, +Flutter CLI 会在你下次使用 CLI 运行应用时尝试将项目迁移为使用 Swift Package Manager。 + However, the Flutter CLI tool might be unable to migrate your project automatically if there are unexpected modifications. +不过,如果存在意外的修改,Flutter CLI 工具可能无法自动迁移你的项目。 + If the automatic migration fails, use the steps below to add Swift Package Manager integration to a project manually. +如果自动迁移失败,请使用以下步骤手动为项目添加 Swift Package Manager 集成。 + Before migrating manually, [file an issue][]; this helps the Flutter team improve the automatic migration process. Include the error message and, if possible, include a copy of the following files in your issue: +在手动迁移之前,请 [提交 issue][file an issue];这有助于 Flutter 团队改进自动迁移流程。 +请在 issue 中包含错误信息,并尽可能附上 +以下文件的副本: + * `ios/Runner.xcodeproj/project.pbxproj` + * `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` (or the xcsheme for the flavor used) + `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` + (或所用 flavor 对应的 xcscheme) + ### Step 1: Add FlutterGeneratedPluginSwiftPackage Package Dependency {:.no_toc} +### 步骤 1:添加 FlutterGeneratedPluginSwiftPackage 包依赖 {:.no_toc} + 1. Open your app (`ios/Runner.xcworkspace`) in Xcode. + + 在 Xcode 中打开你的应用(`ios/Runner.xcworkspace`)。 + 1. Navigate to **Package Dependencies** for the project. + 导航到项目的 **Package Dependencies**(包依赖)。 + 1. Click the button. + + 点击 按钮。 + 1. In the dialog that opens, click **Add Local...**. + + 在打开的对话框中,点击 **Add Local...**(添加本地…)。 + 1. Navigate to `ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage` and click **Add Package**. + + 导航到 `ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage`, + 然后点击 **Add Package**(添加包)。 + 1. Ensure that it's added to the `Runner` target and click **Add Package**. + 确保已添加到 `Runner` 目标,然后点击 **Add Package**(添加包)。 + 1. Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**. + 确保 `FlutterGeneratedPluginSwiftPackage` 已添加到 **Frameworks, + Libraries, and Embedded Content**(框架、库与嵌入内容)。 + ### Step 2: Add Run Prepare Flutter Framework Script Pre-Action {:.no_toc} +### 步骤 2:添加 Run Prepare Flutter Framework Script 预操作 {:.no_toc} + **The following steps must be completed for each flavor.** +**以下步骤必须针对每个 flavor 完成。** + 1. Go to **Product > Scheme > Edit Scheme**. + + 前往 **Product > Scheme > Edit Scheme**(产品 > 方案 > 编辑方案)。 + 1. Expand the **Build** section in the left side bar. + + 在左侧边栏中展开 **Build**(构建)部分。 + 1. Click **Pre-actions**. + + 点击 **Pre-actions**(预操作)。 + 1. Click the button and select **New Run Script Action** from the menu. + + 点击 按钮, + 从菜单中选择 **New Run Script Action**(新建运行脚本操作)。 + 1. Click the **Run Script** title and change it to: + 点击 **Run Script**(运行脚本)标题并将其改为: + ```plaintext Run Prepare Flutter Framework Script ``` 1. Change the **Provide build settings from** to the `Runner` app. + + 将 **Provide build settings from**(提供构建设置来源)改为 `Runner` 应用。 + 1. Input the following in the text box: + 在文本框中输入以下内容: + ```sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ``` @@ -63,13 +124,23 @@ the following files in your issue: ### Step 3: Run app {:.no_toc} +### 步骤 3:运行应用 {:.no_toc} + 1. Run the app in Xcode. + + 在 Xcode 中运行应用。 + 1. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency. + 确保 **Run Prepare Flutter Framework Script** 作为预操作运行, + 且 `FlutterGeneratedPluginSwiftPackage` 是目标依赖项。 + 1. Ensure that the app runs on the command line with `flutter run`. + 确保应用在命令行中可通过 `flutter run` 运行。 + [turn on Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-on-swift-package-manager [file an issue]: {{site.github}}/flutter/flutter/issues/new?template=2_bug.yml diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project.md index a038c54dfd..765492c62a 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-ios-project.md @@ -3,15 +3,28 @@ your project the next time you run your app using the CLI. This migration updates your Xcode project to use Swift Package Manager to add Flutter plugin dependencies. +[开启 Swift Package Manager][Turn on Swift Package Manager] 后, +Flutter CLI 会在你下次使用 CLI 运行应用时尝试迁移你的项目。 +此次迁移会更新你的 Xcode 项目,使其通过 Swift Package Manager 添加 Flutter 插件依赖。 + To migrate your project: +要迁移你的项目: + 1. [Turn on Swift Package Manager][]. + [开启 Swift Package Manager][Turn on Swift Package Manager]。 + 1. Run the iOS app using the Flutter CLI. + 使用 Flutter CLI 运行 iOS 应用。 + If your iOS project doesn't have Swift Package Manager integration yet, the Flutter CLI tries to migrate your project and outputs something like: + 如果你的 iOS 项目尚未集成 Swift Package Manager, + Flutter CLI 会尝试迁移你的项目,并输出类似以下内容: + ```console $ flutter run Adding Swift Package Manager integration... @@ -21,15 +34,30 @@ To migrate your project: `ios/Runner.xcodeproj/project.pbxproj` and `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` files. + 自动 iOS 迁移会修改 + `ios/Runner.xcodeproj/project.pbxproj` 和 + `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` 文件。 + 1. If the Flutter CLI's automatic migration fails, follow the steps in [add Swift Package Manager integration manually][manualIntegration]. + 如果 Flutter CLI 的自动迁移失败,请按照 + [手动添加 Swift Package Manager 集成][manualIntegration] 中的步骤操作。 + [Optional] To check if your project is migrated: +[可选] 检查你的项目是否已迁移: + 1. Run the app in Xcode. + + 在 Xcode 中运行应用。 + 1. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency. + 确保 **Run Prepare Flutter Framework Script** 作为预操作运行, + 且 `FlutterGeneratedPluginSwiftPackage` 是目标依赖项。 + [Turn on Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-on-swift-package-manager diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project-manually.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project-manually.md index 8b0e623dfa..62ab1a9107 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project-manually.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project-manually.md @@ -2,59 +2,120 @@ Once you [turn on Swift Package Manager][], the Flutter CLI tries to migrate your project to use Swift Package Manager the next time you run your app using the CLI. +[开启 Swift Package Manager][turn on Swift Package Manager] 后, +Flutter CLI 会在你下次使用 CLI 运行应用时尝试将项目迁移为使用 Swift Package Manager。 + However, the Flutter CLI tool might be unable to migrate your project automatically if there are unexpected modifications. +不过,如果存在意外的修改,Flutter CLI 工具可能无法自动迁移你的项目。 + If the automatic migration fails, use the steps below to add Swift Package Manager integration to a project manually. +如果自动迁移失败,请使用以下步骤手动为项目添加 Swift Package Manager 集成。 + Before migrating manually, [file an issue][]; this helps the Flutter team improve the automatic migration process. Include the error message and, if possible, include a copy of the following files in your issue: +在手动迁移之前,请 [提交 issue][file an issue]; +这有助于 Flutter 团队改进自动迁移流程。 +请在 issue 中包含错误信息,并尽可能附上以下文件的副本: + * `macos/Runner.xcodeproj/project.pbxproj` + * `macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` (or the xcscheme for the flavor used) + `macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` + (或所用 flavor 对应的 xcscheme) + ### Step 1: Add FlutterGeneratedPluginSwiftPackage Package Dependency {:.no_toc} +### 步骤 1:添加 FlutterGeneratedPluginSwiftPackage 包依赖 {:.no_toc} + 1. Open your app (`macos/Runner.xcworkspace`) in Xcode. + + 在 Xcode 中打开你的应用(`macos/Runner.xcworkspace`)。 + 1. Navigate to **Package Dependencies** for the project. + 导航到项目的 **Package Dependencies**(包依赖)。 + 1. Click the button. + + 点击 按钮。 + 1. In the dialog that opens, click the **Add Local...**. + + 在打开的对话框中,点击 **Add Local...**(添加本地…)。 + 1. Navigate to `macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage` and click the **Add Package**. + + 导航到 `macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage`, + 然后点击 **Add Package**(添加包)。 + 1. Ensure that it's added to the Runner Target and click **Add Package**. + 确保已添加到 Runner 目标,然后点击 **Add Package**(添加包)。 + 1. Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**. + 确保 `FlutterGeneratedPluginSwiftPackage` 已添加到 **Frameworks, + Libraries, and Embedded Content**(框架、库与嵌入内容)。 + ### Step 2: Add Run Prepare Flutter Framework Script Pre-Action {:.no_toc} +### 步骤 2:添加 Run Prepare Flutter Framework Script 预操作 {:.no_toc} + **The following steps must be completed for each flavor.** +**以下步骤必须针对每个 flavor 完成。** + 1. Go to **Product > Scheme > Edit Scheme**. + + 前往 **Product > Scheme > Edit Scheme**(产品 > 方案 > 编辑方案)。 + 1. Expand the **Build** section in the left side bar. + + 在左侧边栏中展开 **Build**(构建)部分。 + 1. Click **Pre-actions**. + + 点击 **Pre-actions**(预操作)。 + 1. Click the button and select **New Run Script Action** from the menu. + + 点击 按钮, + 从菜单中选择 **New Run Script Action**(新建运行脚本操作)。 + 1. Click the **Run Script** title and change it to: + 点击 **Run Script**(运行脚本)标题并将其改为: + ```plaintext Run Prepare Flutter Framework Script ``` 1. Change the **Provide build settings from** to the `Runner` target. + + 将 **Provide build settings from**(提供构建设置来源)改为 `Runner` 目标。 + 1. Input the following in the text box: + 在文本框中输入以下内容: + ```sh "$FLUTTER_ROOT"/packages/flutter_tools/bin/macos_assemble.sh prepare ``` @@ -63,13 +124,23 @@ the following files in your issue: ### Step 3: Run app {:.no_toc} +### 步骤 3:运行应用 {:.no_toc} + 1. Run the app in Xcode. + + 在 Xcode 中运行应用。 + 1. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency. + 确保 **Run Prepare Flutter Framework Script** 作为预操作运行, + 且 `FlutterGeneratedPluginSwiftPackage` 是目标依赖项。 + 1. Ensure that the app runs on the command line with `flutter run`. + 确保应用在命令行中可通过 `flutter run` 运行。 + [turn on Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-on-swift-package-manager [file an issue]: {{site.github}}/flutter/flutter/issues/new?template=2_bug.yml diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project.md index 6aeeb1c9a6..ce044870b7 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-macos-project.md @@ -3,15 +3,29 @@ your project the next time you run your app using the CLI. This migration updates your Xcode project to use Swift Package Manager to add Flutter plugin dependencies. +[开启 Swift Package Manager][Turn on Swift Package Manager] 后,Flutter CLI 会在你下次使用 CLI +运行应用时尝试迁移你的项目。 +此次迁移会更新你的 Xcode 项目,使其通过 Swift Package Manager +添加 Flutter 插件依赖。 + To migrate your project: +要迁移你的项目: + 1. [Turn on Swift Package Manager][]. + [开启 Swift Package Manager][Turn on Swift Package Manager]。 + 1. Run the macOS app using the Flutter CLI. + 使用 Flutter CLI 运行 macOS 应用。 + If your macOS project doesn't have Swift Package Manager integration yet, the Flutter CLI tries to migrate your project and outputs something like: + 如果你的 macOS 项目尚未集成 Swift Package Manager, + Flutter CLI 会尝试迁移你的项目,并输出类似以下内容: + ```console $ flutter run -d macos Adding Swift Package Manager integration... @@ -21,15 +35,30 @@ To migrate your project: `macos/Runner.xcodeproj/project.pbxproj` and `macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` files. + 自动 iOS 迁移会修改 + `macos/Runner.xcodeproj/project.pbxproj` 和 + `macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` 文件。 + 1. If the Flutter CLI's automatic migration fails, follow the steps in [add Swift Package Manager integration manually][manualIntegration]. + 如果 Flutter CLI 的自动迁移失败,请按照 + [手动添加 Swift Package Manager 集成][manualIntegration] 中的步骤操作。 + [Optional] To check if your project is migrated: +[可选] 检查你的项目是否已迁移: + 1. Run the app in Xcode. + + 在 Xcode 中运行应用。 + 1. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency. + 确保 **Run Prepare Flutter Framework Script** 作为预操作运行, + 且 `FlutterGeneratedPluginSwiftPackage` 是目标依赖项。 + [Turn on Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-on-swift-package-manager diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-objective-c-plugin.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-objective-c-plugin.md index 6636474b8a..750cb8cb25 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-objective-c-plugin.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-objective-c-plugin.md @@ -1,12 +1,21 @@ Replace `plugin_name` throughout this guide with the name of your plugin. The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. +在本指南全文将 `plugin_name` 替换为你的插件名称。 +以下示例使用 `ios`,请酌情将 `ios` 替换为 `macos`/`darwin`。 + 1. [Turn on the Swift Package Manager feature][enableSPM]. + [开启 Swift Package Manager 功能][enableSPM]。 + 1. Start by creating a directory under the `ios`, `macos`, and/or `darwin` directories. Name this new directory the name of the platform package. + 首先在 `ios`、`macos` 和/或 `darwin` + 目录下创建一个目录。 + 将该新目录命名为平台包的名称。 + - plugin_name/ @@ -18,18 +27,43 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Within this new directory, create the following files/directories: + 在此新目录中,创建以下文件/目录: + - `Package.swift` (file) + + `Package.swift`(文件) + - `Sources` (directory) + + `Sources`(目录) + - `Sources/plugin_name` (directory) + + `Sources/plugin_name`(目录) + - `Sources/plugin_name/include` (directory) + + `Sources/plugin_name/include`(目录) + - `Sources/plugin_name/include/plugin_name` (directory) + + `Sources/plugin_name/include/plugin_name`(目录) + - `Sources/plugin_name/include/plugin_name/.gitkeep` (file) + + `Sources/plugin_name/include/plugin_name/.gitkeep`(文件) + - This file ensures the directory is committed. You can remove the `.gitkeep` file if other files are added to the directory. + 此文件确保目录会被提交。 + 如果向该目录添加了其他文件,可以删除 `.gitkeep` 文件。 + Your plugin should look like: + 你的插件结构应如下所示: + - plugin_name/ @@ -44,6 +78,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Use the following template in the `Package.swift` file: + 在 `Package.swift` 文件中使用以下模板: + ```swift title="Package.swift" // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -94,6 +130,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update the [supported platforms][] in your `Package.swift` file. + 在 `Package.swift` 文件中更新 [支持的平台][supported platforms]。 + ```swift title="Package.swift" platforms: [ // TODO: Update the platforms your plugin supports. @@ -108,6 +146,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update the package, library, and target names in your `Package.swift` file. + 在 `Package.swift` 文件中更新包、库和目标名称。 + ```swift title="Package.swift" let package = Package( // TODO: Update your plugin name. @@ -151,12 +191,19 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. :::note If the plugin name contains `_`, the library name must be a `-` separated version of the plugin name. + + 如果插件名称包含 `_`,库名称必须是插件名称用 `-` 分隔 + 后的形式。 ::: 1. If your plugin has a [`PrivacyInfo.xcprivacy` file][], move it to `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy` and uncomment the resource in the `Package.swift` file. + 如果你的插件有 [`PrivacyInfo.xcprivacy` 文件][`PrivacyInfo.xcprivacy` file],请将其移动到 + `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy`,并在 + `Package.swift` 文件中取消注释该资源。 + ```swift title="Package.swift" resources: [ // TODO: If your plugin requires a privacy manifest @@ -178,32 +225,64 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. For more instructions, see [https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package). + 将 `ios/Assets` 中的资源文件移动到 + `ios/plugin_name/Sources/plugin_name`(或其子目录)。 + 如适用,将资源文件添加到 `Package.swift` 文件中。 + 更多说明请参阅 + [https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package)。 + 1. Move any public headers from `ios/Classes` to `ios/plugin_name/Sources/plugin_name/include/plugin_name`. + 将 `ios/Classes` 中的公共头文件移动到 + `ios/plugin_name/Sources/plugin_name/include/plugin_name`。 + * If you're unsure which headers are public, check your `podspec` file's [`public_header_files`][] attribute. If this attribute isn't specified, all of your headers were public. You should consider whether you want all of your headers to be public. + 如果不确定哪些头文件是公共的,请检查 `podspec` 文件中的 + [`public_header_files`][] 属性。 + 如果未指定该属性,则所有头文件都是公共的。 + 你应斟酌是否希望所有头文件都是公共的。 + * The `pluginClass` defined in your `pubspec.yaml` file must be public and within this directory. + 在 `pubspec.yaml` 文件中定义的 `pluginClass` 必须是公共的,且 + 位于此目录中。 + 1. Handling `modulemap`. + 处理 `modulemap`。 + Skip this step if your plugin does not have a `modulemap`. + 如果你的插件没有 `modulemap`,请跳过此步骤。 + If you're using a `modulemap` for CocoaPods to create a Test submodule, consider removing it for Swift Package Manager. Note that this makes all public headers available through the module. + 如果你为 CocoaPods 使用 `modulemap` 来创建 Test 子模块, + 请考虑为 Swift Package Manager 将其移除。 + 请注意,这会使所有公共头文件通过该模块可用。 + To remove the `modulemap` for Swift Package Manager but keep it for CocoaPods, exclude the `modulemap` and umbrella header in the plugin's `Package.swift` file. + 若要为 Swift Package Manager 移除 `modulemap` 但为 + CocoaPods 保留,请在插件的 + `Package.swift` 文件中排除 `modulemap` 和 umbrella 头文件。 + The example below assumes the `modulemap` and umbrella header are located in the `ios/plugin_name/Sources/plugin_name/include` directory. + 以下示例假定 `modulemap` 和 umbrella 头文件位于 + `ios/plugin_name/Sources/plugin_name/include` 目录。 + ```swift title="Package.swift" diff .target( name: "plugin_name", @@ -214,6 +293,9 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. If you want to keep your unit tests compatible with both CocoaPods and Swift Package Manager, you can try the following: + 如果你希望单元测试同时兼容 CocoaPods 和 + Swift Package Manager,可以尝试以下做法: + ```objc title="Tests/TestFile.m" diff @import plugin_name; - @import plugin_name.Test; @@ -225,19 +307,34 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. If you would like to use a custom `modulemap` with your Swift package, refer to [Swift Package Manager's documentation][]. + 如果你希望将自定义 `modulemap` 用于 Swift 包, + 请参阅 [Swift Package Manager 文档][Swift Package Manager's documentation]。 + 1. Move all remaining files from `ios/Classes` to `ios/plugin_name/Sources/plugin_name`. + 将 `ios/Classes` 中所有剩余文件移动到 + `ios/plugin_name/Sources/plugin_name`。 + 1. The `ios/Assets`, `ios/Resources`, and `ios/Classes` directories should now be empty and can be deleted. + `ios/Assets`、`ios/Resources` 和 `ios/Classes` 目录现在应 + 为空,可以删除。 + 1. If your header files are no longer in the same directory as your implementation files, you should update your import statements. + 如果头文件不再与实现文件位于同一目录,你应更新 import 语句。 + For example, imagine the following migration: + 例如,假设进行以下迁移: + * Before: + 迁移前: + ```plaintext ios/Classes/ ├── PublicHeaderFile.h @@ -246,6 +343,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. * After: + 迁移后: + ```plaintext highlightLines=2 ios/plugin_name/Sources/plugin_name/ └── include/plugin_name/ @@ -256,6 +355,9 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. In this example, the import statements in `ImplementationFile.m` should be updated: + 在此示例中,应更新 `ImplementationFile.m` 中的 + import 语句: + ```objc title="Sources/plugin_name/ImplementationFile.m" diff - #import "PublicHeaderFile.h" + #import "./include/plugin_name/PublicHeaderFile.h" @@ -263,6 +365,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. If your plugin uses [Pigeon][], update your Pigeon input file. + 如果你的插件使用 [Pigeon][],请更新 Pigeon 输入文件。 + ```dart title="pigeons/messages.dart" diff javaOptions: JavaOptions(), - objcHeaderOut: 'ios/Classes/messages.g.h', @@ -276,6 +380,10 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. `objcSourceOut`, you can change the `#import` using `ObjcOptions.headerIncludePath`: + 如果 `objcHeaderOut` 文件不再与 + `objcSourceOut` 位于同一目录,可以使用 + `ObjcOptions.headerIncludePath` 更改 `#import`: + ```dart title="pigeons/messages.dart" diff javaOptions: JavaOptions(), - objcHeaderOut: 'ios/Classes/messages.g.h', @@ -290,29 +398,52 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. Run Pigeon to re-generate its code with the latest configuration. + 运行 Pigeon,以最新配置重新生成代码。 + 1. Update your `Package.swift` file with any customizations you might need. + 根据需要进行自定义,更新 `Package.swift` 文件。 + 1. Open the `ios/plugin_name/` directory in Xcode. + 在 Xcode 中打开 `ios/plugin_name/` 目录。 + 1. In Xcode, open your `Package.swift` file. Verify Xcode doesn't produce any warnings or errors for this file. + 在 Xcode 中打开 `Package.swift` 文件。 + 确认 Xcode 不会对此文件产生警告或错误。 + :::tip If Xcode doesn't show any files, quit Xcode (**Xcode > Quit Xcode**) and reopen. + 如果 Xcode 未显示任何文件,请退出 Xcode(**Xcode > Quit Xcode**)并 + 重新打开。 + If Xcode doesn't update after you make a change, try clicking **File > Packages > Reset Package Caches**. + + 如果更改后 Xcode 未更新,请尝试点击 + **File > Packages > Reset Package Caches**。 ::: 1. If your `ios/plugin_name.podspec` file has [CocoaPods `dependency`][]s, add the corresponding [Swift Package Manager dependencies][] to your `Package.swift` file. + 如果 `ios/plugin_name.podspec` 文件包含 [CocoaPods `dependency`][], + 请将对应的 [Swift Package Manager 依赖][Swift Package Manager dependencies] 添加到 + `Package.swift` 文件。 + 1. If your package must be linked explicitly `static` or `dynamic` ([not recommended by Apple][]), update the [Product][] to define the type: + 如果包必须显式以 `static` 或 `dynamic` 链接 + ([Apple 不推荐][not recommended by Apple]),请更新 [Product][] 以定义 + 类型: + ```swift title="Package.swift" products: [ .library(name: "plugin-name", type: .static, targets: ["plugin_name"]) @@ -323,13 +454,22 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. `Package.swift` file, see [https://developer.apple.com/documentation/packagedescription](https://developer.apple.com/documentation/packagedescription). + 进行其他自定义。有关如何编写 + `Package.swift` 文件的更多信息,请参阅 + [https://developer.apple.com/documentation/packagedescription](https://developer.apple.com/documentation/packagedescription)。 + :::tip If you add targets to your `Package.swift` file, use unique names. This avoids conflicts with targets from other packages. + + 如果向 `Package.swift` 文件添加目标,请使用唯一名称。 + 这可避免与其他包中的目标冲突。 ::: 1. Update your `ios/plugin_name.podspec` to point to new paths. + 更新 `ios/plugin_name.podspec`,使其指向新路径。 + ```ruby title="ios/plugin_name.podspec" diff - s.source_files = 'Classes/**/*.{h,m}' - s.public_header_files = 'Classes/**/*.h' @@ -343,6 +483,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update loading of resources from bundle to use `SWIFTPM_MODULE_BUNDLE`: + 更新从 bundle 加载资源的方式,改用 `SWIFTPM_MODULE_BUNDLE`: + ```objc #if SWIFT_PACKAGE NSBundle *bundle = SWIFTPM_MODULE_BUNDLE; @@ -357,12 +499,21 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. (either [defined in the `Package.swift` file][Bundling resources] or [automatically included by Xcode][Xcode resource detection]). Otherwise, using `SWIFTPM_MODULE_BUNDLE` results in an error. + + 仅当存在实际资源时,`SWIFTPM_MODULE_BUNDLE` 才有效 + (在 [`Package.swift` 文件中定义][Bundling resources] 或 + [由 Xcode 自动包含][Xcode resource detection])。 + 否则,使用 `SWIFTPM_MODULE_BUNDLE` 会导致错误。 ::: 1. If your `ios/plugin_name/Sources/plugin_name/include` directory only contains a `.gitkeep`, you'll want update your `.gitignore` to include the following: + 如果 `ios/plugin_name/Sources/plugin_name/include` 目录仅 + 包含 `.gitkeep`,你需要更新 `.gitignore` 以包含 + 以下内容: + ```text title=".gitignore" !.gitkeep ``` @@ -370,36 +521,53 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. Run `flutter pub publish --dry-run` to ensure the `include` directory is published. + 运行 `flutter pub publish --dry-run`,确保 `include` 目录 + 会被发布。 + 1. Commit your plugin's changes to your version control system. + 将插件的更改提交到版本控制系统。 + 1. Verify the plugin still works with CocoaPods. + 验证插件在 CocoaPods 下仍能正常工作。 + 1. Turn off Swift Package Manager: + 关闭 Swift Package Manager: + ```sh flutter config --no-enable-swift-package-manager ``` 1. Navigate to the plugin's example app. + 进入插件的示例应用目录。 + ```sh cd path/to/plugin/example/ ``` 1. Ensure the plugin's example app builds and runs. + 确保插件的示例应用能构建并运行。 + ```sh flutter run ``` 1. Navigate to the plugin's top-level directory. + 进入插件的顶层目录。 + ```sh cd path/to/plugin/ ``` 1. Run CocoaPods validation lints: + 运行 CocoaPods 验证 lint: + ```sh pod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries ``` @@ -410,20 +578,28 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Verify the plugin works with Swift Package Manager. + 验证插件在 Swift Package Manager 下能正常工作。 + 1. Turn on Swift Package Manager: + 开启 Swift Package Manager: + ```sh flutter config --enable-swift-package-manager ``` 1. Navigate to the plugin's example app. + 进入插件的示例应用目录。 + ```sh cd path/to/plugin/example/ ``` 1. Ensure the plugin's example app builds and runs. + 确保插件的示例应用能构建并运行。 + ```sh flutter run ``` @@ -433,26 +609,46 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. Swift Package Manager feature turned on migrates the project to add Swift Package Manager integration. + 在开启 Swift Package Manager 功能的情况下, + 使用 Flutter CLI 运行插件的示例应用会迁移项目以添加 Swift Package Manager 集成。 + This raises the example app's Flutter SDK requirement to version 3.24 or higher. + 这会将示例应用的 Flutter SDK 要求提高到 3.24 或更高版本。 + If you'd like to run the example app using an older Flutter SDK version, do not commit the migration's changes to your version control system. If needed, you can always [undo the Swift Package Manager migration][removeSPM]. + + 如果你想使用较旧的 Flutter SDK 版本运行示例应用, + 请勿将迁移产生的更改提交到版本控制系统。 + 如有需要,你始终可以 + [撤销 Swift Package Manager 迁移][removeSPM]。 ::: 1. Open the plugin's example app in Xcode. Ensure that **Package Dependencies** shows in the left **Project Navigator**. + 在 Xcode 中打开插件的示例应用。 + 确保左侧 **Project Navigator**(项目导航器)中显示 **Package Dependencies**(包依赖)。 + 1. Verify tests pass. + 验证测试通过。 + * **If your plugin has native unit tests (XCTest), make sure you also [update unit tests in the plugin's example app][].** + **如果你的插件有原生单元测试(XCTest),请确保你也 + [更新了插件示例应用中的单元测试][update unit tests in the plugin's example app]。** + * Follow instructions for [testing plugins][]. + 按照 [测试插件][testing plugins] 说明操作。 + [enableSPM]: /packages-and-plugins/swift-package-manager/for-plugin-authors#how-to-turn-on-swift-package-manager [`PrivacyInfo.xcprivacy` file]: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files [`public_header_files`]: https://guides.cocoapods.org/syntax/podspec.html#public_header_files diff --git a/sites/docs/src/_includes/docs/swift-package-manager/migrate-swift-plugin.md b/sites/docs/src/_includes/docs/swift-package-manager/migrate-swift-plugin.md index d0f5dc223c..b47c37f44f 100644 --- a/sites/docs/src/_includes/docs/swift-package-manager/migrate-swift-plugin.md +++ b/sites/docs/src/_includes/docs/swift-package-manager/migrate-swift-plugin.md @@ -1,12 +1,21 @@ Replace `plugin_name` throughout this guide with the name of your plugin. The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. +在本指南全文将 `plugin_name` 替换为你的插件名称。 +以下示例使用 `ios`,请酌情将 `ios` 替换为 `macos`/`darwin`。 + 1. [Turn on the Swift Package Manager feature][enableSPM]. + [开启 Swift Package Manager 功能][enableSPM]。 + 1. Start by creating a directory under the `ios`, `macos`, and/or `darwin` directories. Name this new directory the name of the platform package. + 首先在 `ios`、`macos` 和/或 `darwin` + 目录下创建一个目录。 + 将该新目录命名为平台包的名称。 + - plugin_name/ @@ -18,12 +27,24 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Within this new directory, create the following files/directories: + 在此新目录中,创建以下文件/目录: + - `Package.swift` (file) + + `Package.swift`(文件) + - `Sources` (directory) + + `Sources`(目录) + - `Sources/plugin_name` (directory) + `Sources/plugin_name`(目录) + Your plugin should look like: + 你的插件结构应如下所示: + - plugin_name/ @@ -38,6 +59,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Use the following template in the `Package.swift` file: + 在 `Package.swift` 文件中使用以下模板: + ```swift title="Package.swift" // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -88,6 +111,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update the [supported platforms][] in your `Package.swift` file. + 在 `Package.swift` 文件中更新 [支持的平台][supported platforms]。 + ```swift title="Package.swift" platforms: [ // TODO: Update the platforms your plugin supports. @@ -102,6 +127,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update the package, library, and target names in your `Package.swift` file. + 在 `Package.swift` 文件中更新包、库和目标名称。 + ```swift title="Package.swift" let package = Package( // TODO: Update your plugin name. @@ -141,12 +168,19 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. :::note If the plugin name contains `_`, the library name must be a `-` separated version of the plugin name. + + 如果插件名称包含 `_`,库名称必须是插件名称用 `-` 分隔 + 后的形式。 ::: 1. If your plugin has a [`PrivacyInfo.xcprivacy` file][], move it to `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy` and uncomment the resource in the `Package.swift` file. + 如果你的插件有 [`PrivacyInfo.xcprivacy` 文件][`PrivacyInfo.xcprivacy` file],请将其移动到 + `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy`,并在 + `Package.swift` 文件中取消注释该资源。 + ```swift title="Package.swift" resources: [ // TODO: If your plugin requires a privacy manifest @@ -168,12 +202,24 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. For more instructions, see [https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package). + 将 `ios/Assets` 中的资源文件移动到 + `ios/plugin_name/Sources/plugin_name`(或其子目录)。 + 如适用,将资源文件添加到 `Package.swift` 文件中。 + 更多说明请参阅 + [https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package)。 + 1. Move all files from `ios/Classes` to `ios/plugin_name/Sources/plugin_name`. + 将 `ios/Classes` 中的所有文件移动到 `ios/plugin_name/Sources/plugin_name`。 + 1. **New in Flutter 3.41!** Add the FlutterFramework as a dependency and update Dart/Flutter version. + **Flutter 3.41 新增!** 将 FlutterFramework 添加为依赖并更新 Dart/Flutter 版本。 + Update `Package.swift` to include `FlutterFramework`: + 更新 `Package.swift` 以包含 `FlutterFramework`: + ```swift title="Package.swift" dependencies: [ [!.package(name: "FlutterFramework", path: "../FlutterFramework")!] @@ -189,6 +235,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. In `pubspec.yaml`, update versions to: + 在 `pubspec.yaml` 中,将版本更新为: + ```yaml title="pubspec.yaml" environment: sdk: ^3.11.0 @@ -198,8 +246,12 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. The `ios/Assets`, `ios/Resources`, and `ios/Classes` directories should now be empty and can be deleted. + `ios/Assets`、`ios/Resources` 和 `ios/Classes` 目录现在应为空,可以删除。 + 1. If your plugin uses [Pigeon][], update your Pigeon input file. + 如果你的插件使用 [Pigeon][],请更新 Pigeon 输入文件。 + ```dart title="pigeons/messages.dart" diff kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', @@ -211,27 +263,48 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update your `Package.swift` file with any customizations you might need. + 根据需要进行自定义,更新 `Package.swift` 文件。 + 1. Open the `ios/plugin_name/` directory in Xcode. + 在 Xcode 中打开 `ios/plugin_name/` 目录。 + 1. In Xcode, open your `Package.swift` file. Verify Xcode doesn't produce any warnings or errors for this file. + 在 Xcode 中打开 `Package.swift` 文件。 + 确认 Xcode 不会对此文件产生警告或错误。 + :::tip If Xcode doesn't show any files, quit Xcode (**Xcode > Quit Xcode**) and reopen. + 如果 Xcode 未显示任何文件,请退出 Xcode(**Xcode > Quit Xcode**)并 + 重新打开。 + If Xcode doesn't update after you make a change, try clicking **File > Packages > Reset Package Caches**. + + 如果更改后 Xcode 未更新,请尝试点击 + **File > Packages > Reset Package Caches**。 ::: 1. If your `ios/plugin_name.podspec` file has [CocoaPods `dependency`][]s, add the corresponding [Swift Package Manager dependencies][] to your `Package.swift` file. + 如果 `ios/plugin_name.podspec` 文件包含 [CocoaPods `dependency`][], + 请将对应的 [Swift Package Manager 依赖][Swift Package Manager dependencies] 添加到 + `Package.swift` 文件。 + 1. If your package must be linked explicitly `static` or `dynamic` ([not recommended by Apple][]), update the [Product][] to define the type: + 如果包必须显式以 `static` 或 `dynamic` 链接 + ([Apple 不推荐][not recommended by Apple]),请更新 [Product][] 以定义 + 类型: + ```swift title="Package.swift" products: [ .library(name: "plugin-name", type: .static, targets: ["plugin_name"]) @@ -242,13 +315,22 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. `Package.swift` file, see [https://developer.apple.com/documentation/packagedescription](https://developer.apple.com/documentation/packagedescription). + 进行其他自定义。有关如何编写 + `Package.swift` 文件的更多信息,请参阅 + [https://developer.apple.com/documentation/packagedescription](https://developer.apple.com/documentation/packagedescription)。 + :::tip If you add targets to your `Package.swift` file, use unique names. This avoids conflicts with targets from other packages. + + 如果向 `Package.swift` 文件添加目标,请使用唯一名称。 + 这可避免与其他包中的目标冲突。 ::: 1. Update your `ios/plugin_name.podspec` to point to new paths. + 更新 `ios/plugin_name.podspec`,使其指向新路径。 + ```ruby title="ios/plugin_name.podspec" diff - s.source_files = 'Classes/**/*.swift' - s.resource_bundles = {'plugin_name_privacy' => ['Resources/PrivacyInfo.xcprivacy']} @@ -258,6 +340,8 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Update loading of resources from bundle to use [`Bundle.module`][]. + 更新从 bundle 加载资源的方式,改用 [`Bundle.module`][]。 + ```swift #if SWIFT_PACKAGE let settingsURL = Bundle.module.url(forResource: "image", withExtension: "jpg") @@ -271,11 +355,19 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. [defined in the `Package.swift` file][Bundling resources] or [automatically included by Xcode][Xcode resource detection]. Otherwise, using `Bundle.module` results in an error. + + 仅当存在资源时,`Bundle.module` 才有效 + (在 [`Package.swift` 文件中定义][Bundling resources] 或 + [由 Xcode 自动包含][Xcode resource detection])。 + 否则,使用 `Bundle.module` 会导致错误。 ::: 1. If your `.gitignore` doesn't include `.build/` and `.swiftpm/` directories, you'll want to update your `.gitignore` to include: + 如果 `.gitignore` 未包含 `.build/` 和 `.swiftpm/` 目录, + 你需要更新 `.gitignore` 以包含: + ```text title=".gitignore" .build/ .swiftpm/ @@ -283,34 +375,48 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. Commit your plugin's changes to your version control system. + 将插件的更改提交到版本控制系统。 + 1. Verify the plugin still works with CocoaPods. + 验证插件在 CocoaPods 下仍能正常工作。 + 1. Turn off Swift Package Manager. + 关闭 Swift Package Manager: + ```sh flutter config --no-enable-swift-package-manager ``` 1. Navigate to the plugin's example app. + 进入插件的示例应用目录。 + ```sh cd path/to/plugin/example/ ``` 1. Ensure the plugin's example app builds and runs. + 确保插件的示例应用能构建并运行。 + ```sh flutter run ``` 1. Navigate to the plugin's top-level directory. + 进入插件的顶层目录。 + ```sh cd path/to/plugin/ ``` 1. Run CocoaPods validation lints. + 运行 CocoaPods 验证 lint: + ```sh pod lib lint ios/plugin_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries ``` @@ -321,20 +427,28 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. 1. Verify the plugin works with Swift Package Manager. + 验证插件在 Swift Package Manager 下能正常工作。 + 1. Turn on Swift Package Manager. + 开启 Swift Package Manager: + ```sh flutter config --enable-swift-package-manager ``` 1. Navigate to the plugin's example app. + 进入插件的示例应用目录。 + ```sh cd path/to/plugin/example/ ``` 1. Ensure the plugin's example app builds and runs. + 确保插件的示例应用能构建并运行。 + ```sh flutter run ``` @@ -344,26 +458,46 @@ The example below uses `ios`, replace `ios` with `macos`/`darwin` as applicable. Swift Package Manager feature turned on migrates the project to add Swift Package Manager integration. + 在开启 Swift Package Manager 功能的情况下, + 使用 Flutter CLI 运行插件的示例应用会迁移项目以添加 Swift Package Manager 集成。 + This raises the example app's Flutter SDK requirement to version 3.24 or higher. + 这会将示例应用的 Flutter SDK 要求提高到 3.24 或更高版本。 + If you'd like to run the example app using an older Flutter SDK version, do not commit the migration's changes to your version control system. If needed, you can always [undo the Swift Package Manager migration][removeSPM]. + + 如果你想使用较旧的 Flutter SDK 版本运行示例应用, + 请勿将迁移产生的更改提交到版本控制系统。 + 如有需要,你始终可以 + [撤销 Swift Package Manager 迁移][removeSPM]。 ::: 1. Open the plugin's example app in Xcode. Ensure that **Package Dependencies** shows in the left **Project Navigator**. + 在 Xcode 中打开插件的示例应用。 + 确保左侧 **Project Navigator**(项目导航器)中显示 **Package Dependencies**(包依赖)。 + 1. Verify tests pass. + 验证测试通过。 + * **If your plugin has native unit tests (XCTest), make sure you also [update unit tests in the plugin's example app][].** + **如果你的插件有原生单元测试(XCTest),请确保你也 + [更新了插件示例应用中的单元测试][update unit tests in the plugin's example app]。** + * Follow instructions for [testing plugins][]. + 按照 [测试插件][testing plugins] 说明操作。 + [enableSPM]: /packages-and-plugins/swift-package-manager/for-plugin-authors#how-to-turn-on-swift-package-manager [`PrivacyInfo.xcprivacy` file]: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files [Pigeon]: https://pub.dev/packages/pigeon diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-reload.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-reload.md index 33786b5e6e..9eb1c04dd4 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-reload.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-reload.md @@ -1 +1 @@ -![Small yellow lightning bolt that indicates reloading the UI of a Flutter app without resetting any state values](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-reload.png) +![小型黄色闪电图标,表示在不重置任何状态值的情况下对 Flutter 应用进行热重载](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-reload.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-restart.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-restart.md index ece267dd14..ecf54c8533 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-restart.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/hot-restart.md @@ -1 +1 @@ -![Small green almost circular arrow that indicates reloading the UI of a Flutter app and resetting any state values](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-restart.png) +![小型绿色近似圆形箭头图标,表示热重启 Flutter 应用并重置任何状态值](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-restart.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/inspector.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/inspector.md index 7a44518fc2..c7f144c1a1 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/inspector.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/inspector.md @@ -1 +1 @@ -![Small blue magnifying class with the Flutter logo inside it that opens the Widget inspector](/assets/images/docs/testing/debugging/vscode-ui/icons/inspector.png) +![小型蓝色放大镜(内含 Flutter 标志)图标,用于打开 widget 检查器](/assets/images/docs/testing/debugging/vscode-ui/icons/inspector.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/pause.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/pause.md index f4e51c7a59..e4ef5a3456 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/pause.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/pause.md @@ -1 +1 @@ -![Small blue double vertical line that indicates pausing the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/pause.png) +![小型蓝色双竖线图标,表示暂停 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/pause.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/play.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/play.md index f9489ceeb7..883d167705 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/play.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/play.md @@ -1 +1 @@ -![Small blue vertical line with a blue triangle that indicates playing or resuming the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/play-or-resume.png) +![小型蓝色竖线与蓝色三角图标,表示播放或恢复 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/play-or-resume.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-into.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-into.md index 1dcd598d19..02019bdc3d 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-into.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-into.md @@ -1 +1 @@ -![Small blue downward arrow over a blue circle that indicates going into the next function in a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-into.png) +![小型蓝色向下箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中进入下一个函数](/assets/images/docs/testing/debugging/vscode-ui/icons/step-into.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-out.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-out.md index 827bd97a26..bf7f530000 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-out.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-out.md @@ -1 +1 @@ -![Small blue upward arrow over a blue circle that indicates exiting the current function after one passthrough in a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-out.png) +![小型蓝色向上箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中单次执行后退出当前函数](/assets/images/docs/testing/debugging/vscode-ui/icons/step-out.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-over.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-over.md index b0ac5b16b4..3b0ed135a5 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/step-over.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/step-over.md @@ -1 +1 @@ -![Small blue arched arrow over a blue circle that indicates skipping the current block or statement in the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-over.png) +![小型蓝色拱形箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中跳过当前块或语句](/assets/images/docs/testing/debugging/vscode-ui/icons/step-over.png) diff --git a/sites/docs/src/_includes/docs/vscode-flutter-bar/stop.md b/sites/docs/src/_includes/docs/vscode-flutter-bar/stop.md index 13e43a9b37..c12416c971 100644 --- a/sites/docs/src/_includes/docs/vscode-flutter-bar/stop.md +++ b/sites/docs/src/_includes/docs/vscode-flutter-bar/stop.md @@ -1 +1 @@ -![Red empty square that indicates you want to stop the running Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/stop.png) +![红色空心方块图标,表示你要停止正在运行的 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/stop.png) diff --git a/sites/docs/src/content/add-to-app/android/add-flutter-view.md b/sites/docs/src/content/add-to-app/android/add-flutter-view.md index a66bbc1875..d3b70da196 100644 --- a/sites/docs/src/content/add-to-app/android/add-flutter-view.md +++ b/sites/docs/src/content/add-to-app/android/add-flutter-view.md @@ -1,19 +1,29 @@ --- -title: Add a Flutter View to an Android app -shortTitle: Integrate via FlutterView -description: Learn how to perform advanced integrations via Flutter Views. +# title: Add a Flutter View to an Android app +title: 向 Android 应用添加 Flutter View +# shortTitle: Integrate via FlutterView +shortTitle: 通过 FlutterView 集成 +# description: Learn how to perform advanced integrations via Flutter Views. +description: 了解如何通过 Flutter View 进行高级集成。 +tags: Flutter混合工程,add2app +keywords: Android,FlutterView +ai-translated: true --- :::warning Integrating via a [FlutterView]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) is advanced usage and requires manually creating custom, application specific bindings. + +通过 [FlutterView]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) 集成属于高级用法,需要手动创建与应用相关的自定义绑定。 ::: Integrating via a [FlutterView]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) requires a bit more work than via FlutterActivity and FlutterFragment previously described. +通过 [FlutterView]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) 集成比前文介绍的 FlutterActivity 和 FlutterFragment 方式需要更多工作。 + Fundamentally, the Flutter framework on the Dart side requires access to various activity-level events and lifecycles to function. Since the FlutterView (which is an [android.view.View]({{site.android-dev}}/reference/android/view/View.html)) @@ -21,22 +31,35 @@ can be added to any activity which is owned by the developer's application and since the FlutterView doesn't have access to activity level events, the developer must bridge those connections manually to the [FlutterEngine]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html). +从根本上说,Dart 侧的 Flutter 框架需要访问各种 activity 级别的事件与生命周期才能正常工作。由于 FlutterView(它是一个 [android.view.View]({{site.android-dev}}/reference/android/view/View.html))可以添加到开发者应用拥有的任意 activity 中,而 FlutterView 无法访问 activity 级别的事件,因此开发者必须手动将这些连接桥接到 [FlutterEngine]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html)。 + How you choose to feed your application's activities' events to the FlutterView will be specific to your application. +你如何将应用的 activity 事件传递给 FlutterView,取决于你的应用本身。 + ## A sample +## 示例 + Add Flutter View sample video Unlike the guides for FlutterActivity and FlutterFragment, the FlutterView integration could be better demonstrated with a sample project. +与 FlutterActivity 和 FlutterFragment 的指南不同,FlutterView 集成更适合用示例项目来演示。 + A sample project is at [https://github.com/flutter/samples/tree/main/add_to_app/android_view]({{site.repo.samples}}/tree/main/add_to_app/android_view) to document a simple FlutterView integration where FlutterViews are used for some of the cells in a RecycleView list of cards as seen in the gif above. +示例项目位于 [https://github.com/flutter/samples/tree/main/add_to_app/android_view]({{site.repo.samples}}/tree/main/add_to_app/android_view), +演示了简单的 FlutterView 集成:在 RecycleView 卡片列表的部分 cell 中使用 FlutterView,如上方 gif 所示。 + ## General approach +## 通用做法 + The general gist of the FlutterView-level integration is that you must recreate the various interactions between your Activity, the [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) @@ -56,6 +79,18 @@ you must recreate the connections manually. Otherwise, the [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) won't render anything or have other missing functionalities. +FlutterView 级别集成的要点是:你必须在自己的应用代码中重现 Activity、 +[`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) +与 [`FlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html) +之间在 [`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) 中的各种交互。 +[`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) 中的连接在使用 +[`FlutterActivity`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterActivity.html) +或 [`FlutterFragment`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterFragment.html) 时会自动完成; +但本场景下 [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) +被添加到你应用中的 `Activity` 或 `Fragment`,因此你必须手动重建这些连接。 +否则 [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) +将无法渲染任何内容,或缺少其他功能。 + A sample [`FlutterViewEngine`]({{site.repo.samples}}/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) class shows one such possible implementation of an application-specific @@ -63,21 +98,40 @@ connection between an `Activity`, a [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) and a [FlutterEngine]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html). +示例 [`FlutterViewEngine`]({{site.repo.samples}}/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) +类展示了一种可能的实现:在 `Activity`、[`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) +与 [FlutterEngine]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html) +之间建立应用特定的连接。 + ### APIs to implement +### 需要实现的 API + The absolute minimum implementation needed for Flutter to draw anything at all is to: +要让 Flutter 至少能绘制任何内容,最低限度的实现包括: + * Call [`attachToFlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html#attachToFlutterEngine-io.flutter.embedding.engine.FlutterEngine-) when the [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) is added to a resumed `Activity`'s view hierarchy and is visible; and + + 当 [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) + 被添加到已 resumed 的 `Activity` 视图层级且可见时, + 调用 [`attachToFlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html#attachToFlutterEngine-io.flutter.embedding.engine.FlutterEngine-); + 以及 + * Call [`appIsResumed`]({{site.api}}/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html#appIsResumed--) on the [`FlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html)'s `lifecycleChannel` field when the `Activity` hosting the [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) is visible. + 当承载 [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) 的 `Activity` 可见时, + 在 [`FlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/engine/FlutterEngine.html) 的 `lifecycleChannel` 字段上调用 + [`appIsResumed`]({{site.api}}/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html#appIsResumed--)。 + The reverse [`detachFromFlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html#detachFromFlutterEngine--) and other lifecycle methods on the @@ -85,6 +139,11 @@ and other lifecycle methods on the class must also be called to not leak resources when the `FlutterView` or `Activity` is no longer visible. +当 `FlutterView` 或 `Activity` 不再可见时, +还必须调用反向的 [`detachFromFlutterEngine`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html#detachFromFlutterEngine--) +以及 [`LifecycleChannel`]({{site.api}}/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html) +类上的其他生命周期方法,以避免资源泄漏。 + In addition, see the remaining implementation in the [`FlutterViewEngine`]({{site.repo.samples}}/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) demo class or in the @@ -92,8 +151,14 @@ demo class or in the to ensure a correct functioning of other features such as clipboards, system UI overlay, plugins, and so on. +此外,请参阅 [`FlutterViewEngine`]({{site.repo.samples}}/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) 演示类 +或 [`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) 中的其余实现, +以确保剪贴板、系统 UI 叠加层、插件等其他功能正常工作。 + ## Content-sized views +## 按内容自适应尺寸的 View + Usually, a [`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) needs fixed dimensions either through its own dimensions or by matching a parent's dimensions. This can be seen in the [sample project]({{site.repo.samples}}/tree/main/add_to_app/android_view/android_view). @@ -101,10 +166,20 @@ However, it's now possible to allow `FlutterView` to size itself based on its content. By using, `content_wrap` for either the height or the width a `FlutterView` can size itself, as shown in the [content sized sample project]({{site.repo.samples}}/tree/main/add_to_app/android_view/content_sizing_android_view). +通常,[`FlutterView`]({{site.api}}/javadoc/io/flutter/embedding/android/FlutterView.html) +需要通过自身尺寸或匹配父级尺寸来固定大小, +如 [示例项目]({{site.repo.samples}}/tree/main/add_to_app/android_view/android_view) 所示。 +不过,现在可以让 `FlutterView` 根据内容自行确定尺寸: +对高度或宽度使用 `content_wrap`,`FlutterView` 即可自适应, +如 [按内容定尺寸示例项目]({{site.repo.samples}}/tree/main/add_to_app/android_view/content_sizing_android_view) 所示。 + * To _enable_ Content-sized view when deploying your app, add the following setting to your project's `AndroidManifest.xml` file under the `` tag: + 在部署应用时 **启用** 按内容自适应尺寸的 View, + 请在项目 `AndroidManifest.xml` 的 `` 标签下添加以下设置: + ```xml @@ -112,6 +171,9 @@ dependencies { And your Flutter module also depends on [firebase_crashlytics][] via `pubspec.yaml`: +而你的 Flutter 模块也通过 `pubspec.yaml` +依赖了 [firebase_crashlytics][]: + ```yaml title="flutter_module/pubspec.yaml" … dependencies: @@ -124,6 +186,9 @@ dependencies: This plugin usage transitively adds a Gradle dependency again via firebase_crashlytics v0.1.3's own [Gradle file][]: +这种 plugin 用法会通过 firebase_crashlytics v0.1.3 自身的 +[Gradle 文件][Gradle file] 再次传递依赖式地添加一个 Gradle 依赖: + ```groovy title="firebase_crashlytics_via_pub/android/build.gradle … dependencies { @@ -139,15 +204,28 @@ might not be the same version. In this example, the host app requested v2.10.1 and the Flutter module plugin requested v2.9.9. +这两个 `com.crashlytics.sdk.android:crashlytics` 依赖 +可能版本不同。在本例中, +宿主应用请求的是 v2.10.1,而 Flutter +模块 plugin 请求的是 v2.9.9。 + By default, Gradle v5 [resolves dependency version conflicts][] by using the newest version of the library. +默认情况下,Gradle v5 +[解决依赖版本冲突][resolves dependency version conflicts] +的方式是使用该库的最新版本。 + This is generally ok as long as there are no API or implementation breaking changes between the versions. For example, you might use the new Crashlytics library in your existing app as follows: +只要各版本之间没有破坏性的 API 或实现变更, +这通常没有问题。例如,你可能会在现有应用中 +按如下方式使用新的 Crashlytics 库: + @@ -181,11 +259,18 @@ This approach won't work since there are major API differences between the Crashlytics' Gradle library version v17.0.0-beta03 and v2.9.9. +这种方式行不通,因为 Crashlytics 的 Gradle 库 +v17.0.0-beta03 与 v2.9.9 之间存在重大的 API 差异。 + For Gradle libraries that follow semantic versioning, you can generally avoid compilation and runtime errors by using the same major semantic version in your existing app and Flutter module plugin. +对于遵循语义化版本的 Gradle 库, +你通常可以通过在现有应用和 Flutter 模块 plugin 中 +使用相同的主版本号,来避免编译和运行时错误。 + [ExoPlayer from the video_player plugin]: {{site.repo.packages}}/blob/main/packages/video_player/video_player_android/android/build.gradle [firebase_crashlytics]: {{site.pub}}/packages/firebase_crashlytics diff --git a/sites/docs/src/content/add-to-app/debugging.md b/sites/docs/src/content/add-to-app/debugging.md index 8664aa8b6d..f6b8f71dfc 100644 --- a/sites/docs/src/content/add-to-app/debugging.md +++ b/sites/docs/src/content/add-to-app/debugging.md @@ -1,7 +1,13 @@ --- -title: Debug your add-to-app module -shortTitle: Debugging -description: How to run, debug, and hot reload your add-to-app Flutter module. +# title: Debug your add-to-app module +title: 调试 add-to-app 模块 +# shortTitle: Debugging +shortTitle: 调试 +# description: How to run, debug, and hot reload your add-to-app Flutter module. +description: 如何运行、调试并对 add-to-app Flutter 模块进行热重载。 +tags: Flutter混合工程,add2app +keywords: 调试,热重载 +ai-translated: true --- Once you've integrated the Flutter module to your project and used @@ -9,38 +15,71 @@ Flutter's platform APIs to run the Flutter engine and/or UI, you can then build and run your Android or iOS app the same way you run normal Android or iOS apps. +将 Flutter 模块集成到你的项目,并使用 Flutter 平台 API +运行 Flutter 引擎和/或 UI 之后, +你就可以像运行普通 Android 或 iOS 应用一样, +构建并运行你的 Android 或 iOS 应用。 + Flutter now powers the UI wherever your code includes `FlutterActivity` or `FlutterViewController`. +只要你的代码中包含 `FlutterActivity` 或 `FlutterViewController`, +Flutter 就会驱动相应的 UI。 + ## Overview +## 概览 + You might be used to having your suite of favorite Flutter debugging tools available when running `flutter run` or an equivalent command from an IDE. But you can also use all your Flutter [debugging functionalities][] such as hot reload, performance overlays, DevTools, and setting breakpoints in add-to-app scenarios. +你可能习惯了在 IDE 中运行 `flutter run` 或等效命令时, +使用一整套你喜爱的 Flutter 调试工具。 +但在 add-to-app 场景中,你同样可以使用所有 Flutter [调试功能][debugging functionalities], +例如热重载、性能叠加层、DevTools +以及设置断点。 + The `flutter attach` command provides these functionalities. To run this command, you can use the SDK's CLI tools, VS Code or IntelliJ IDEA or Android Studio. +`flutter attach` 命令提供这些功能。 +要运行此命令,你可以使用 SDK 的 CLI 工具、VS Code、 +IntelliJ IDEA 或 Android Studio。 + The `flutter attach` command connects once you run your `FlutterEngine`. It remains attached until you dispose your `FlutterEngine`. You can invoke `flutter attach` before starting your engine. The `flutter attach` command waits for the next available Dart VM that your engine hosts. +运行 `FlutterEngine` 后,`flutter attach` 命令便会建立连接, +并在你 dispose `FlutterEngine` 之前保持连接。 +你可以在启动引擎之前调用 `flutter attach`, +该命令会等待引擎托管的 +下一个可用 Dart VM。 + ## Debug from the Terminal +## 从终端调试 + To attach from the terminal, run `flutter attach`. To select a specific target device, add `-d `. +要从终端附加调试,请运行 `flutter attach`。 +要选择特定的目标设备,请添加 `-d `。 + ```console $ flutter attach ``` The command should print output resembling the following: +该命令应打印出类似以下的输出: + ```console Syncing files to device iPhone 15 Pro... 7,738ms (!) @@ -51,65 +90,116 @@ To hot restart (and rebuild state). press "R". ## Debug iOS extension in Xcode and VS Code +## 在 Xcode 与 VS Code 中调试 iOS 扩展 + {% render "docs/debug/debug-flow-ios.md", add:'launch' %} ## Debug Android extension in Android Studio +## 在 Android Studio 中调试 Android 扩展 + {% render "docs/debug/debug-flow-androidstudio-as-start.md" %} [debugging functionalities]: /testing/debugging ## Debug without USB connection {:#wireless-debugging} +## 无 USB 连接调试 + To debug your app over Wi-Fi on an iOS or Android device, use `flutter attach`. +要在 iOS 或 Android 设备上通过 Wi-Fi 调试你的应用, +请使用 `flutter attach`。 + ### Debug over Wi-Fi on iOS devices +### 在 iOS 设备上通过 Wi-Fi 调试 + For an iOS target, complete the follow steps: +对于 iOS 目标,请完成以下步骤: + 1. Verify your device connects to Xcode over Wi-Fi as described in the [iOS setup guide][]. + 按 [iOS 设置指南][iOS setup guide] 所述, + 确认你的设备已通过 Wi-Fi 连接到 Xcode。 + 1. On your macOS development machine, open **Xcode** > **Product** > **Scheme** > **Edit Scheme...**. + 在你的 macOS 开发机上, + 打开 **Xcode** > + **Product** > + **Scheme** > + **Edit Scheme...**。 + You can also press Cmd + <. + 也可以按 Cmd + <。 + 1. Click **Run**. + 点击 **Run**。 + 1. Click **Arguments**. + 点击 **Arguments**。 + 1. In **Arguments Passed On Launch**, Click **+**. + 在 **Arguments Passed On Launch** 中,点击 **+**。 + {:type="a"} 1. If your dev machine uses IPv4, add `--vm-service-host=0.0.0.0`. + 若你的开发机使用 IPv4,请添加 `--vm-service-host=0.0.0.0`。 + 1. If your dev machine uses IPv6, add `--vm-service-host=::0`. + 若你的开发机使用 IPv6,请添加 `--vm-service-host=::0`。 + #### To determine if you're on an IPv6 network +#### 如何判断你是否处于 IPv6 网络 + 1. Open **Settings** > **Wi-Fi**. + 打开 **Settings** > **Wi-Fi**。 + 1. Click on your connected network. + 点击你已连接的网络。 + 1. Click **Details...** + 点击 **Details...** + 1. Click **TCP/IP**. + 点击 **TCP/IP**。 + 1. Check for an **IPv6 address** section. + 查看是否有 **IPv6 address** 部分。 + ### Debug over Wi-Fi on Android devices +### 在 Android 设备上通过 Wi-Fi 调试 + Verify your device connects to Android Studio over Wi-Fi as described in the [Android setup guide][]. +按 [Android 设置指南][Android setup guide] 所述, +确认你的设备已通过 Wi-Fi 连接到 Android Studio。 + [iOS setup guide]: /platform-integration/ios/setup [Android setup guide]: /platform-integration/android/setup#set-up-devices diff --git a/sites/docs/src/content/add-to-app/index.md b/sites/docs/src/content/add-to-app/index.md index 61914cf278..0c2133fd00 100644 --- a/sites/docs/src/content/add-to-app/index.md +++ b/sites/docs/src/content/add-to-app/index.md @@ -4,9 +4,10 @@ title: 将 Flutter 集成到现有应用 # shortTitle: Add to app shortTitle: 集成到现有应用 # description: Adding Flutter as a library to an existing Android, iOS, macOS, or web app. -description: 将 Flutter 作为 library 集成到现有的 Android、iOS、macOS 以及 web 应用。 +description: 将 Flutter 作为 library 集成到现有的 Android、iOS、macOS 或 web 应用。 tags: Flutter混合工程,add2app keywords: Flutter原生混编,Flutter集成 +ai-translated: true --- ## Add-to-app @@ -17,6 +18,10 @@ If you are writing a new application from scratch, it is easy to [get started][] using Flutter. But what if you already have an app that's not written in Flutter, and it's impractical to start from scratch? +如果你正从零开始编写一个新应用,使用 Flutter [上手][get started] +会很容易。但如果你已经有一个并非使用 Flutter 编写的应用, +并且从头开始重写并不现实,该怎么办? + For those situations, Flutter can be integrated into your existing application piecemeal, as a module. This feature is known as "add-to-app". The module can be imported into your existing app to render part of your app using Flutter, while @@ -24,33 +29,66 @@ the rest can be rendered using existing technology. This method can also be used to run shared non-UI logic by taking advantage of Dart's portability and interoperability with other languages. +对于这些场景,Flutter 可以作为一个 module,逐步集成到你的现有应用中。 +这项功能称为「add-to-app」。该 module 可以导入到你的现有应用中, +用 Flutter 渲染应用的一部分,而其余部分继续使用现有技术渲染。 +借助 Dart 的可移植性以及与其他语言的互操作性, +这种方式也可以用来运行共享的非 UI 逻辑。 + Add-to-app is currently supported on Android, iOS, macOS, and web. +Add-to-app 目前支持 Android、iOS、macOS 和 web。 + Flutter supports two flavors of add-to-app: +Flutter 支持两种 add-to-app 形式: + - **Multi-engine**: supported on Android, iOS, and macOS, allows running one or more instances of Flutter, each rendering a widget embedded into the host application. Each instance is a separate Dart program, running in isolation from other programs. Having multiple Flutter instances allows each instance to maintain independent application and UI state while using minimal memory resources. See more in the [multiple Flutters][] page. + + **多引擎**:支持 Android、iOS 和 macOS,允许运行一个或多个 Flutter 实例, + 每个实例都会渲染一个嵌入到宿主应用中的 widget。 + 每个实例都是一个独立的 Dart 程序,并与其他程序隔离运行。 + 多个 Flutter 实例可以让每个实例在使用较少内存资源的同时, + 维护各自独立的应用状态和 UI 状态。请在 [multiple Flutters][] 页面了解更多内容。 + - **Multi-view**: supported on the web, allows creating multiple [FlutterView][]s, each rendering a widget embedded into the host application. In this mode there's only one Dart program and all views and widgets can share objects. + **多视图**:支持 web,允许创建多个 [FlutterView][], + 每个视图都会渲染一个嵌入到宿主应用中的 widget。 + 在这种模式下只有一个 Dart 程序,所有视图和 widget 都可以共享对象。 + Add-to-app supports integrating multiple Flutter views of any size, supporting various use-cases. Two of the most common use-cases are: +Add-to-app 支持集成多个任意尺寸的 Flutter 视图,适用于多种用例。 +其中最常见的两个用例是: + * **Hybrid navigation stacks**: an app is made of multiple screens, some of which are rendered by Flutter, and others by another framework. The user can navigate from one screen to another freely, no matter which framework is used to render the screen. + + **混合导航栈**:一个应用由多个屏幕组成,其中一些屏幕由 Flutter 渲染, + 另一些屏幕由其他框架渲染。无论屏幕使用哪种框架渲染, + 用户都可以在不同屏幕之间自由导航。 + * **Partial-screen views**: a screen in the app renders multiple widgets, some of which are rendered by Flutter, and others by another framework. The user can scroll and interact with any widget freely, no matter which framework is used to render the widget. + **局部屏幕视图**:应用中的一个屏幕会渲染多个 widget, + 其中一些由 Flutter 渲染,另一些由其他框架渲染。 + 无论 widget 使用哪种框架渲染,用户都可以自由滚动并与任何 widget 交互。 + ## Supported features ## 已支持的特性 @@ -64,44 +102,45 @@ various use-cases. Two of the most common use-cases are: * Auto-build and import the Flutter module by adding a Flutter SDK hook to your Gradle script. - 在 Gradle 脚本中添加一个自动构建并引入 Flutter 模块的 Flutter SDK 钩子。 + 在 Gradle 脚本中添加 Flutter SDK 钩子,自动构建并导入 Flutter module。 * Build your Flutter module into a generic [Android Archive (AAR)][] for integration into your own build system and for better Jetifier interoperability with AndroidX. - 将 Flutter 模块构建为通用的 [Android Archive (AAR)][Android Archive (AAR)] - 以便集成到你自己的构建系统中,并提高 Jetifier 与 AndroidX 的互操作性; + 将 Flutter module 构建为通用的 + [Android Archive (AAR)][Android Archive (AAR)], + 以便集成到你自己的构建系统中,并提升 Jetifier 与 AndroidX 的互操作性。 * [`FlutterEngine`][java-engine] API for starting and persisting your Flutter environment independently of attaching a [`FlutterActivity`][]/[`FlutterFragment`][] etc. - [`FlutterEngine`][java-engine] API 用于启动并持续地为挂载 - [`FlutterActivity`][] 或 [`FlutterFragment`][] 提供独立的 Flutter 环境; + [`FlutterEngine`][java-engine] API 可用于启动并持久化 Flutter 环境, + 且不依赖附加 [`FlutterActivity`][]、[`FlutterFragment`][] 等组件。 * Android Studio Android/Flutter co-editing and module creation/import wizard. - Android Studio 的 Android 与 Flutter 同时编辑, - 以及 Flutter module 创建与导入向导; + Android Studio 提供 Android/Flutter 协同编辑, + 以及 module 创建/导入向导。 * Java and Kotlin host apps are supported. - 支持了 Java 和 Kotlin 为宿主的应用程序; + 支持 Java 和 Kotlin 宿主应用。 * Flutter modules can use [Flutter plugins][] to interact with the platform. - Flutter 模块可以通过使用 [Flutter plugins][] 与平台进行交互。 + Flutter module 可以使用 [Flutter 插件][Flutter plugins] 与平台交互。 * Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter. - 支持通过从 IDE 或命令行中使用 `flutter attach` - 来实现 Flutter 调试与有状态的热重载。 + 支持从 IDE 或命令行使用 `flutter attach` 连接到包含 Flutter 的应用, + 以进行 Flutter 调试和有状态的热重载。 ### Add to iOS applications @@ -112,59 +151,84 @@ various use-cases. Two of the most common use-cases are: * Build your Flutter module into a Swift package for integration into your own build system. - 将 Flutter 模块构建为 Swift package, - 以便集成到你自己的构建系统中; + 将 Flutter module 构建为 Swift package, + 以便集成到你自己的构建系统中。 * Auto-build and import the Flutter module using Xcode build phases. - 使用 Xcode 的 Build Phase 自动构建并引入 Flutter 模块。 + 使用 Xcode build phase 自动构建并导入 Flutter module。 * [`FlutterEngine`][ios-engine] API for starting and persisting your Flutter environment independently of attaching a [`FlutterViewController`][]. - [`FlutterEngine`][ios-engine] API 用于启动并持续地为挂载 - [`FlutterViewController`][] 以提供独立的 Flutter 环境; - + [`FlutterEngine`][ios-engine] API 可用于启动并持久化 Flutter 环境, + 且不依赖附加 [`FlutterViewController`][]。 + * Objective-C and Swift host apps supported. - 支持了 Objective-C 和 Swift 为宿主的应用程序; - + 支持 Objective-C 和 Swift 宿主应用。 + * Flutter modules can use [Flutter plugins][] to interact with the platform. - Flutter 模块可以通过使用 [Flutter plugins][] 与平台进行交互; - + Flutter module 可以使用 [Flutter 插件][Flutter plugins] 与平台交互。 + - Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter. - 支持通过从 IDE 或命令行中使用 `flutter attach` - 来实现 Flutter 调试与有状态的热重载。 + 支持从 IDE 或命令行使用 `flutter attach` 连接到包含 Flutter 的应用, + 以进行 Flutter 调试和有状态的热重载。 See our [add-to-app GitHub Samples repository][] for sample projects in Android and iOS that import a Flutter module for UI. 请查看我们的 [add-to-app GitHub 示例仓库][add-to-app GitHub Samples repository], -其中包含了在 Android 和 iOS 平台上引入 Flutter module 用于 UI 的示例项目。 +其中包含在 Android 和 iOS 中导入 Flutter module 来构建 UI 的示例项目。 ### Add to macOS applications + +### 集成到 macOS 应用 + * Build your Flutter module into a Swift package for integration into your own build system. + + 将 Flutter module 构建为 Swift package, + 以便集成到你自己的构建系统中。 + * Auto-build and import the Flutter module using Xcode build phases. + + 使用 Xcode build phase 自动构建并导入 Flutter module。 + * [`FlutterEngine`][macos-engine] API for starting and persisting your Flutter environment independently of attaching a [`FlutterViewController`][macos-flutterviewcontroller]. + + [`FlutterEngine`][macos-engine] API 可用于启动并持久化 Flutter 环境, + 且不依赖附加 [`FlutterViewController`][macos-flutterviewcontroller]。 + * Swift host apps supported. + + 支持 Swift 宿主应用。 + * Flutter modules can use [Flutter plugins][] to interact with the platform. + + Flutter module 可以使用 [Flutter 插件][Flutter plugins] 与平台交互。 + * Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter. + 支持从 IDE 或命令行使用 `flutter attach` 连接到包含 Flutter 的应用, + 以进行 Flutter 调试和有状态的热重载。 + ### Add to web applications +### 集成到 web 应用 + Flutter can be added to any existing HTML DOM-based web app written in any client-side Dart web framework ([jaspr][], [ngdart][], [over_react][], etc), any client-side JS framework ([React][], [Angular][], [Vue.js][], etc), @@ -174,9 +238,20 @@ any server-side rendered framework ([Django][], [Ruby on Rails][], and its framework support importing JavaScript libraries, and creating HTML elements for Flutter to render into. +Flutter 可以添加到任何基于 HTML DOM 的现有 web 应用中, +这些应用可以使用任意客户端 Dart web 框架([jaspr][]、[ngdart][]、[over_react][] 等)、 +任意客户端 JS 框架([React][]、[Angular][]、[Vue.js][] 等)、 +任意服务端渲染框架([Django][]、[Ruby on Rails][]、[Apache Struts][] 等), +甚至可以不使用任何框架(亲切地称为「[VanillaJS][]」)。 +最低要求只是你的现有应用及其框架支持导入 JavaScript library, +并支持创建供 Flutter 渲染内容的 HTML 元素。 + To add Flutter to an existing app, build it normally, then follow the [embedding instructions][] for putting Flutter views onto the page. +若要将 Flutter 添加到现有应用,请按常规方式构建它, +然后按照 [嵌入说明][embedding instructions] 将 Flutter 视图放到页面上。 + [jaspr]: https://pub.dev/packages/jaspr [ngdart]: https://pub.dev/packages/ngdart [over_react]: https://pub.dev/packages/over_react @@ -191,12 +266,12 @@ To add Flutter to an existing app, build it normally, then follow the ## Get started -## 开始 +## 开始使用 To get started, see our project integration guide for Android, web, iOS, and macOS: -第一步,查看以下工程集成指南 +若要开始使用,请参阅 Android、web、iOS 和 macOS 的项目集成指南:
      @@ -231,7 +306,7 @@ Android, web, iOS, and macOS: After Flutter is integrated into your project, see our API usage guides at the following links: -将 Flutter 集成进你的工程后,可以查看以下 API 使用指南 +将 Flutter 集成到你的项目后,请参阅以下链接中的 API 使用指南:
      - +
      iOS
      @@ -262,50 +337,48 @@ see our API usage guides at the following links: ## Limitations -## 已知限制 +## 限制 Mobile limitations: -移动端的限制: +移动端限制: * Multi-view mode is not supported (multi-engine only). - 不支持多视图模式(仅限多引擎)。 + 不支持多视图模式(仅支持多引擎)。 * Packing multiple Flutter libraries into an application isn't supported. - 不支持将多个 Flutter 库(Flutter 模块)同时打包进一个应用。 - + 不支持将多个 Flutter library 打包到同一个应用中。 + * Plugins that don't support `FlutterPlugin` might have unexpected behaviors if they make assumptions that are untenable in add-to-app (such as assuming that a Flutter `Activity` is always present). - 不支持 `FlutterPlugin` 的插件如果在 add-to-app 进行一些不合理的假设 - (例如假设 Flutter 的 `Activity` 始终存在),可能会出现意外行为。 + 如果不支持 `FlutterPlugin` 的插件做出了 add-to-app 中并不成立的假设 + (例如假设 Flutter `Activity` 始终存在),可能会出现意外行为。 * On Android, the Flutter module only supports AndroidX applications. - Android 平台的 Flutter 模块仅支持适配了 AndroidX 的应用。 + 在 Android 上,Flutter module 仅支持 AndroidX 应用。 Web limitations: -Web 端的限制: +Web 限制: * Multi-engine mode is not supported (multi-view only). - 不支持多引擎模式(仅限多视图)。 + 不支持多引擎模式(仅支持多视图)。 * There's no way to completely "shutdown" the Flutter engine. The app can remove all the [FlutterView][] objects and make sure all data is garbage collected using normal Dart concepts. However, the engine will remain warmed up, even if it's not rendering anything. - 无法完全“关闭” Flutter 引擎。 - 应用程序可以移除所有 [FlutterView][] 对象, - 并确保所有数据通过 Dart 常规的垃圾回收机制被清理。 - 然而,即使引擎不再渲染任何内容, - 它仍会保持预热状态。 + 无法完全「关闭」Flutter 引擎。应用可以移除所有 [FlutterView][] 对象, + 并确保所有数据都通过常规 Dart 概念完成垃圾回收。 + 但是,即使引擎未渲染任何内容,它仍会保持预热状态。 [get started]: /learn/pathway [add-to-app GitHub Samples repository]: {{site.repo.samples}}/tree/main/add_to_app diff --git a/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md b/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md index e858131e14..5d9143ec10 100644 --- a/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md +++ b/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md @@ -7,11 +7,12 @@ shortTitle: 添加 Flutter 页面 description: 了解如何在现有 iOS 应用中添加单个 Flutter 页面。 tags: Flutter混合工程,add2app keywords: iOS,Flutter页面,FlutterEngine +ai-translated: true --- This guide describes how to add a single Flutter screen to an existing iOS app. -本指南描述了怎样在既有 iOS 应用中添加单个 Flutter 页面。 +本指南介绍如何在现有 iOS 应用中添加单个 Flutter 页面。 ## Start a FlutterEngine and FlutterViewController @@ -20,7 +21,7 @@ This guide describes how to add a single Flutter screen to an existing iOS app. To launch a Flutter screen from an existing iOS app, you start a [`FlutterEngine`][] and a [`FlutterViewController`][]. -为了在既有 iOS 应用中展示 Flutter 页面, +若要从现有 iOS 应用启动 Flutter 页面, 请启动 [`FlutterEngine`][] 和 [`FlutterViewController`][]。 :::note @@ -30,36 +31,37 @@ and the `FlutterViewController` attaches to a `FlutterEngine` to pass input events into Flutter and to display frames rendered by the `FlutterEngine`. -`FlutterEngine` 充当 Dart VM 和 Flutter 运行时的主机; -`FlutterViewController` 依附于 `FlutterEngine`, -给 Flutter 传递 UIKit 的输入事件,并展示被 `FlutterEngine` 渲染的每一帧画面。 +`FlutterEngine` 承载 Dart VM 和 Flutter 运行时; +`FlutterViewController` 会附加到 `FlutterEngine`, +以便将输入事件传递给 Flutter,并显示 `FlutterEngine` 渲染出的帧。 ::: The `FlutterEngine` might have the same lifespan as your `FlutterViewController` or outlive your `FlutterViewController`. -`FlutterEngine` 的寿命可能与 `FlutterViewController` 相同,也可能超过 `FlutterViewController`。 +`FlutterEngine` 的生命周期可能与 `FlutterViewController` 相同, +也可能比 `FlutterViewController` 更长。 :::tip It's generally recommended to pre-warm a long-lived `FlutterEngine` for your application because: -通常建议为你的应用预热一个“长寿”的 `FlutterEngine` 是因为: +通常建议为你的应用预热一个长生命周期的 `FlutterEngine`,原因如下: * The first frame appears faster when showing the `FlutterViewController`. - 当展示 `FlutterViewController` 时,第一帧画面将会更快展现; + 展示 `FlutterViewController` 时,第一帧会更快出现。 * Your Flutter and Dart state will outlive one `FlutterViewController`. - 你的 Flutter 和 Dart 状态将比一个`FlutterViewController` 存活更久; + 你的 Flutter 和 Dart 状态可以比单个 `FlutterViewController` 存活得更久。 * Your application and your plugins can interact with Flutter and your Dart logic before showing the UI. - 在展示 UI 前,你的应用和 plugins 可以与 Flutter 和 Dart 逻辑交互。 + 在展示 UI 之前,你的应用和插件可以与 Flutter 以及 Dart 逻辑交互。 ::: @@ -67,8 +69,8 @@ See [Loading sequence and performance][] for more analysis on the latency and memory trade-offs of pre-warming an engine. -[加载顺序和性能][Loading sequence and performance] -里有更多关于预热 engine 的延迟和内存取舍的分析。 +请参阅 [加载顺序和性能][Loading sequence and performance], +了解更多关于预热引擎在延迟和内存方面取舍的分析。 ### Create a FlutterEngine @@ -76,7 +78,7 @@ trade-offs of pre-warming an engine. Where you create a `FlutterEngine` depends on your host app. -在哪创建 `FlutterEngine` 取决于你要用的宿主类型。 +在哪里创建 `FlutterEngine` 取决于你的宿主应用。 @@ -86,10 +88,10 @@ object called `FlutterDependencies`. Pre-warm the engine by calling `run()`, and then inject this object into a `ContentView` using the `environment()` view modifier. -在这个例子中, -我们在名为 `FlutterDependencies` 的 SwiftUI [`Observable`][] 对象中创建了一个 `FlutterEngine` 对象。 -调用 `run()` 为引擎预热, -然后使用 `environment()` view modifier 将此对象传递给了 `ContentView`。 +在这个例子中,我们会在一个名为 `FlutterDependencies` 的 +SwiftUI [`Observable`][] 对象中创建 `FlutterEngine` 对象。 +调用 `run()` 预热引擎,然后使用 `environment()` 视图修饰符 +将此对象注入 `ContentView`。 ```swift title="MyApp.swift" import SwiftUI @@ -128,8 +130,8 @@ As an example, we demonstrate creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate. -这个例子中,我们在应用启动时的 App Delegate 中创建了一个 `FlutterEngine` -并作为属性暴露给外界。 +在这个例子中,我们会在应用启动时,在 app delegate 中创建一个 +`FlutterEngine`,并将其作为属性暴露。 ```swift title="AppDelegate.swift" import UIKit @@ -157,9 +159,8 @@ class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate. The following example demonstrates creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate. -下面的示例演示了, -我们在应用启动时的 App Delegate 中创建了一个 `FlutterEngine` -并作为属性暴露给外界。 +下面的示例演示了如何在应用启动时,在 app delegate 中创建一个 +`FlutterEngine`,并将其作为属性暴露。 ```objc title="AppDelegate.h" @import UIKit; @@ -208,11 +209,11 @@ First, create a `FlutterViewControllerRepresentable` to represent the the pre-warmed `FlutterEngine` as an argument, which is injected through the view environment. -下面的例子中展示了一个带有 [`NavigationLink`][] 的 `ContentView` -连接到一个 Flutter 屏幕。 -首先,创建一个 `FlutterViewControllerRepresentable` 来代表 `FlutterViewController`。 -`FlutterViewController` 的构造函数会接收一个预热过的 -`FlutterEngine` 作为参数,并通过视图环境 (view environment) 注入。 +下面的示例展示了一个通用的 `ContentView`, +它通过 [`NavigationLink`][] 连接到 Flutter 页面。 +首先,创建一个 `FlutterViewControllerRepresentable` 来表示 +`FlutterViewController`。`FlutterViewController` 构造函数接收预热好的 +`FlutterEngine` 作为参数,该参数通过视图环境 (view environment) 注入。 ```swift title="ContentView.swift" import SwiftUI @@ -245,15 +246,15 @@ struct ContentView: View { Now, you have a Flutter screen embedded in your iOS app. -现在,你的 iOS 应用中集成了一个 Flutter 页面。 +现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。 :::note In this example, your Dart `main()` entrypoint function runs when the `FlutterDependencies` observable is initialized. -在本例中,当 `FlutterDependencies` Observable 对象初始化时, -Dart `main()` 入口函数会被执行。 +在本例中,当 `FlutterDependencies` observable 初始化时, +你的 Dart `main()` 入口函数会运行。 ::: @@ -265,10 +266,10 @@ The following example shows a generic `ViewController` with a The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`. -下面的例子展示了一个普通的 `ViewController`, -包含一个能跳转到 [`FlutterViewController`][] 的 `UIButton`,这个 -`FlutterViewController` 使用在 `AppDelegate` -中创建的 Flutter 引擎 (`FlutterEngine`)。 +下面的示例展示了一个通用的 `ViewController`, +其中有一个 `UIButton` 用于呈现 [`FlutterViewController`][]。 +这个 `FlutterViewController` 会使用在 `AppDelegate` 中创建的 +`FlutterEngine` 实例。 ```swift title="ViewController.swift" import Flutter @@ -297,7 +298,7 @@ class ViewController: UIViewController { Now, you have a Flutter screen embedded in your iOS app. -现在,你的 iOS 应用中集成了一个 Flutter 页面。 +现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。 :::note @@ -306,8 +307,8 @@ entrypoint function of your default Dart library would run when calling `run` on the `FlutterEngine` created in the `AppDelegate`. -使用前面的示例,当在 `AppDelegate` 中创建的 `FlutterEngine` -调用 `run` 时,默认的 Dart 库中的 `main()` 入口函数会被执行。 +使用前面的示例时,当在 `AppDelegate` 中创建的 `FlutterEngine` +调用 `run` 时,默认 Dart library 中的默认 `main()` 入口函数会运行。 ::: @@ -320,10 +321,10 @@ The following example shows a generic `ViewController` with a The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`. -下面的例子中展示了在一个常见的 `ViewController`, -包含一个能跳转到 [`FlutterViewController`][] 的 `UIButton`, -`FlutterViewController` 会使用在 `AppDelegate` -中创建的 `FlutterEngine` 实例。 +下面的示例展示了一个通用的 `ViewController`, +其中有一个 `UIButton` 用于呈现 [`FlutterViewController`][]。 +这个 `FlutterViewController` 会使用在 `AppDelegate` 中创建的 +`FlutterEngine` 实例。 ```objc title="ViewController.m" @import Flutter; @@ -357,7 +358,7 @@ created in the `AppDelegate`. Now, you have a Flutter screen embedded in your iOS app. -现在,你的 iOS 应用中集成了一个 Flutter 页面。 +现在,你的 iOS 应用中已经嵌入了一个 Flutter 页面。 :::note @@ -366,8 +367,8 @@ entrypoint function of your default Dart library would run when calling `run` on the `FlutterEngine` created in the `AppDelegate`. -在上一个例子中,你的默认 Dart 库的默认入口函数 `main()`, -将会在 `AppDelegate` 创建 `FlutterEngine` 并调用 `run` 方法时调用。 +使用前面的示例时,当在 `AppDelegate` 中创建的 `FlutterEngine` +调用 `run` 时,默认 Dart library 中的默认 `main()` 入口函数会运行。 ::: @@ -383,8 +384,8 @@ As an alternative to the previous example, you can let the `FlutterViewController` implicitly create its own `FlutterEngine` without pre-warming one ahead of time. -上一个示例还有另一个选择,你可以让 `FlutterViewController` -隐式创建它自己的 `FlutterEngine`,而不用提前预热 engine。 +作为前一个示例的替代方案,你可以让 `FlutterViewController` +隐式创建自己的 `FlutterEngine`,而不提前预热引擎。 This is not usually recommended because creating a `FlutterEngine` on-demand could introduce a noticeable @@ -394,20 +395,19 @@ useful if the Flutter screen is rarely shown, when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers. -不过不建议这样做,因为按需创建`FlutterEngine` 的话, -在 `FlutterViewController` 被 present 出来之后, -第一帧图像渲染完之前,将会引入明显的延迟。 -但是当 Flutter 页面很少被展示时,当对决定何时启动 Dart VM 没有好的启发时, -当 Flutter 无需在页面(view controller)之间保持状态时, -此方式可能会有用。 +通常不建议这样做,因为按需创建 `FlutterEngine` 可能会在 +`FlutterViewController` 呈现后、渲染第一帧前引入明显延迟。 +不过,如果 Flutter 页面很少显示、没有合适的启发式方法判断何时启动 +Dart VM,且 Flutter 不需要在多个 view controller 之间保持状态, +这种方式也可能有用。 To let the `FlutterViewController` present without an existing `FlutterEngine`, omit the `FlutterEngine` construction, and create the `FlutterViewController` without an engine reference. -为了不使用已经存在的 `FlutterEngine` 来展现 `FlutterViewController`, -省略 `FlutterEngine` 的创建步骤, -并且在创建 `FlutterViewController` 时,去掉 engine 的引用。 +若要在没有现有 `FlutterEngine` 的情况下呈现 `FlutterViewController`, +请省略 `FlutterEngine` 的构造步骤,并在创建 `FlutterViewController` 时 +不要传入 engine 引用。 @@ -468,8 +468,8 @@ func showFlutter() { See [Loading sequence and performance][] for more explorations on latency and memory usage. -查看 [加载顺序和性能][Loading sequence and performance] -了解更多关于延迟和内存使用的探索。 +请参阅 [加载顺序和性能][Loading sequence and performance], +了解更多关于延迟和内存使用的探讨。 ## Using the FlutterAppDelegate @@ -478,21 +478,22 @@ for more explorations on latency and memory usage. Letting your application's `UIApplicationDelegate` subclass `FlutterAppDelegate` is recommended but not required. -推荐让你应用的 `UIApplicationDelegate` 继承 `FlutterAppDelegate`,但不是必须的。 +建议让你的应用的 `UIApplicationDelegate` 继承 `FlutterAppDelegate`, +但这并非必需。 The `FlutterAppDelegate` performs functions such as: -`FlutterAppDelegate` 有这些功能: +`FlutterAppDelegate` 会执行以下功能: * Forwarding application callbacks such as [`openURL`][] to plugins such as [local_auth][]. - 传递应用的回调,例如 [`openURL`][] 到 Flutter 的插件 —— [local_auth][]。 + 将应用回调(例如 [`openURL`][])转发给 [local_auth][] 等插件。 * Keeping the Flutter connection open in debug mode when the phone screen locks. - 当手机屏幕锁定时,在调试模式下保持 Flutter 连接处于开启状态。 + 在手机屏幕锁定时,在调试模式下保持 Flutter 连接打开。 ### Creating a FlutterAppDelegate subclass @@ -504,9 +505,9 @@ In a SwiftUI app, you can create a subclass of the `FlutterAppDelegate` and annotate it with the [`Observable()`][] macro as follows: [启动 FlutterEngine 和 FlutterViewController][Start a FlutterEngine and FlutterViewController section] -文档中展示了如何在使用 UIKit 的应用中创建 `FlutterAppDelegate` 子类。 -在使用 SwiftUI 的应用中,你可以创建一个 `FlutterAppDelegate` 的子类, -并使用 [`Observable()`][] 宏 (macro) 对其进行注解,如下所示: +一节展示了如何在 UIKit 应用中创建 `FlutterAppDelegate` 子类。 +在 SwiftUI 应用中,你可以创建 `FlutterAppDelegate` 的子类, +并使用 [`Observable()`][] 宏为其添加注解,如下所示: ```swift title="MyApp.swift" import SwiftUI @@ -544,7 +545,7 @@ struct MyApp: App { Then, in your view, the `AppDelegate` is accessible through the view environment. -然后,在视图中,可以通过视图环境 (view environment) 访问 `AppDelegate`。 +然后,你可以在视图中通过视图环境 (view environment) 访问 `AppDelegate`。 ```swift title="ContentView.swift" import SwiftUI @@ -585,9 +586,9 @@ protocol in order to make sure your plugins receive the necessary callbacks. Otherwise, plugins that depend on these events might have undefined behavior. 如果你的 app delegate 不能直接继承 `FlutterAppDelegate`, -让你的 app delegate 实现 `FlutterAppLifeCycleProvider` 协议, -来确保 Flutter plugins 接收到必要的回调。 -否则,依赖这些事件的 plugins 将会有无法预估的行为。 +请让 app delegate 实现 `FlutterAppLifeCycleProvider` 协议, +以确保 Flutter 插件能收到必要的回调。 +否则,依赖这些事件的插件可能会出现未定义行为。 For instance: @@ -671,8 +672,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvid The implementation should delegate mostly to a `FlutterPluginAppLifeCycleDelegate`: -在具体实现中,应该最大化地委托给 -`FlutterPluginAppLifeCycleDelegate`: +具体实现应尽量委托给 `FlutterPluginAppLifeCycleDelegate`: ```objc title="AppDelegate.m" @interface AppDelegate () @@ -782,12 +782,12 @@ performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))comp The examples demonstrate running Flutter using the default launch settings. -例子中展示了使用默认启动选项运行 Flutter。 +这些示例演示了如何使用默认启动设置运行 Flutter。 In order to customize your Flutter runtime, you can also specify the Dart entrypoint, library, and route. -为了定制化你的 Flutter 运行时,你也可以置顶 Dart 入口、库和路由。 +若要自定义 Flutter 运行时,你也可以指定 Dart 入口点、library 和路由。 ### Dart entrypoint @@ -797,16 +797,15 @@ Calling `run` on a `FlutterEngine`, by default, runs the `main()` Dart function of your `lib/main.dart` file. -在 `FlutterEngine` 上调用 `run`, -默认将会调用你的 `lib/main.dart` -文件里的 `main()` 函数。 +默认情况下,在 `FlutterEngine` 上调用 `run` 会运行 +`lib/main.dart` 文件中的 Dart `main()` 函数。 You can also run a different entrypoint function by using [`runWithEntrypoint`][] with an `NSString` specifying a different Dart function. -你也可以使用另一个入口方法 [`runWithEntrypoint`][], -并使用 `NSString` 字符串指定一个不同的 Dart 入口。 +你也可以使用 [`runWithEntrypoint`][] 并传入指定其他 Dart 函数的 +`NSString`,以运行不同的入口点函数。 :::note @@ -814,9 +813,8 @@ Dart entrypoint functions other than `main()` must be annotated with the following in order to not be [tree-shaken][] away when compiling: -使用 `main()` 以外的 Dart 入口函数,必须使用下面的注解, -防止被 [tree-shaken][] 优化掉, -而没有编译。 +`main()` 以外的 Dart 入口点函数必须使用以下注解, +以免在编译时被 [摇树优化][tree-shaken] 移除: ```dart @pragma('vm:entry-point') @@ -832,13 +830,13 @@ void myOtherEntrypoint() { ... }; In addition to specifying a Dart function, you can specify an entrypoint function in a specific file. -另外,在指定 Dart 函数时,你可以指定特定文件的特定函数。 +除了指定 Dart 函数,你还可以指定特定文件中的入口点函数。 For instance the following runs `myOtherEntrypoint()` in `lib/other_file.dart` instead of `main()` in `lib/main.dart`: -下面的例子使用 `lib/other_file.dart` 文件的 -`myOtherEntrypoint()` 函数取代 `lib/main.dart` 的 `main()` 函数: +例如,下面的代码会运行 `lib/other_file.dart` 中的 +`myOtherEntrypoint()`,而不是 `lib/main.dart` 中的 `main()`: @@ -865,7 +863,7 @@ flutterEngine.run(withEntrypoint: "myOtherEntrypoint", libraryURI: "other_file.d An initial route can be set for your Flutter [`WidgetsApp`][] when constructing the engine. -当构建 engine 时,可以为你的 Flutter [`WidgetsApp`][] 设置一个初始路由。 +构造引擎时,可以为你的 Flutter [`WidgetsApp`][] 设置初始路由。 @@ -893,13 +891,13 @@ FlutterEngine *flutterEngine = [[FlutterEngine alloc] init]; This code sets your `dart:ui`'s [`PlatformDispatcher.defaultRouteName`][] to `"/onboarding"` instead of `"/"`. -这段代码使用 `"/onboarding"` 取代 `"/"`, -作为你的 `dart:ui` 的 [`PlatformDispatcher.defaultRouteName`][] +这段代码会将你的 `dart:ui` 的 [`PlatformDispatcher.defaultRouteName`][] +设置为 `"/onboarding"`,而不是 `"/"`。 Alternatively, to construct a FlutterViewController directly without pre-warming a FlutterEngine: -你也可以直接构造 FlutterViewController 而不用提前初始化 FlutterEngine: +或者,也可以不预热 FlutterEngine,直接构造 FlutterViewController: @@ -930,23 +928,24 @@ route from the platform side after the `FlutterEngine` is already running, use [`pushRoute()`][] or [`popRoute()`] on the `FlutterViewController`. -如果在 `FlutterEngine` 启动后,迫切得需要在平台侧改变你当前的 Flutter 路由, -可以使用 `FlutterViewController` 里的 [`pushRoute()`][] 或者 [`popRoute()`][]。 +如果需要在 `FlutterEngine` 已经运行后,从平台侧以命令式方式更改当前 +Flutter 路由,请在 `FlutterViewController` 上使用 [`pushRoute()`][] 或 +[`popRoute()`][]。 To pop the iOS route from the Flutter side, call [`SystemNavigator.pop()`][]. -在 Flutter 侧推出 iOS 路由,调用 [`SystemNavigator.pop()`][]。 +若要从 Flutter 侧弹出 iOS 路由,请调用 [`SystemNavigator.pop()`][]。 ::: See [Navigation and routing][] for more about Flutter's routes. -查看文档:[路由和导航][Navigation and routing] 了解更多 Flutter 路由的内容。 +请参阅 [路由和导航][Navigation and routing],了解更多关于 Flutter 路由的内容。 ### Other -### 其它 +### 其他 The previous example only illustrates a few ways to customize how a Flutter instance is initiated. Using [platform channels][], @@ -954,15 +953,18 @@ you're free to push data or prepare your Flutter environment in any way you'd like, before presenting the Flutter UI using a `FlutterViewController`. -之前的例子仅仅展示了怎样定制 Flutter 实例初始化的几种方式, -通过 [撰写双端平台代码][platform channels], -你可以在 `FlutterViewController` 展示 Flutter UI 之前, -自由地选择你喜欢的,推入数据和准备 Flutter 环境的方式。 +前面的示例只展示了几种自定义 Flutter 实例初始化方式。 +借助 [平台通道][platform channels],你可以在使用 `FlutterViewController` +呈现 Flutter UI 之前,以任何想要的方式推送数据或准备 Flutter 环境。 ## Content-sized views +## 内容自适应尺寸的视图 + On iOS, you can also set your embedded `FlutterView` to size itself based off its content. +在 iOS 上,你也可以将嵌入的 `FlutterView` 设置为根据其内容自行调整尺寸。 + @@ -984,14 +986,27 @@ _flutterViewController.autoResizable = YES; ### Restrictions +### 限制 + To use this, your root widget must support unbounded constraints. Avoid using widgets that require bounded constraints (like `ListView` or `LayoutBuilder`) at the top of your tree, as they can conflict with the dynamic sizing logic. +若要使用此功能,你的根 widget 必须支持无界约束。 +请避免在 widget 树顶层使用需要有界约束的 widget +(例如 `ListView` 或 `LayoutBuilder`), +因为它们可能会与动态尺寸逻辑冲突。 + In practice, this means that quite a few common widgets are not supported, such as `ScaffoldBuilder`, `CupertinoTimerPicker`, or any widget that internally relies on a `LayoutBuilder`. When in doubt, you can use an `UnconstrainedBox` to test the usability of a widget for a content-sized view, as in the following example: +实际上,这意味着相当多常见的 widget 并不受支持, +例如 `ScaffoldBuilder`、`CupertinoTimerPicker`, +或者任何内部依赖 `LayoutBuilder` 的 widget。 +如果不确定,可以使用 `UnconstrainedBox` 测试某个 widget +是否适用于内容自适应尺寸的视图,如下例所示: + ```dart import 'package:flutter/material.dart'; @@ -1018,8 +1033,11 @@ class MyPage extends StatelessWidget { } } ``` + For a working example, refer to this [sample project][]. +可运行的示例请参阅此 [示例项目][sample project]。 + [`FlutterEngine`]: {{site.api}}/ios-embedder/interface_flutter_engine.html [`FlutterViewController`]: {{site.api}}/ios-embedder/interface_flutter_view_controller.html [Loading sequence and performance]: /add-to-app/performance diff --git a/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md b/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md index d25ec14b64..51d32a1971 100644 --- a/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md +++ b/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md @@ -1,7 +1,13 @@ --- -title: Integrate a Flutter module into your iOS project (Legacy) -shortTitle: Integrate Flutter (Legacy) -description: Learn how to integrate a Flutter module into your existing iOS project. +# title: Integrate a Flutter module into your iOS project (Legacy) +title: 将 Flutter 模块集成到 iOS 项目(旧版) +# shortTitle: Integrate Flutter (Legacy) +shortTitle: 集成 Flutter(旧版) +# description: Learn how to integrate a Flutter module into your existing iOS project. +description: 了解如何将 Flutter 模块集成到你现有的 iOS 项目中。 +tags: Flutter混合工程,add2app +keywords: iOS,项目集成,CocoaPods +ai-translated: true --- :::warning @@ -11,9 +17,16 @@ as the default dependency manager for iOS and macOS Flutter apps. CocoaPods is officially in maintenance mode, and its registry will permanently [become read-only on December 2, 2026][]. +自 Flutter 3.44 起,Swift Package Manager(SwiftPM)取代 CocoaPods, +成为 iOS 与 macOS Flutter 应用的默认依赖管理器。 +CocoaPods 已正式进入维护模式, +其 registry 将于 2026 年 12 月 2 日永久 [变为只读][become read-only on December 2, 2026]。 + This guide is preserved for reference only, and will not receive ongoing maintenance. Please migrate to using Swift Package Manager using the [updated integration guide][]. +本指南仅作参考保留,不会持续维护。请使用 [更新的集成指南][updated integration guide] 迁移到 Swift Package Manager。 + ::: Flutter UI components can be incrementally added into your existing iOS @@ -21,11 +34,16 @@ application as embedded frameworks. To embed Flutter in your existing application, consider one of the following three methods. -| Embedding Method | Methodology | Benefit | +可将 Flutter UI 组件以嵌入 framework 的方式逐步添加到你现有的 iOS 应用中。要将 Flutter 嵌入现有应用,可考虑以下三种方法之一。 + +| Embedding Method嵌入方式 | Methodology方法 | Benefit优势 | |---|---|---| | Use CocoaPods _(Recommended)_ | Install and use the Flutter SDK and CocoaPods. Flutter compiles the `flutter_module` from source each time Xcode builds the iOS app. | Least complicated method to embed Flutter into your app. | +| 使用 CocoaPods **(推荐)** | 安装并使用 Flutter SDK 与 CocoaPods。每次 Xcode 构建 iOS 应用时,Flutter 从源码编译 `flutter_module`。 | 将 Flutter 嵌入应用的最简单方式。 | | Use [iOS frameworks][] | Create iOS frameworks for Flutter components, embed them into your iOS, and update your existing app's build settings. | Doesn't require every developer to install the Flutter SDK and CocoaPods on their local machines. | +| 使用 [iOS frameworks][] | 为 Flutter 组件创建 iOS framework,嵌入 iOS 应用,并更新现有应用的构建设置。 | 不要求每位开发者在本地安装 Flutter SDK 与 CocoaPods。 | | Use iOS frameworks and CocoaPods | Embed the frameworks for your iOS app and the plugins in Xcode, but distribute the Flutter engine as a CocoaPods podspec. | Provides an alternative to distributing the large Flutter engine (`Flutter.xcframework`) library. | +| 使用 iOS frameworks 与 CocoaPods | 在 Xcode 中嵌入 iOS 应用与插件的 frameworks,但以 CocoaPods podspec 分发 Flutter 引擎。 | 为分发大型 Flutter 引擎(`Flutter.xcframework`)库提供替代方案。 | {:.table .table-striped} @@ -34,20 +52,35 @@ consider one of the following three methods. When you add Flutter to your existing iOS app, it [increases the size of your iOS app][app-size]. +将 Flutter 添加到现有 iOS 应用时,会 [增大 iOS 应用体积][app-size]。 + For examples using an app built with UIKit, see the iOS directories in the [add_to_app code samples][]. For an example using SwiftUI, consult the iOS directory in [News Feed App][]. +使用 UIKit 构建的应用示例, +请参阅 [add_to_app 代码示例][add_to_app code samples] 中的 iOS 目录。 +SwiftUI 示例请参阅 [News Feed App][] 中的 iOS 目录。 + ## Development system requirements +## 开发系统要求 + Flutter requires the latest version of Xcode and [CocoaPods][]. +Flutter 需要最新版本的 Xcode 与 [CocoaPods][]。 + ## Create a Flutter module +## 创建 Flutter 模块 + To embed Flutter into your existing application with any method, create a Flutter module first. Use the following command to create a Flutter module. +使用任一方式将 Flutter 嵌入现有应用前, +须先创建 Flutter 模块。使用以下命令创建: + ```console $ cd /path/to/my_flutter $ flutter create --template module my_flutter @@ -57,6 +90,10 @@ Flutter creates module project under `/path/to/my_flutter/`. If you use the [CocoaPods method][], save the module in the same parent directory as your existing iOS app. +Flutter 会在 `/path/to/my_flutter/` 下创建模块项目。 +若使用 [CocoaPods 方式][CocoaPods method], +请将模块保存在与现有 iOS 应用相同的父目录中。 + [CocoaPods method]: /add-to-app/ios/project-setup-legacy/?tab=embed-using-cocoapods From the Flutter module directory, @@ -68,10 +105,20 @@ This project contains a single-view example version of your module before embedding it in your existing iOS app. This helps when testing the Flutter-only parts of your code. +在 Flutter 模块目录中, +你可运行与其他 Flutter 项目相同的 `flutter` 命令, +例如 `flutter run` 或 `flutter build ios`。 +你也可在安装了 Flutter 与 Dart 插件的 [VS Code][] 或 [Android Studio/IntelliJ][] 中运行模块。 +该项目包含嵌入现有 iOS 应用前的单视图示例版本,便于测试代码中仅 Flutter 的部分。 + ## Organize your module +## 组织模块结构 + The `my_flutter` module directory structure resembles a typical Flutter app. +`my_flutter` 模块目录结构类似典型 Flutter 应用。 + - my_flutter/ @@ -90,12 +137,18 @@ Your Dart code should be added to the `lib/` directory. Your Flutter dependencies, packages, and plugins must be added to the `pubspec.yaml` file. +Dart 代码应放在 `lib/` 目录。Flutter 依赖、package 与插件须添加到 `pubspec.yaml`。 + The `.ios/` hidden subfolder contains an Xcode workspace where you can run a standalone version of your module. This wrapper project bootstraps your Flutter code. It contains helper scripts to facilitate building frameworks or embedding the module into your existing application with CocoaPods. +隐藏的 `.ios/` 子文件夹包含 Xcode workspace,可单独运行模块。 +该包装项目引导 Flutter 代码,并包含辅助脚本, +便于构建 framework 或通过 CocoaPods 将模块嵌入现有应用。 + :::note * Add custom iOS code to your own existing application's @@ -104,44 +157,69 @@ embedding the module into your existing application with CocoaPods. directory don't appear in your existing iOS project using the module, and might be overwritten by Flutter. + 将自定义 iOS 代码添加到你现有应用的项目或插件中, + 而非模块的 `.ios/` 目录。 + 在模块 `.ios/` 目录中的更改不会出现在使用该模块的现有 iOS 项目中, + 且可能被 Flutter 覆盖。 + * Exclude the `.ios/` directory from source control as it's autogenerated. + 将 `.ios/` 目录排除在版本控制之外,因其为自动生成。 + * Before building the module on a new machine, run `flutter pub get` in the `my_flutter` directory. This regenerates the `.ios/` directory before building the iOS project that uses the Flutter module. + 在新机器上构建模块前, + 在 `my_flutter` 目录运行 `flutter pub get`, + 以在使用 Flutter 模块的 iOS 项目构建前重新生成 `.ios/` 目录。 + ::: ## Embed a Flutter module in your iOS app +## 将 Flutter 模块嵌入 iOS 应用 + After you have developed your Flutter module, you can embed it using the methods described in the table at the top of the page. +开发完 Flutter 模块后,可使用页面顶部表格中的方法进行嵌入。 + You can run in **Debug** mode on a simulator or a real device, and **Release** mode on a real device. +可在模拟器或真机上以 **Debug** 模式运行,在真机上以 **Release** 模式运行。 + :::note Learn more about [Flutter's build modes][build modes of Flutter]. +了解更多关于 [Flutter 构建模式][build modes of Flutter] 的信息。 + To use Flutter debugging features such as hot reload, consult [Debugging your add-to-app module][]. + +要使用热重载等 Flutter 调试功能, +请参阅 [调试 add-to-app 模块][Debugging your add-to-app module]。 ::: - + + {% render "docs/add-to-app/ios-project/embed-cocoapods.md" %} - + + {% render "docs/add-to-app/ios-project/embed-frameworks.md" %} - + + {% render "docs/add-to-app/ios-project/embed-split.md" %} @@ -151,10 +229,14 @@ consult [Debugging your add-to-app module][]. ## Set local network privacy permissions +## 设置本地网络隐私权限 + {% render "docs/add-to-app/ios-project/local-network-privacy-permissions.md" %} ## Mitigate known issue with Apple Silicon Macs +## 缓解 Apple Silicon Mac 上的已知问题 + On [Macs running Apple Silicon][apple-silicon], the host app builds for an `arm64` simulator. While Flutter supports `arm64` simulators, some plugins might not. @@ -163,40 +245,78 @@ If you use one of these plugins, you might see a compilation error like If this occurs, exclude `arm64` from the simulator architectures in your host app. +在 [运行 Apple Silicon 的 Mac][apple-silicon] 上, +宿主应用会为 `arm64` 模拟器构建。 +Flutter 支持 `arm64` 模拟器,但部分插件可能不支持。 +若使用这类插件,可能看到类似 **Undefined symbols for architecture arm64** 的编译错误。 +若出现此情况,请在宿主应用中从模拟器架构中排除 `arm64`。 + 1. In the **Project Navigator**, click on your project. + 在 **Project Navigator** 中点击项目。 + 1. Click the **Build Settings** tab. + 点击 **Build Settings** 标签页。 + 1. Click **All** and **Combined** sub-tabs. + 点击 **All** 与 **Combined** 子标签页。 + 1. Under **Architectures**, click on **Excluded Architectures**. + 在 **Architectures** 下点击 **Excluded Architectures**。 + 1. Expand to see the available build configurations. + 展开以查看可用构建配置。 + 1. Click **Debug**. + 点击 **Debug**。 + 1. Click the **+** (plus sign). + 点击 **+**(加号)。 + 1. Select **iOS Simulator**. + 选择 **iOS Simulator**。 + 1. Double-click in the value column for **Any iOS Simulator SDK**. + 双击 **Any iOS Simulator SDK** 的值列。 + 1. Click the **+** (plus sign). + 点击 **+**(加号)。 + 1. Type `arm64` in the **Debug > Any iOS Simulator SDK** dialog box. + 在 **Debug > Any iOS Simulator SDK** 对话框中输入 `arm64`。 + 1. Press Esc to close this dialog box. + 按 Esc 关闭对话框。 + 1. Repeat these steps for the **Release** build mode. + 对 **Release** 构建模式重复上述步骤。 + 1. Repeat for any iOS unit test targets. + 对所有 iOS 单元测试 target 重复操作。 + ## Next steps +## 后续步骤 + You can now [add a Flutter screen][] to your existing iOS app. +你现在可以 [向现有 iOS 应用添加 Flutter 屏幕][add a Flutter screen]。 + [add_to_app code samples]: {{site.repo.samples}}/tree/main/add_to_app [add a Flutter screen]: /add-to-app/ios/add-flutter-screen [Android Studio/IntelliJ]: /tools/android-studio @@ -208,4 +328,4 @@ You can now [add a Flutter screen][] to your existing iOS app. [Debugging your add-to-app module]: /add-to-app/debugging/ [apple-silicon]: https://support.apple.com/en-us/116943 [become read-only on December 2, 2026]: https://blog.cocoapods.org/CocoaPods-Specs-Repo/ -[updated integration guide]: /add-to-app/ios/project-setup \ No newline at end of file +[updated integration guide]: /add-to-app/ios/project-setup diff --git a/sites/docs/src/content/add-to-app/ios/project-setup.md b/sites/docs/src/content/add-to-app/ios/project-setup.md index 0f870fc63b..9f1ea66b7d 100644 --- a/sites/docs/src/content/add-to-app/ios/project-setup.md +++ b/sites/docs/src/content/add-to-app/ios/project-setup.md @@ -7,43 +7,76 @@ shortTitle: 集成 Flutter description: 了解如何将 Flutter 应用集成到你现有的 iOS 项目中。 tags: Flutter混合工程,add2app keywords: iOS,项目集成 +ai-translated: true --- -:::tip New! This guide has been updated to use Swift Package Manager + +:::tip 全新内容!本指南已更新为使用 Swift Package Manager As of Flutter 3.44, Swift Package Manager replaces CocoaPods as the default dependency manager for iOS and macOS Flutter apps. CocoaPods is officially in maintenance mode, and its registry will permanently [become read-only on December 2, 2026][]. +自 Flutter 3.44 起,Swift Package Manager 取代 CocoaPods, +成为 iOS 和 macOS Flutter 应用的默认依赖管理器。 +CocoaPods 已正式进入维护模式, +其注册表将 [于 2026 年 12 月 2 日永久变为只读][become read-only on December 2, 2026]。 + The [legacy integration guide][] is preserved for reference, but will not receive ongoing maintenance. Please migrate to using Swift Package Manager. +[旧版集成指南][legacy integration guide] 仍保留以供参考, +但不再持续维护。请迁移到使用 Swift Package Manager。 + ::: Flutter UI components can be incrementally added into your existing iOS application using Swift packages. +你可以使用 Swift package 将 Flutter UI 组件 +逐步添加到你现有的 iOS 应用中。 + ## Prerequisites +## 前提条件 + * Flutter 3.44 or later + + Flutter 3.44 或更高版本 + * Xcode 15.0 or later + Xcode 15.0 或更高版本 + ### Migrate from Legacy Integration (If Applicable) {: #migrate-legacy-integration} +### 从旧版集成方式迁移(如适用) + If you've already integrated Flutter into your iOS app using CocoaPods or embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below. +如果你已经使用 CocoaPods 或嵌入式 framework +将 Flutter 集成到 iOS 应用中, +则必须先移除该集成, +再按照下面的 Swift Package Manager 说明操作。 +
      - Expand to see instructions to migrate from CocoaPods integration + Expand to see instructions to migrate from CocoaPods integration展开查看从 CocoaPods 集成迁移的说明 If your app was previously integrated using CocoaPods, you must first remove the Flutter installation code from your Podfile. + 如果你的应用之前是通过 CocoaPods 集成的, + 则必须先从 Podfile 中移除 Flutter 安装代码。 + 1. Remove Flutter installation code from your Podfile + + 从 Podfile 中移除 Flutter 安装代码。 + ```ruby title="MyApp/Podfile" diff - flutter_application_path = '../my_flutter' - load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') @@ -54,47 +87,82 @@ before following the Swift Package Manager instructions below. ``` 1. Run `pod install`. + + 运行 `pod install`。 +
      - Expand to see instructions to migrate from embedded frameworks integration + Expand to see instructions to migrate from embedded frameworks integration展开查看从嵌入式 framework 集成迁移的说明 If your app was previously integrated using frameworks generated by the `flutter build ios-framework` command, you must first remove the frameworks from your Xcode project. + 如果你的应用之前是通过 `flutter build ios-framework` 命令 + 生成的 framework 集成的, + 则必须先从 Xcode 项目中移除这些 framework。 + 1. Navigate to your target's General tab and remove all Flutter-related frameworks and libraries under **Frameworks, Libraries, and Embedded Content**. + 进入 target 的 General 标签页, + 在 **Frameworks, Libraries, and Embedded Content**(框架、库与嵌入内容) + 下移除所有与 Flutter 相关的 framework 和库。 + This includes the `App.xcframework`, `Flutter.xcframework`, `FlutterPluginRegistrant.xcframework`, and any Flutter plugins' `xcframework` files. + 这包括 `App.xcframework`、`Flutter.xcframework`、 + `FlutterPluginRegistrant.xcframework`, + 以及所有 Flutter 插件的 `xcframework` 文件。 + 1. Remove the Flutter pod from your Podfile + + 从 Podfile 中移除 Flutter pod。 + ```ruby title="MyApp/Podfile" diff - pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/[build mode]/Flutter.podspec' ``` 1. Run `pod install`. + + 运行 `pod install`。 +
      The [legacy integration guide][] is preserved for reference, but will not receive ongoing maintenance. +[旧版集成指南][legacy integration guide] 仍保留以供参考, +但不再持续维护。 + ### Organize your projects relative to each other {: #organize-projects-relatively} +### 组织各项目的相对位置 + This guide assumes that your existing iOS app and your Flutter app or module reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly. +本指南假定你现有的 iOS 应用 +与你的 Flutter 应用或模块位于同级目录中。 +如果你的目录结构不同, +则需要相应地调整示例中的相对路径。 + :::note If integrating for the first time, it's recommended to use a Flutter application (instead of a module). Run the following command to create a new Flutter application: +如果是首次集成, +建议使用 Flutter 应用(而非模块)。 +运行以下命令创建一个新的 Flutter 应用: + ```console flutter create my_flutter_app ``` @@ -103,8 +171,11 @@ flutter create my_flutter_app The example directory structure resembles the following: +示例目录结构如下所示: + - + + @@ -117,7 +188,8 @@ The example directory structure resembles the following: - + + @@ -134,16 +206,24 @@ The example directory structure resembles the following: ## Integrate with Swift Package Manager {: #integrate-with-swiftpm} +## 使用 Swift Package Manager 集成 + 1.

      Build the FlutterNativeIntegration Swift package

      +

      构建 FlutterNativeIntegration Swift package

      + Within your Flutter application or module, run the following command: + 在你的 Flutter 应用或模块中,运行以下命令: + ```console flutter build swift-package --platform ios ``` This will generate the following directories: + 这会生成以下目录: + - my_flutter_app/build/ios/SwiftPackages/ @@ -155,52 +235,104 @@ The example directory structure resembles the following: You can optionally change the location of this output with the `--output` flag. + 你也可以通过 `--output` 标志更改此输出的位置。 + 1.

      Add FlutterNativeIntegration to your Xcode project

      +

      将 FlutterNativeIntegration 添加到你的 Xcode 项目

      + 1. Open your existing iOS app in Xcode. + + 在 Xcode 中打开你现有的 iOS 应用。 + 1. In the Project navigator, right click on your project and select **Add Files to "MyNativeApp"...** + + 在 Project navigator(项目导航器)中右键点击你的项目, + 选择 **Add Files to "MyNativeApp"...**(向 "MyNativeApp" 添加文件…)。 + 1. Navigate to and select the generated `FlutterNativeIntegration` Swift package and click **Add**. + + 定位并选择生成的 + `FlutterNativeIntegration` Swift package,然后点击 **Add**(添加)。 + 1. Select **Reference files in place** and click **Finish**. + + 选择 **Reference files in place**(在原位置引用文件),然后点击 **Finish**(完成)。 + 1. In the File inspector, verify the **Location** is **Relative to Project**. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app. + 在 File inspector(文件检查器)中, + 确认 **Location**(位置)为 **Relative to Project**(相对于项目)。 + 如果不是,则需要将 Flutter 输出目录 + 移动为与原生应用同级的目录。 + 1. Navigate to your target's **General** tab and add `FlutterNativeIntegration` under **Frameworks, Libraries, and Embedded Content**. + + 进入 target 的 **General**(通用)标签页, + 在 **Frameworks, Libraries, and Embedded Content**(框架、库与嵌入内容) + 下添加 `FlutterNativeIntegration`。 + 1.

      Add build settings

      +

      添加构建设置

      + 1. In the **Build Settings** tab, set the location of the Flutter app's Swift package output directory: ``` FLUTTER_SWIFT_PACKAGE_OUTPUT=$SRCROOT/../my_flutter_app/build/ios/SwiftPackages ``` + 在 **Build Settings**(构建设置)标签页中, + 设置 Flutter 应用的 Swift package 输出目录的位置: + ``` + FLUTTER_SWIFT_PACKAGE_OUTPUT=$SRCROOT/../my_flutter_app/build/ios/SwiftPackages + ``` + 1. For custom configurations, set the Flutter build mode. + 对于自定义配置,请设置 Flutter 构建模式。 + Flutter supports three [build modes][]: Debug, Profile, and Release. The build mode is determined using the `CONFIGURATION`. If your configuration does not match one of these, you can set the `FLUTTER_BUILD_MODE` build setting to one of these values. + Flutter 支持三种 [构建模式][build modes]:Debug、Profile 和 Release。 + 构建模式由 `CONFIGURATION` 决定。 + 如果你的配置与其中任何一个都不匹配, + 可以将 `FLUTTER_BUILD_MODE` 构建设置 + 设为这些值之一。 + 1. (Optional) Allow Xcode to re-build your Flutter app. + (可选)允许 Xcode 重新构建你的 Flutter 应用。 + Add the below build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run `flutter build swift-package`. This requires Flutter to be installed on the machine. + 将以下构建设置添加到你的 target, + 以允许 Xcode 在其构建过程中重新构建你的 Flutter 应用。 + 这样你就可以更改 Flutter 应用, + 而无需重新运行 `flutter build swift-package`。 + 这需要机器上已安装 Flutter。 + ``` FLUTTER_APPLICATION_PATH=$SRCROOT/../my_flutter_app ENABLE_USER_SCRIPT_SANDBOXING=NO @@ -210,17 +342,32 @@ The example directory structure resembles the following: This only re-builds the Flutter app's code. If you add new dependencies, you’ll need to re-run `flutter build swift-package`. + + 这只会重新构建 Flutter 应用的代码。 + 如果你添加了新的依赖, + 则需要重新运行 `flutter build swift-package`。 ::: 1.

      Add Pre-action Run Script to Scheme

      +

      向 Scheme 添加 Pre-action 运行脚本

      + 1. Open **Product** > **Scheme** > **Edit Scheme...** > **Build** (in left side bar) > **Pre-action** > **+** > **New Run Script Action** + 打开 **Product** > **Scheme** > **Edit Scheme...** + > **Build**(左侧边栏)> **Pre-action** > **+** + > **New Run Script Action** + 1. Select your project in the **Provide build settings from** dropdown. + 在 **Provide build settings from**(提供构建设置来源)下拉菜单中选择你的项目。 + 1. Set the script to the following: + + 将脚本设为以下内容: + ``` /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh prebuild ``` @@ -229,15 +376,30 @@ The example directory structure resembles the following: 1.

      Add New Run Script Build Phase to Target

      +

      向 Target 添加新的运行脚本构建阶段

      + 1. Navigate to your target's **Build Phases** > **+** > **New Run Script Phase** + 进入你的 target 的 **Build Phases**(构建阶段) + > **+** > **New Run Script Phase** + 1. Set the script to the following: + + 将脚本设为以下内容: + ``` /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh assemble ``` + 1. Uncheck **Based on dependency analysis** + + 取消勾选 **Based on dependency analysis**(基于依赖分析)。 + 1. Add the following to **Input File Lists**: + + 将以下内容添加到 **Input File Lists**(输入文件列表): + ``` $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist ``` @@ -246,11 +408,21 @@ The example directory structure resembles the following: 1.

      (Optional) Set LLDB Init File

      +

      (可选)设置 LLDB Init File

      + Using Flutter's LLDB Init File improves performance when debugging on physical iOS 26+ devices. + 在 iOS 26+ 真机上调试时,使用 Flutter 的 LLDB Init File 可以提升性能。 + 1. Open **Product** > **Scheme** > **Edit Scheme...** > **Run** (in left side bar). + + 打开 **Product** > **Scheme** > **Edit Scheme...** > **Run**(左侧边栏)。 + 1. Set the **LLDB Init File** to the following path: + + 将 **LLDB Init File** 设为以下路径: + ``` $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/flutter_lldbinit ``` @@ -260,6 +432,11 @@ The example directory structure resembles the following: The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. + 或者,如果你的 scheme 已经有 LLDB Init File, + 可以将 Flutter 的 LLDB 文件添加进去。 + Flutter 的 LLDB Init File 的路径必须相对于 + 你项目的 LLDB Init File 的位置。 + ``` command source --relative-to-command-file "../my_flutter_app/build/ios/SwiftPackages/Scripts/flutter_lldbinit" ``` @@ -268,14 +445,20 @@ The example directory structure resembles the following: ## Set local network privacy permissions {: #local-network-permissions} +## 设置本地网络隐私权限 + {% render "docs/add-to-app/ios-project/local-network-privacy-permissions.md" %} ## Next steps +## 后续步骤 + You can now [add a Flutter screen][] to your existing iOS app. +现在你可以向现有的 iOS 应用 [添加 Flutter 页面][add a Flutter screen] 了。 + [add a Flutter screen]: /add-to-app/ios/add-flutter-screen [legacy integration guide]: /add-to-app/ios/project-setup-legacy [become read-only on December 2, 2026]: https://blog.cocoapods.org/CocoaPods-Specs-Repo/ diff --git a/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md b/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md index 69db3b5c11..64975de0b0 100644 --- a/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md +++ b/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md @@ -1,44 +1,82 @@ --- -title: Add a Flutter screen to an macOS app -shortTitle: Add a Flutter screen -description: Learn how to add a single Flutter screen to your existing macOS app. +# title: Add a Flutter screen to an macOS app +title: 向 macOS 应用添加 Flutter 屏幕 +# shortTitle: Add a Flutter screen +shortTitle: 添加 Flutter 屏幕 +# description: Learn how to add a single Flutter screen to your existing macOS app. +description: 了解如何向你现有的 macOS 应用添加单个 Flutter 屏幕。 +tags: Flutter混合工程,add2app +keywords: macOS,Flutter 屏幕 +ai-translated: true --- This guide describes how to add a single Flutter screen to an existing macOS app. +本指南介绍如何向现有 macOS 应用添加单个 Flutter 屏幕。 + ## Start a FlutterEngine and FlutterViewController +## 启动 FlutterEngine 与 FlutterViewController + To launch a Flutter screen from an existing macOS app, you start a [`FlutterEngine`][] and a [`FlutterViewController`][]. +要从现有 macOS 应用启动 Flutter 屏幕, +需要启动 [`FlutterEngine`][] 与 [`FlutterViewController`][]。 + :::note The `FlutterEngine` serves as a host to the Dart VM and your Flutter runtime, and the `FlutterViewController` attaches to a `FlutterEngine` to pass input events into Flutter and to display frames rendered by the `FlutterEngine`. + +`FlutterEngine` 作为 Dart VM 与 Flutter 运行时的宿主, +`FlutterViewController` 附着于 `FlutterEngine`, +将输入事件传入 Flutter,并显示由 `FlutterEngine` 渲染的帧。 ::: The `FlutterEngine` might have the same lifespan as your `FlutterViewController` or outlive your `FlutterViewController`. +`FlutterEngine` 的生命周期可能与 `FlutterViewController` 相同,也可能更长。 + :::tip + It's generally recommended to pre-warm a long-lived `FlutterEngine` for your application because: +通常建议为应用预热长期存活的 `FlutterEngine`,因为: + * The first frame appears faster when showing the `FlutterViewController`. + + 显示 `FlutterViewController` 时首帧出现更快。 + * Your Flutter and Dart state will outlive one `FlutterViewController`. + + Flutter 与 Dart 状态可跨越多个 `FlutterViewController` 存活。 + * Your application and your plugins can interact with Flutter and your Dart logic before showing the UI. + + 应用与插件可在显示 UI 之前与 Flutter 及 Dart 逻辑交互。 + ::: See [Loading sequence and performance][] for more analysis on the latency and memory trade-offs of pre-warming an engine. +有关预热引擎在延迟与内存方面的权衡, +请参阅 [加载顺序与性能][Loading sequence and performance]。 + ### Create a FlutterEngine +### 创建 FlutterEngine + Where you create a `FlutterEngine` depends on your host app. +创建 `FlutterEngine` 的位置取决于宿主应用。 + @@ -47,6 +85,10 @@ inside a SwiftUI [`Observable`][] object called `FlutterDependencies`. Pre-warm the engine by calling `run()`, and then inject this object into a `ContentView` using the `environment()` view modifier. +本示例在名为 `FlutterDependencies` 的 SwiftUI [`Observable`][] 对象内 +创建 `FlutterEngine`,通过调用 `run()` 预热引擎, +再使用 `environment()` 视图修饰符将其注入 `ContentView`。 + ```swift title="MyApp.swift" import SwiftUI import FlutterMacOS @@ -83,6 +125,8 @@ struct MyApp: App { As an example, we demonstrate creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate. +作为示例,我们在应用委托的应用启动时创建并暴露为属性的 `FlutterEngine`。 + ```swift title="AppDelegate.swift" import Cocoa import FlutterMacOS @@ -105,6 +149,8 @@ class AppDelegate: FlutterAppDelegate { ### Show a FlutterViewController with your FlutterEngine +### 使用 FlutterEngine 显示 FlutterViewController + @@ -116,6 +162,10 @@ The `FlutterViewController` constructor takes the pre-warmed `FlutterEngine` as an argument, which is injected through the view environment. +以下示例展示带有连接到 Flutter 屏幕的 [`NavigationLink`][] 的通用 `ContentView`。 +首先创建 `FlutterViewControllerRepresentable` 表示 `FlutterViewController`。 +`FlutterViewController` 构造函数接收预热的 `FlutterEngine` 作为参数,通过视图环境注入。 + ```swift title="ContentView.swift" import SwiftUI import FlutterMacOS @@ -148,9 +198,13 @@ struct ContentView: View { Now, you have a Flutter screen embedded in your macOS app. +现在,你已在 macOS 应用中嵌入了 Flutter 屏幕。 + :::note In this example, your Dart `main()` entrypoint function runs when the `FlutterDependencies` observable is initialized. + +在本示例中,Dart `main()` 入口函数在 `FlutterDependencies` observable 初始化时运行。 ::: @@ -161,6 +215,10 @@ The following example shows a generic `ViewController` with an The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`. +以下示例展示带有 `NSButton` 的通用 `ViewController`, +用于呈现 [`FlutterViewController`][]。 +`FlutterViewController` 使用在 `AppDelegate` 中创建的 `FlutterEngine` 实例。 + ```swift title="ViewController.swift" import Cocoa import FlutterMacOS @@ -189,10 +247,15 @@ class ViewController: NSViewController { Now, you have a Flutter screen embedded in your macOS app. +现在,你已在 macOS 应用中嵌入了 Flutter 屏幕。 + :::note Using the previous example, the default `main()` entrypoint function of your default Dart library runs when calling `run` on the `FlutterEngine` created in the `AppDelegate`. + +使用上一示例,在 `AppDelegate` 中创建的 `FlutterEngine` 上调用 `run` 时, +默认 Dart 库的默认 `main()` 入口函数会运行。 ::: @@ -200,10 +263,14 @@ when calling `run` on the `FlutterEngine` created in the `AppDelegate`. ### _Alternatively_ - Create a FlutterViewController with an implicit FlutterEngine +### _另一种方式_ — 使用隐式 FlutterEngine 创建 FlutterViewController + As an alternative to the previous example, you can let the `FlutterViewController` implicitly create its own `FlutterEngine` without pre-warming one ahead of time. +作为上一示例的替代,可让 `FlutterViewController` 隐式创建自己的 `FlutterEngine`,而无需事先预热。 + This is not usually recommended because creating a `FlutterEngine` on-demand could introduce a noticeable latency between when the `FlutterViewController` is presented @@ -213,10 +280,18 @@ when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers. +通常不推荐这样做, +因为按需创建 `FlutterEngine` 可能在呈现 `FlutterViewController` 与渲染首帧之间引入明显延迟。 +但若 Flutter 屏幕很少显示、难以判断何时应启动 Dart VM, +且 Flutter 无需在 view controller 之间保持状态时,这种方式可能有用。 + To let the `FlutterViewController` present without an existing `FlutterEngine`, omit the `FlutterEngine` construction, and create the `FlutterViewController` without an engine reference. +要让 `FlutterViewController` 在没有现有 `FlutterEngine` 的情况下呈现, +省略 `FlutterEngine` 的构建,并创建不带引擎引用的 `FlutterViewController`。 + @@ -246,23 +321,41 @@ func showFlutter() { See [Loading sequence and performance][] for more explorations on latency and memory usage. +有关延迟与内存使用的更多探讨, +请参阅 [加载顺序与性能][Loading sequence and performance]。 + ## Using the FlutterAppDelegate +## 使用 FlutterAppDelegate + Letting your application's `UIApplicationDelegate` subclass `FlutterAppDelegate` is recommended but not required. +建议(但非必须)让应用的 `UIApplicationDelegate` 子类化 `FlutterAppDelegate`。 + The `FlutterAppDelegate` performs functions such as: +`FlutterAppDelegate` 执行的功能包括: + * Forwarding application callbacks such as [`openURLs`][] to plugins such as [google_sign_in][]. + 将 [`openURLs`][] 等应用回调转发给 [google_sign_in][] 等插件。 + ### Creating a FlutterAppDelegate subclass +### 创建 FlutterAppDelegate 子类 + Creating a subclass of the `FlutterAppDelegate` in UIKit apps was shown in the [Start a FlutterEngine and FlutterViewController section][]. In a SwiftUI app, you can create a subclass of the `FlutterAppDelegate` and annotate it with the [`Observable()`][] macro as follows: +在 UIKit 应用中创建 `FlutterAppDelegate` 子类的方法 +见 [启动 FlutterEngine 与 FlutterViewController 一节][Start a FlutterEngine and FlutterViewController section]。 +在 SwiftUI 应用中,可创建 `FlutterAppDelegate` 子类并用 [`Observable()`][] 宏标注, +如下所示: + ```swift title="MyApp.swift" import SwiftUI import FlutterMacOS @@ -297,6 +390,8 @@ struct MyApp: App { Then, in your view, the `AppDelegate` is accessible through the view environment. +随后,在视图中可通过视图环境访问 `AppDelegate`。 + ```swift title="ContentView.swift" import SwiftUI import FlutterMacOS diff --git a/sites/docs/src/content/add-to-app/macos/project-setup.md b/sites/docs/src/content/add-to-app/macos/project-setup.md index be04ea4ea7..269d78f942 100644 --- a/sites/docs/src/content/add-to-app/macos/project-setup.md +++ b/sites/docs/src/content/add-to-app/macos/project-setup.md @@ -1,59 +1,102 @@ --- -title: Integrate a Flutter app into your macOS project -shortTitle: Integrate Flutter -description: Learn how to integrate a Flutter app into your existing macOS project. +# title: Integrate a Flutter app into your macOS project +title: 将 Flutter 应用集成到 macOS 项目 +# shortTitle: Integrate Flutter +shortTitle: 集成 Flutter +# description: Learn how to integrate a Flutter app into your existing macOS project. +description: 了解如何将 Flutter 应用集成到你现有的 macOS 项目中。 +tags: Flutter混合工程,add2app +keywords: macOS,项目集成,SwiftPM +ai-translated: true --- Flutter UI components can be incrementally added into your existing macOS application using Swift packages. +可使用 Swift package 将 Flutter UI 组件逐步添加到你现有的 macOS 应用中。 + ## Prerequisites +## 前提条件 + * Flutter 3.44 or later + + Flutter 3.44 或更高版本 + * Xcode 15.0 or later + Xcode 15.0 或更高版本 + ### Migrate from legacy integration (if applicable) {: #migrate-legacy-integration} +### 从旧版集成迁移(如适用) {: #migrate-legacy-integration} + If you've already integrated Flutter into your macOS app using embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below. +若你已使用嵌入 framework 将 Flutter 集成到 macOS 应用, +须先移除该集成,再按下方 Swift Package Manager 说明操作。 +
      - Expand to see instructions to migrate from embedded frameworks integration + 展开查看从嵌入 framework 集成迁移的说明 If your app was previously integrated using frameworks generated by the `flutter build macos-framework` command, you must first remove the frameworks from your Xcode project. + 若应用此前通过 `flutter build macos-framework` 命令生成的 framework 集成, + 须先从 Xcode 项目中移除这些 framework。 + 1. Navigate to your target's **General** tab and remove all Flutter-related frameworks and libraries under **Frameworks, Libraries, and Embedded Content**. + 进入 target 的 **General** 标签页, + 在 **Frameworks, Libraries, and Embedded Content** 下 + 移除所有与 Flutter 相关的 framework 与库。 + This includes the `App.xcframework`, `FlutterMacOS.xcframework`, `FlutterPluginRegistrant.xcframework`, and any Flutter plugins' `xcframework` files. + 包括 `App.xcframework`、`FlutterMacOS.xcframework`、`FlutterPluginRegistrant.xcframework` + 以及所有 Flutter 插件的 `xcframework` 文件。 + 1. Remove the Flutter pod from your Podfile + + 从 Podfile 中移除 Flutter pod + ```ruby title="MyApp/Podfile" diff - pod 'FlutterMacOS', :podspec => '/path/to/MyApp/Flutter/[build mode]/FlutterMacOS.podspec' ``` 1. Run `pod install`. + + 运行 `pod install`。 +
      ### Organize your projects relative to each other {: #organize-projects-relatively} +### 相对组织项目结构 {: #organize-projects-relatively} + This guide assumes that your existing macOS app and your Flutter app reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly. +本指南假定现有 macOS 应用与 Flutter 应用位于同级目录。 +若目录结构不同,须相应调整示例中的相对路径。 + :::note If integrating for the first time, run the following command to create a new Flutter application: +若首次集成,运行以下命令创建新的 Flutter 应用: + ```console flutter create my_flutter_app ``` @@ -62,6 +105,8 @@ flutter create my_flutter_app The example directory structure resembles the following: +示例目录结构如下: + - my_flutter_app/ @@ -75,16 +120,24 @@ The example directory structure resembles the following: ## Integrate with Swift Package Manager {: #integrate-with-swiftpm} +## 使用 Swift Package Manager 集成 {: #integrate-with-swiftpm} + 1.

      Build the FlutterNativeIntegration Swift package

      +

      构建 FlutterNativeIntegration Swift package

      + Within your Flutter application or module, run the following command: + 在 Flutter 应用或模块中运行以下命令: + ```console flutter build swift-package --platform macos ``` This generates the following directories: + 将生成以下目录: + - my_flutter_app/build/macos/SwiftPackages/ @@ -96,44 +149,78 @@ The example directory structure resembles the following: You can optionally change the location of this output with the `--output` flag. + 你可使用 `--output` 标志可选地更改输出位置。 + 1.

      Add FlutterNativeIntegration to your Xcode project

      +

      将 FlutterNativeIntegration 添加到 Xcode 项目

      + 1. In the Project navigator, right click on your project and select **Add Files to "MyNativeApp"...** + + 在 Project navigator 中右键项目,选择 **Add Files to "MyNativeApp"...** + 1. Navigate to and select the generated `FlutterNativeIntegration` Swift package and click **Add**. + + 找到并选择生成的 `FlutterNativeIntegration` Swift package,点击 **Add**。 + 1. Select **Reference files in place** and click **Finish**. + + 选择 **Reference files in place**,点击 **Finish**。 + 1. In the File inspector, verify the **Location** is **Relative to Project**. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app. + 在 File inspector 中确认 **Location** 为 **Relative to Project**。 + 若不是,须将 Flutter 输出目录移至与原生应用同级的目录。 + 1. Navigate to your target's **General** tab and add `FlutterNativeIntegration` under **Frameworks, Libraries, and Embedded Content**. + + 进入 target 的 **General** 标签页, + 在 **Frameworks, Libraries, and Embedded Content** 下添加 `FlutterNativeIntegration`。 + 1.

      Add build settings

      +

      添加构建设置

      + 1. In the **Build Settings** tab, set the location of the Flutter app's Swift package output directory: + + 在 **Build Settings** 标签页中设置 Flutter 应用 Swift package 输出目录位置: + ``` FLUTTER_SWIFT_PACKAGE_OUTPUT=$SRCROOT/../my_flutter_app/build/macos/SwiftPackages ``` + 1. For custom configurations, set the Flutter build mode. + 对于自定义配置,设置 Flutter 构建模式。 + Flutter supports three [build modes][]: Debug, Profile, and Release. The build mode is determined using the `CONFIGURATION` value. If your configuration does not match one of these, you can set the `FLUTTER_BUILD_MODE` build setting to one of these values. + Flutter 支持三种 [构建模式][build modes]:Debug、Profile 与 Release。 + 构建模式由 `CONFIGURATION` 值决定。 + 若配置不匹配其中任一,可将 `FLUTTER_BUILD_MODE` 构建设置设为这些值之一。 + 1. For **Debug** configurations only, set the following build settings: + 仅对 **Debug** 配置设置以下构建设置: + ``` ENABLE_APP_SANDBOX=YES ENABLE_INCOMING_NETWORK_CONNECTIONS=YES @@ -144,12 +231,18 @@ The example directory structure resembles the following: 1. (Optional) Allow Xcode to re-build your Flutter app. + (可选)允许 Xcode 重新构建 Flutter 应用。 + Add the following build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run `flutter build swift-package`. This requires a Flutter installation on the machine. + 向 target 添加以下构建设置,使 Xcode 在构建过程中重新构建 Flutter 应用。 + 这样修改 Flutter 应用后无需重新运行 `flutter build swift-package`。 + 这要求机器上已安装 Flutter。 + ``` FLUTTER_APPLICATION_PATH=$SRCROOT/../my_flutter_app ENABLE_USER_SCRIPT_SANDBOXING=NO @@ -158,18 +251,33 @@ The example directory structure resembles the following: :::tip This only re-builds the Flutter app's code. If you add new dependencies, - you’ll need to re-run `flutter build swift-package`. + you'll need to re-run `flutter build swift-package`. + + 这仅重新构建 Flutter 应用的代码。 + 若添加新依赖, + 需要重新运行 `flutter build swift-package`。 ::: 1.

      Add Pre-action Run Script to Scheme

      +

      向 Scheme 添加 Pre-action Run Script

      + 1. Open **Product** > **Scheme** > **Edit Scheme...** > **Build** (in left side bar) > **Pre-action** > **+** > **New Run Script Action** + 打开 **Product** > **Scheme** > **Edit Scheme...** + > **Build**(左侧边栏)> **Pre-action** > **+** + > **New Run Script Action** + 1. Select your project in the **Provide build settings from** dropdown. + 在 **Provide build settings from** 下拉菜单中选择项目。 + 1. Set the script to the following: + + 将脚本设置为: + ``` /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh prebuild ``` @@ -178,15 +286,30 @@ The example directory structure resembles the following: 1.

      Add new run script build phase to your target

      +

      向 target 添加新的 Run Script 构建阶段

      + 1. Navigate to your target's **Build Phases** > **+** > **New Run Script Phase** + 进入 target 的 **Build Phases** + > **+** > **New Run Script Phase** + 1. Set the script to the following: + + 将脚本设置为: + ``` /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh assemble ``` + 1. Uncheck **Based on dependency analysis** + + 取消勾选 **Based on dependency analysis** + 1. Add the following to **Input File Lists**: + + 在 **Input File Lists** 中添加: + ``` $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist ``` @@ -197,8 +320,12 @@ The example directory structure resembles the following: ## Next steps +## 后续步骤 + You can now [add a Flutter screen][] to your existing macOS app. +你现在可以 [向现有 macOS 应用添加 Flutter 屏幕][add a Flutter screen]。 + [add a Flutter screen]: /add-to-app/macos/add-flutter-screen [become read-only on December 2, 2026]: https://blog.cocoapods.org/CocoaPods-Specs-Repo/ -[build modes]: /testing/build-modes \ No newline at end of file +[build modes]: /testing/build-modes diff --git a/sites/docs/src/content/add-to-app/multiple-flutters.md b/sites/docs/src/content/add-to-app/multiple-flutters.md index 96fada25a5..f3b68045e6 100644 --- a/sites/docs/src/content/add-to-app/multiple-flutters.md +++ b/sites/docs/src/content/add-to-app/multiple-flutters.md @@ -6,7 +6,7 @@ shortTitle: 添加多个 Flutter 引擎 # description: > # How to integrate multiple instances of # Flutter engine, screens, or views to your application. -description: 如何将多个 Flutter 引擎 (engine)、页面 (screen) 或视图 (view) 添加到你的应用中(实验性)。 +description: 如何将多个 Flutter 引擎 (engine)、页面 (screen) 或视图 (view) 添加到你的应用中。 --- ## Scenarios @@ -46,7 +46,7 @@ scenarios motivating the usage of multiple Flutters can be found at 使用多个 Flutter 实例的优势在于,每一个实例互相独立,各自维护路由栈、UI 和应用状态。 这简化了应用程序整体的状态保持考虑,并且进一步模块化。 了解更多关于多个 Flutter 使用的动机和场景,请查看 -RFC 文档: [Multiple Flutters]({{site.flutter-files-cn}}/flutter-design-docs/Multiple_Flutters.pdf)。 +RFC 文档:[Multiple Flutters]({{site.flutter-files-cn}}/flutter-design-docs/Multiple_Flutters.pdf)。 Flutter is optimized for this scenario, with a low incremental memory cost (~180kB) for adding additional Flutter instances. This fixed cost diff --git a/sites/docs/src/content/add-to-app/performance.md b/sites/docs/src/content/add-to-app/performance.md index bec92701dc..49a37f47ca 100644 --- a/sites/docs/src/content/add-to-app/performance.md +++ b/sites/docs/src/content/add-to-app/performance.md @@ -58,7 +58,7 @@ Some packages allow you to share images and fonts from the native application to your Flutter screen. For example: -某些 package 允许你共享图像和字体 +借助某些 package 可以让你共享图像和字体 从原生应用共享到 Flutter 屏幕。 例如: diff --git a/sites/docs/src/content/ai/agent-skills.md b/sites/docs/src/content/ai/agent-skills.md index 7f7f5f449f..6e17adb3c9 100644 --- a/sites/docs/src/content/ai/agent-skills.md +++ b/sites/docs/src/content/ai/agent-skills.md @@ -1,69 +1,119 @@ --- -title: Agent skills for Flutter and Dart +# title: Agent skills for Flutter and Dart +title: Flutter 与 Dart 的 Agent Skills sidenav: ai +# shortTitle: Agent skills shortTitle: Agent skills +# description: >- +# Learn how to give AI agents new capabilities and expertise +# using Agent Skills. description: >- - Learn how to give AI agents new capabilities and expertise - using Agent Skills. + 了解如何使用 Agent Skills 为 AI 智能体赋予新能力与专业知识。 +ai-translated: true --- This guide covers how to enhance your AI agents and coding assistants with domain-specific capabilities using Agent Skills. +本指南介绍如何使用 Agent Skills 为 AI 智能体与编程助手增强领域特定能力。 + ## Overview +## 概述 + AI agents can write Flutter and Dart code, but they sometimes are unaware of tools and best practices that professional developers use. +AI 智能体可以编写 Flutter 与 Dart 代码,但有时不了解专业开发者使用的工具与最佳实践。 + [Agent Skills](https://agentskills.io/) help solve this problem by providing a standardized way to give your AI agent a set of task-oriented blueprints to follow. By giving the agent actual domain expertise and repeatable workflows, you drastically reduce mistakes and can enforce consistent patterns. +[Agent Skills](https://agentskills.io/) 通过标准化方式为 AI 智能体提供面向任务的蓝图集合来解决这一问题。 +为智能体提供真正的领域专业知识与可重复工作流,可大幅减少错误并落实一致的模式。 + To understand how Agent Skills fit into your workflow, consider how they compare to other AI capabilities: +要理解 Agent Skills 如何融入工作流,可将其与其他 AI 能力对比: + * **Rules files:** While [rules files](/ai/ai-rules) configure the agent's general behavior across all tasks, Agent Skills give the AI step-by-step instructions for one specific job. + + **规则文件:** [规则文件](/ai/ai-rules) 配置智能体在所有任务中的一般行为, + 而 Agent Skills 为某一具体工作提供分步指令。 + * **Model Context Protocol (MCP):** The [Dart and Flutter MCP server](/ai/mcp-server) gives your agent access to specialized tools. If MCP provides the raw machinery, an Agent Skill provides the professional know-how to operate that machinery correctly. + **Model Context Protocol (MCP):** [Dart 与 Flutter MCP 服务器](/ai/mcp-server) + 为智能体提供专用工具访问。 + 若 MCP 提供原始机制,Agent Skill 则提供正确操作该机制的专业知识。 + Skills use what we call "progressive disclosure," which is similar to deferred loading in Flutter. Instead of loading every single instruction into the context window up front, the agent only reads the metadata first. It pulls in the heavy, detailed instructions only when it actually needs them for the task at hand. +Skills 使用我们称为「渐进式披露」的机制,类似 Flutter 中的延迟加载。 +智能体不会一次性将全部指令载入上下文窗口,而是先只读取元数据, +仅在当前任务实际需要时才拉取详尽指令。 + ## Official repositories +## 官方仓库 + The Dart and Flutter teams maintain official repositories packed with skills tailored specifically for our frameworks. +Dart 与 Flutter 团队维护面向本框架定制的官方 Skills 仓库。 + * **[dart-lang/skills](https://github.com/dart-lang/skills)**: Provides skills for Dart development. Use these to generate unit tests, resolve package dependencies, and fix static analysis errors. + + **[dart-lang/skills](https://github.com/dart-lang/skills)**:提供 Dart 开发相关 skills。 + 可用于生成单元测试、解决 package 依赖并修复静态分析错误。 + * **[flutter/skills](https://github.com/flutter/skills)**: Provides skills for Flutter development. These skills help the AI build responsive layouts, wire up declarative routing, and implement JSON serialization. + **[flutter/skills](https://github.com/flutter/skills)**:提供 Flutter 开发相关 skills。 + 这些 skills 帮助 AI 构建响应式布局、接入声明式路由并实现 JSON 序列化。 + ## Getting started +## 入门 + By default, compatible AI agents discover Agent Skills within the `.agents/skills` directory of your project workspace. +默认情况下,兼容的 AI 智能体会在项目工作区的 `.agents/skills` 目录中发现 Agent Skills。 + To easily download and manage skills in that folder, you can use the `skills` CLI tool. It's distributed through npm, so you'll need [Node.js](https://nodejs.org/) installed to run it with `npx`. +要方便地下载并管理该文件夹中的 skills,可使用 `skills` CLI 工具。 +它通过 npm 分发,因此需要安装 [Node.js](https://nodejs.org/) 才能用 `npx` 运行。 + To install the official Flutter skills: +安装官方 Flutter skills: + ```bash npx skills add flutter/skills --skill '*' --agent universal ``` -And to install the official Dart skills: +To install the official Dart skills: + +安装官方 Dart skills: ```bash npx skills add dart-lang/skills --skill '*' --agent universal @@ -72,13 +122,24 @@ npx skills add dart-lang/skills --skill '*' --agent universal Running these commands automatically creates the `.agents/skills` directory and downloads the requested skills into your project. +运行这些命令会自动创建 `.agents/skills` 目录, +并将请求的 skills 下载到项目中。 + For more details on available skills, updating, and contributing, see the [Dart skills repository](https://github.com/dart-lang/skills) and the [Flutter skills repository](https://github.com/flutter/skills). +有关可用 skills、更新与贡献的更多详情,请参阅 +[Dart skills 仓库](https://github.com/dart-lang/skills) 与 +[Flutter skills 仓库](https://github.com/flutter/skills)。 + :::tip Once you've added skills to your project, try asking your AI agent to review the `.agents/skills` directory. You can ask, "Which of my installed skills can help me with [your current task]?" or "Summarize the capabilities of the skills I have available." + +将 skills 添加到项目后,可请 AI 智能体查看 `.agents/skills` 目录。 +你可以问:「Which of my installed skills can help me with [your current task]?」 +或「Summarize the capabilities of the skills I have available.」 ::: diff --git a/sites/docs/src/content/ai/ai-rules.md b/sites/docs/src/content/ai/ai-rules.md index 9f2a660f99..cb3dcf9ee4 100644 --- a/sites/docs/src/content/ai/ai-rules.md +++ b/sites/docs/src/content/ai/ai-rules.md @@ -1,62 +1,113 @@ --- -title: AI rules for Flutter and Dart +# title: AI rules for Flutter and Dart +title: Flutter 与 Dart 的 AI 规则 sidenav: ai -shortTitle: AI rules +# shortTitle: AI rules +shortTitle: AI 规则 +# description: >- +# Learn how to add AI rules to tools that accelerate your +# development workflow. description: >- - Learn how to add AI rules to tools that accelerate your - development workflow. + 了解如何为加速开发工作流的工具添加 AI 规则。 +ai-translated: true --- This guide covers how you can leverage AI rules to streamline your Flutter and Dart development. +本指南介绍如何利用 AI 规则简化 Flutter 与 Dart 开发。 + :::note Agent skills While rules configure the default behavior for all tasks, you can use [Agent skills](/ai/agent-skills) to give the AI specific tools and instructions for discrete tasks. + +规则配置所有任务的默认行为, +你可以使用 [Agent skills](/ai/agent-skills) 为离散任务向 AI 提供特定工具与指令。 ::: ## Overview +## 概述 + AI-powered editors use rules files to provide context and instructions to an underlying LLM. These files help you: +AI 驱动的编辑器使用规则文件为底层 LLM 提供上下文与指令。 +这些文件帮助你: + * Customize AI behavior to your team's needs. + + 按团队需求自定义 AI 行为。 + * Enforce project best practices for code style and design. + + 落实项目在代码风格与设计方面的最佳实践。 + * Provide critical project context to the AI. + 向 AI 提供关键项目上下文。 + The Flutter project provides several versions of the rules file to accommodate different tool limits: +Flutter 项目提供规则文件的多个版本,以适配不同工具限制: + * [`rules.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules.md): The comprehensive master rule set. + + [`rules.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules.md): + 完整的主规则集。 + * [`rules_10k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_10k.md): A condensed version (<10k chars) for tools with stricter context limits. + + [`rules_10k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_10k.md): + 精简版(小于 10k 字符),适用于上下文限制更严格的工具。 + * [`rules_4k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_4k.md): A highly concise version (<4k chars) for limited contexts. + + [`rules_4k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_4k.md): + 高度精简版(小于 4k 字符),适用于有限上下文。 + * [`rules_1k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_1k.md): An ultra-compact version (<1k chars) for very strict limits. + [`rules_1k.md`](https://raw.githubusercontent.com/flutter/flutter/refs/heads/main/docs/rules/rules_1k.md): + 超紧凑版(小于 1k 字符),适用于非常严格的限制。 +
      - Download the Flutter and Dart rules template + Download the Flutter and Dart rules template下载 Flutter 与 Dart 规则模板 ## Device & editor specific limits +## 设备与编辑器特定限制 + Different AI coding assistants and tools have varying limits for their "rules" or "custom instructions" files. *Last updated: 2026-01-05.* -| Tool / Product | Rules file / Feature | Limit (soft / hard) | Documentation | +不同 AI 编程助手与工具对「规则」或「自定义指令」文件有不同限制。**最后更新:2026-01-05。** + +| Tool / Product工具 / 产品 | Rules file / Feature规则文件 / 功能 | Limit (soft / hard)限制(软 / 硬) | Documentation文档 | |:---|:---|:---|:---| | Antigravity (Google) | `.agent/rules/.md` | 12,000 chars (Hard) | [Configure rules][antigravity] | +| Antigravity (Google) | `.agent/rules/.md` | 12,000 字符(硬限制) | [Configure rules][antigravity] | | Claude Code | `CLAUDE.md` | No Hard Limit | [Claude Code Docs](https://code.claude.com/docs/en/memory) | +| Claude Code | `CLAUDE.md` | 无硬限制 | [Claude Code Docs](https://code.claude.com/docs/en/memory) | | Cursor | `AGENTS.md` | No Hard Limit | [Cursor Docs](https://cursor.com/docs/context/rules) | +| Cursor | `AGENTS.md` | 无硬限制 | [Cursor Docs](https://cursor.com/docs/context/rules) | | Gemini CLI | `GEMINI.md` | 1M+ Tokens (Context) | [Gemini CLI Docs](https://cloud.google.com/vertex-ai/generative-ai/docs/long-context) | +| Gemini CLI | `GEMINI.md` | 100 万+ token(上下文) | [Gemini CLI Docs](https://cloud.google.com/vertex-ai/generative-ai/docs/long-context) | | GitHub Copilot | `.github/copilot-instructions.md` | ~4k chars | [GitHub Copilot Docs](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot) | +| GitHub Copilot | `.github/copilot-instructions.md` | 约 4k 字符 | [GitHub Copilot Docs](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot) | | JetBrains AI (Junie) | `.junie/guidelines.md` | No Hard Limit | [JetBrains AI Docs](https://www.jetbrains.com/help/junie/get-started-with-junie.html) | +| JetBrains AI (Junie) | `.junie/guidelines.md` | 无硬限制 | [JetBrains AI Docs](https://www.jetbrains.com/help/junie/get-started-with-junie.html) | | VS Code | `.instructions.md` | Unknown | [Configure instructions][vs-code] | +| VS Code | `.instructions.md` | 未知 | [Configure instructions][vs-code] | {:.table .table-striped} @@ -64,6 +115,9 @@ or "custom instructions" files. *Last updated: 2026-01-05.* Support for rules files is still evolving. Please check the documentation for your specific development environment for the most up-to-date naming conventions and instructions. + +规则文件支持仍在演进。 +请查阅你所用开发环境的文档,获取最新的命名约定与说明。 ::: [copilot]: https://code.visualstudio.com/docs/copilot/customization/custom-instructions#_use-a-githubcopilotinstructionsmd-file @@ -78,21 +132,35 @@ the most up-to-date naming conventions and instructions. ## Create rules for your editor +## 为你的编辑器创建规则 + You can adapt our Flutter and Dart rules template for your specific environment. To do so, follow these steps: +你可以将 Flutter 与 Dart 规则模板适配到自己的环境。 +按以下步骤操作: + 1. Download the Flutter and Dart rules template: rules.md + 下载 Flutter 与 Dart 规则模板: + rules.md + 1. In an LLM like [Gemini][], attach the `rules.md` file that you downloaded in the last step. + 在 [Gemini][] 等 LLM 中,附上一步下载的 `rules.md` 文件。 + 1. Provide a prompt to reformat the file for your desired editor. + 提供提示词,将文件重新格式化为目标编辑器所需格式。 + Example prompt: + 示例提示词: + ```text Convert the attached rules.md file into a guidelines.md file for Gemini CLI. Make sure @@ -102,11 +170,17 @@ specific environment. To do so, follow these steps: 1. Review the LLM's output and make any necessary adjustments. + 审阅 LLM 输出并进行必要调整。 + 1. Follow your environment's instructions to add the new rules file. This may involve adding to an existing file or creating a new one. + 按环境说明添加新规则文件。可能需要追加到现有文件或创建新文件。 + 1. Verify that your AI assistant is using the new rules to guide its responses. + 验证 AI 助手是否使用新规则来指导其回复。 + [Gemini]: https://gemini.google.com/ diff --git a/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md b/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md index 9f96753d02..860bafb8d3 100644 --- a/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md +++ b/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md @@ -1,11 +1,16 @@ --- -title: Chat client sample +# title: Chat client sample +title: 聊天客户端示例 sidenav: ai +# description: > +# Learn about the chat client sample included in the AI Toolkit. description: > - Learn about the chat client sample included in the AI Toolkit. + 了解 AI 工具包中包含的聊天客户端示例。 prev: - title: Custom LLM providers + # title: Custom LLM providers + title: 自定义 LLM 提供商 path: /ai/ai-toolkit/custom-llm-providers +ai-translated: true --- The AI Chat sample is meant to be a full-fledged chat app built using the @@ -14,23 +19,38 @@ multi-shot, multi-media, streaming features that it gets from the AI Toolkit, the AI Chat sample shows how to store and manage multiple chats at once in your own apps. On desktop form-factors, the AI Chat sample looks like the following: +AI Chat 示例旨在成为使用 Flutter AI 工具包与 Firebase AI Logic SDK 构建的完整聊天应用。 +除从 AI 工具包获得的多轮、多媒体、流式传输等能力外, +AI Chat 示例还展示如何在你自己的应用中同时存储并管理多个聊天。 +在桌面形态下,AI Chat 示例外观如下: + ![Desktop app UI](/assets/images/docs/ai-toolkit/desktop-pluto-convo.png) On mobile form-factors, it looks like this: +在移动形态下,外观如下: + ![Mobile app UI](/assets/images/docs/ai-toolkit/mobile-pluto-convo.png) The chats are stored in an authenticated Cloud Firestore database; any authenticated user can have as many chats as they like. +聊天存储在已认证的 Cloud Firestore 数据库中;任何已认证用户可拥有任意数量的聊天。 + In addition, for each new chat, while the user can manually title it whatever they like, the initial prompt and response is used to ask the LLM what an appropriate title should be. In fact, the titles of the chats in the screenshots in this page were set automatically. +此外,每个新聊天虽可由用户手动命名, +初始提示词与回复会用于向 LLM 询问合适标题。 +事实上,本页截图中聊天标题均为自动设置。 + To build and run the sample, follow the instructions in the [AI Chat README][]. +要构建并运行示例,请遵循 [AI Chat README][] 中的说明。 + {% comment %} TODO: If Mit agrees, move this to an official Flutter repo Chris didn't want to do it so close to release {% endcomment %} diff --git a/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md b/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md index 527aedebdd..0dedcc39d7 100644 --- a/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md +++ b/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md @@ -1,19 +1,27 @@ --- -title: Custom LLM providers +# title: Custom LLM providers +title: 自定义 LLM 提供商 sidenav: ai +# description: > +# How to integrate with other Flutter features. description: > - How to integrate with other Flutter features. + 如何与其他 Flutter 功能集成。 prev: - title: Feature integration + # title: Feature integration + title: 功能集成 path: /ai/ai-toolkit/feature-integration next: - title: Chat client sample + # title: Chat client sample + title: 聊天客户端示例 path: /ai/ai-toolkit/chat-client-sample +ai-translated: true --- The protocol connecting an LLM and the `LlmChatView` is expressed in the [`LlmProvider` interface][]: +连接 LLM 与 `LlmChatView` 的协议由 [`LlmProvider` 接口][`LlmProvider` interface] 表达: + ```dart abstract class LlmProvider implements Listenable { Stream generateStream(String prompt, {Iterable attachments}); @@ -23,7 +31,7 @@ abstract class LlmProvider implements Listenable { } ``` -The LLM could be in the cloud or local, +LLM could be in the cloud or local, it could be hosted in the Google Cloud Platform or on some other cloud provider, it could be a proprietary LLM or open source. @@ -34,30 +42,59 @@ comes with two providers out of the box, both of which implement the `LlmProvider` interface that is required to plug the provider into the following: +LLM 可在云端或本地, +可托管于 Google Cloud Platform 或其他云提供商, +可以是专有或开源 LLM。 +任何可实现该接口的 LLM 或类 LLM 端点都可作为 LLM 提供商接入聊天视图。 +AI 工具包自带两个提供商,均实现接入所需的 `LlmProvider` 接口: + * The [Firebase AI Logic provider][], which wraps the `firebase_ai` package + + [Firebase AI Logic provider][],封装 `firebase_ai` package + * The [Echo provider][], which is useful as a minimal provider example + [Echo provider][],可作为最简 provider 示例 + [Echo provider]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/EchoProvider-class.html [`LlmProvider` interface]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html [Firebase AI Logic provider]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/FirebaseProvider-class.html ## Implementation +## 实现 + To build your own provider, you need to implement the `LlmProvider` interface with these things in mind: +构建自有提供商时,实现 `LlmProvider` 接口需注意: + 1. Providing for full configuration support + + 提供完整配置支持 + 1. Handling history + + 处理历史记录 + 1. Translating messages and attachments to the underlying LLM + + 将消息与附件转换为底层 LLM 格式 + 1. Calling the underlying LLM + 调用底层 LLM + 1. Configuration To support full configurability in your custom provider, you should allow the user to create the underlying model and pass that in as a parameter, as the MyLlmProvider does: + 配置要在自定义提供商中支持完整可配置性, + 应让用户创建底层模型并作为参数传入,如 `MyLlmProvider`: + ```dart class MyLlmProvider extends LlmProvider ... { @immutable @@ -76,15 +113,26 @@ In this way, no matter what changes come to the underlying model in the future, the configuration knobs will all be available to the user of your custom provider. +这样无论底层模型未来如何变化, +自定义提供商的用户仍可使用全部配置项。 + 2. History -History is a big part of any provider—not only does the provider need -to allow history to be manipulated directly, but it has to notify listeners as -it changes. In addition, to support serialization and changing provider -parameters, it must also support saving history as part of the construction -process. + + 历史记录 + + History is a big part of any provider—not only does the provider need + to allow history to be manipulated directly, but it has to notify listeners as + it changes. In addition, to support serialization and changing provider + parameters, it must also support saving history as part of the construction + process. + + 历史记录是任何提供商的重要部分——不仅需支持直接操作历史,还须在变更时通知监听者。 + 此外,为支持序列化与更改提供商参数,构造过程中还须支持保存历史。 The Firebase provider handles this as shown: +Firebase provider 的处理方式如下: + ```dart class MyLlmProvider extends LlmProvider with ChangeNotifier { @immutable @@ -139,13 +187,30 @@ class MyLlmProvider extends LlmProvider with ChangeNotifier { ``` You'll notice several things in this code: + +你会注意到代码中的几点: + * The use of `ChangeNotifier` to implement the `Listenable` method requirements from the `LlmProvider` interface + + 使用 `ChangeNotifier` 实现 `LlmProvider` 接口对 `Listenable` 的要求 + * The ability to pass initial history in as a constructor parameter + + 可将初始历史作为构造参数传入 + * Notifying listeners when there's a new user prompt/LLM response pair + + 出现新的用户提示词/LLM 回复对时通知监听者 + * Notifying listeners when the history is changed manually + + 手动更改历史时通知监听者 + * Creating a new chat when the history changes, using the new history + 历史变更时用新历史创建新聊天 + Essentially, a custom provider manages the history for a single chat session with the underlying LLM. As the history changes, the underlying chat either @@ -154,8 +219,15 @@ needs to be kept up to date automatically the underlying chat-specific methods) or manually recreated (as the Firebase provider does whenever the history is set manually). +本质上,自定义提供商管理单次聊天会话与底层 LLM 的历史。 +历史变更时,底层聊天需自动保持同步 +(如 Firebase provider 在调用聊天专用方法时),或手动重建 +(如 Firebase provider 在手动设置历史时)。 + 3. Messages and attachments + 消息与附件 + Attachments must be mapped from the standard `ChatMessage` class exposed by the `LlmProvider` type to whatever is handled by the underlying LLM. @@ -164,6 +236,11 @@ For example, the Firebase provider maps from the `Content` type provided by the Firebase Logic AI SDK, as shown in the following example: +附件必须从 `LlmProvider` 暴露的标准 `ChatMessage` 类 +映射到底层 LLM 所处理的类型。 +例如,Firebase provider 将 AI 工具包的 `ChatMessage` 映射为 +Firebase Logic AI SDK 的 `Content` 类型,如下所示: + ```dart import 'package:firebase_ai/firebase_ai.dart'; ... @@ -188,8 +265,13 @@ class MyLlmProvider extends LlmProvider with ChangeNotifier { The `_contentFrom` method is called whenever a user prompt needs to be sent to the underlying LLM. Every provider needs to provide for its own mapping. +每当需要向底层 LLM 发送用户提示词时会调用 `_contentFrom` 方法。 +每个提供商都需自行实现映射。 + 4. Calling the LLM + 调用 LLM + How you call the underlying LLM to implement `generateStream` and `sendMessageStream` methods depends on the protocol it exposes. @@ -198,6 +280,10 @@ handles configuration and history but calls to `generateStream` and `sendMessageStream` each end up in a call to an API from the Firebase Logic AI SDK: +如何实现 `generateStream` 与 `sendMessageStream` 取决于底层 LLM 暴露的协议。 +AI 工具包中的 Firebase provider 处理配置与历史, +但对 `generateStream` 与 `sendMessageStream` 的调用最终都会落到 Firebase Logic AI SDK 的 API: + ```dart class MyLlmProvider extends LlmProvider with ChangeNotifier { ... @@ -269,6 +355,8 @@ class MyLlmProvider extends LlmProvider with ChangeNotifier { ## Examples +## 示例 + The [Firebase provider][] implementation provides a good starting point for your own custom provider. @@ -278,6 +366,10 @@ check out the [Echo example app][], which simply formats the user's prompt and attachments as Markdown to send back to the user as its response. +[Firebase provider][] 实现是构建自定义提供商的良好起点。 +若想看去掉所有底层 LLM 调用的示例实现, +请参阅 [Echo 示例应用][Echo example app]:它将用户提示词与附件格式化为 Markdown 作为回复返回。 + [Echo example app]: {{site.github}}/flutter/ai/blob/main/lib/src/providers/implementations/echo_provider.dart [Firebase provider]: diff --git a/sites/docs/src/content/ai/ai-toolkit/feature-integration.md b/sites/docs/src/content/ai/ai-toolkit/feature-integration.md index 3bbc4b0c88..0da4391b12 100644 --- a/sites/docs/src/content/ai/ai-toolkit/feature-integration.md +++ b/sites/docs/src/content/ai/ai-toolkit/feature-integration.md @@ -1,57 +1,106 @@ --- -title: Feature integration +# title: Feature integration +title: 功能集成 sidenav: ai +# description: > +# How to integrate with other Flutter features. description: > - How to integrate with other Flutter features. + 如何与其他 Flutter 功能集成。 prev: - title: User experience + # title: User experience + title: 用户体验 path: /ai/ai-toolkit/user-experience next: - title: Custom LLM providers + # title: Custom LLM providers + title: 自定义 LLM 提供商 path: /ai/ai-toolkit/custom-llm-providers +ai-translated: true --- In addition to the features that are provided automatically by the [`LlmChatView`][], a number of integration points allow your app to blend seamlessly with other features to provide additional functionality: +除 [`LlmChatView`][`LlmChatView`] 自动提供的功能外,多个集成点可让应用与其他功能无缝融合以提供额外能力: + * **Welcome messages**: Display an initial greeting to users. + + **欢迎消息**:向用户显示初始问候。 + * **Suggested prompts**: Offer users predefined prompts to guide interactions. + + **建议提示词**:提供预定义提示词引导交互。 + * **System instructions**: Provide the LLM with specific input to influence its responses. + + **系统指令**:向 LLM 提供特定输入以影响其回复。 + * **Disable attachments and audio input**: Remove optional parts of the chat UI. + + **禁用附件与音频输入**:移除聊天 UI 的可选部分。 + * **Manage cancel or error behavior**: Change the user cancellation or LLM error behavior. + + **管理取消或错误行为**:更改用户取消或 LLM 错误时的行为。 + * **Manage history**: Every LLM provider allows for managing chat history, which is useful for clearing it, changing it dynamically and storing it between sessions. + + **管理历史**:各 LLM 提供商均支持管理聊天历史,便于清空、动态更改及在会话间存储。 + * **Chat serialization/deserialization**: Store and retrieve conversations between app sessions. + + **聊天序列化/反序列化**:在应用会话间存储与恢复对话。 + * **Custom response widgets**: Introduce specialized UI components to present LLM responses. + + **自定义响应 widget**:引入专用 UI 组件展示 LLM 回复。 + * **Custom styling**: Define unique visual styles to match the chat appearance to the overall app. + + **自定义样式**:定义独特视觉样式使聊天外观与整体应用一致。 + * **Chat without UI**: Interact directly with the LLM providers without affecting the user's current chat session. + + **无 UI 聊天**:直接与 LLM 提供商交互而不影响用户当前聊天会话。 + * **Custom LLM providers**: Build your own LLM provider for integration of chat with your own model backend. + + **自定义 LLM 提供商**:构建自有 LLM 提供商以将聊天与你自己的模型后端集成。 + * **Rerouting prompts**: Debug, log, or reroute messages meant for the provider to track down issues or route prompts dynamically. + **重路由提示词**:调试、记录或重路由发往提供商的消息以排查问题或动态路由提示词。 + [`LlmChatView`]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmChatView-class.html ## Welcome messages +## 欢迎消息 + The chat view allows you to provide a custom welcome message to set context for the user: +聊天视图让你提供自定义欢迎消息以为用户设定上下文: + ![Example welcome message](/assets/images/docs/ai-toolkit/example-of-welcome-message.png) You can initialize the `LlmChatView` with a welcome message by setting the `welcomeMessage` parameter: +可通过设置 `welcomeMessage` 参数为 `LlmChatView` 初始化欢迎消息: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -74,14 +123,20 @@ class ChatPage extends StatelessWidget { To see a complete example of setting the welcome message, check out the [welcome example][]. +完整示例请参阅 [welcome 示例][welcome example]。 + [welcome example]: {{site.github}}/flutter/ai/blob/main/example/lib/welcome/welcome.dart ## Suggested prompts +## 建议提示词 + You can provide a set of suggested prompts to give the user some idea of what the chat session has been optimized for: +你可提供一组建议提示词,让用户了解聊天会话的优化方向: + ![Example suggested prompts](/assets/images/docs/ai-toolkit/example-of-suggested-prompts.png) @@ -89,6 +144,9 @@ The suggestions are only shown when there is no existing chat history. Clicking one sends it immediately as a request to the underlying LLM. To set the list of suggestions, construct the `LlmChatView` with the `suggestions` parameter: +建议仅在无现有聊天历史时显示。点按某条会立即作为请求发送给底层 LLM。 +要设置建议列表,构造 `LlmChatView` 时传入 `suggestions` 参数: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -115,16 +173,24 @@ class ChatPage extends StatelessWidget { To see a complete example of setting up suggestions for the user, take a look at the [suggestions example][]. +完整示例请参阅 [suggestions 示例][suggestions example]。 + [suggestions example]: {{site.github}}/flutter/ai/blob/main/example/lib/suggestions/suggestions.dart ## LLM instructions +## LLM 指令 + To optimize an LLM's responses based on the needs of your app, you'll want to give it instructions. For example, the [recipes example app][] uses the `systemInstructions` parameter of the `GenerativeModel` class to tailor the LLM to focus on delivering recipes based on the user's instructions: +要根据应用需求优化 LLM 回复,需要为其提供指令。 +例如,[recipes 示例应用][recipes example app] 使用 `GenerativeModel` 类的 `systemInstructions` 参数, +使 LLM 专注于根据用户指令提供食谱: + ```dart class _HomePageState extends State { ... @@ -152,18 +218,26 @@ You should keep things casual and friendly. You may generate multiple recipes in Setting system instructions is unique to each provider; the `FirebaseProvider` allows you to provide them through the `systemInstruction` parameter. +设置系统指令因提供商而异;`FirebaseProvider` 可通过 `systemInstruction` 参数提供。 + Notice that, in this case, we're bringing in user preferences as part of the creation of the LLM provider passed to the `LlmChatView` constructor. We set the instructions as part of the creation process each time the user changes their preferences. The recipes app allows the user to change their food preferences using a drawer on the scaffold: +注意此处我们在创建传给 `LlmChatView` 构造函数的 LLM 提供商时纳入用户偏好。 +每次用户更改偏好时,我们在创建过程中设置指令。 +recipes 应用让用户通过 scaffold 上的 drawer 更改食物偏好: + ![Example of refining prompt](/assets/images/docs/ai-toolkit/setting-food-preferences.png) Whenever the user changes their food preferences, the recipes app creates a new model to use the new preferences: +每当用户更改食物偏好,recipes 应用会创建使用新偏好的新模型: + ```dart class _HomePageState extends State { ... @@ -177,24 +251,38 @@ class _HomePageState extends State { ## Function calling +## 函数调用 + To enable the LLM to perform actions on behalf of the user, you can provide a set of tools (functions) that the LLM can call. The `FirebaseProvider` supports function calling out of the box. It handles the loop of sending the user's prompt, receiving a function call request from the LLM, executing the function, and sending the result back to the LLM until a final text response is generated. +要让 LLM 代表用户执行操作,可提供 LLM 可调用的工具(函数)集。 +`FirebaseProvider` 开箱支持函数调用,处理循环:发送用户提示词、 +接收 LLM 的函数调用请求、执行函数并将结果返回 LLM,直至生成最终文本回复。 + To use function calling, you need to define your tools and pass them to the `FirebaseProvider`. Check out the [function calling example][] for details. +使用函数调用需定义工具并传给 `FirebaseProvider`。 +详情请参阅 [function calling 示例][function calling example]。 + [function calling example]: {{site.github}}/flutter/ai/blob/main/example/lib/function_calls/function_calls.dart ## Disable attachments and audio input +## 禁用附件与音频输入 + If you'd like to disable attachments (the **+** button) or audio input (the mic button), you can do so with the `enableAttachments` and `enableVoiceNotes` parameters to the `LlmChatView` constructor: +若要禁用附件(**+** 按钮)或音频输入(麦克风按钮), +可在 `LlmChatView` 构造函数中使用 `enableAttachments` 与 `enableVoiceNotes` 参数: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -217,14 +305,22 @@ class ChatPage extends StatelessWidget { Both of these flags default to `true`. +这两个标志默认为 `true`。 + ## Custom speech-to-text +## 自定义语音转文字 + By default, the AI Toolkit uses the `LlmProvider` to pass to the `LlmChatView` to provide the speech-to-text implementation. If you'd like to provide your own implementation, for example to use a device-specific service, you can do so by implementing the `SpeechToText` interface and passing it to the `LlmChatView` constructor: +默认情况下,AI 工具包通过传给 `LlmChatView` 的 `LlmProvider` 提供语音转文字实现。 +若要提供自有实现(例如使用设备特定服务), +可实现 `SpeechToText` 接口并传给 `LlmChatView` 构造函数: + ```dart LlmChatView( // ... @@ -234,22 +330,32 @@ LlmChatView( Check out the [custom STT example][] for details. +详情请参阅 [custom STT 示例][custom STT example]。 + [custom STT example]: {{site.github}}/flutter/ai/tree/main/example/lib/custom_stt ## Manage cancel or error behavior +## 管理取消或错误行为 + By default, when the user cancels an LLM request, the LLM's response will be appended with the string "CANCEL" and a message will pop up that the user has canceled the request. Likewise, in the event of an LLM error, like a dropped network connection, the LLM's response will be appended with the string "ERROR" and an alert dialog will pop up with the details of the error. +默认情况下,用户取消 LLM 请求时,LLM 回复会追加 "CANCEL" 字符串并弹出已取消消息。 +同样,发生 LLM 错误(如网络断开)时,回复会追加 "ERROR" 并弹出含错误详情的对话框。 + You can override the cancel and error behavior with the `cancelMessage`, `errorMessage`, `onCancelCallback` and `onErrorCallback` parameters of the `LlmChatView`. For example, the following code replaces the default cancellation handling behavior: +可通过 `LlmChatView` 的 `cancelMessage`、`errorMessage`、`onCancelCallback` 与 `onErrorCallback` 参数覆盖取消与错误行为。 +例如以下代码替换默认取消处理: + ```dart class ChatPage extends StatelessWidget { // ... @@ -275,12 +381,18 @@ class ChatPage extends StatelessWidget { You can override any or all of these parameters and the `LlmChatView` will use its defaults for anything you don't override. +你可覆盖其中任意或全部参数;未覆盖的项将使用 `LlmChatView` 默认值。 + ## Manage history +## 管理历史 + The [standard interface that defines all LLM providers][providerIF] that can plug into the chat view includes the ability to get and set history for the provider: +[定义所有可接入聊天视图的 LLM 提供商的标准接口][providerIF] 包含获取与设置提供商历史的能力: + ```dart abstract class LlmProvider implements Listenable { Stream generateStream( @@ -306,6 +418,9 @@ exposed by the `Listenable` base class. This means that you manually subscribe/unsubscribe with the `add` and `remove` methods or use it to construct an instance of the `ListenableBuilder` class. +提供商历史变更时会调用 `Listenable` 基类暴露的 `notifyListener` 方法。 +这意味着你可手动用 `add` 与 `remove` 订阅/取消订阅,或用它构造 `ListenableBuilder` 实例。 + The `generateStream` method calls into the underlying LLM without affecting the history. Calling the `sendMessageStream` method changes the history by adding two new messages to the provider's history—one for the user message and one for @@ -313,8 +428,14 @@ the LLM response—when the response is completed. The chat view uses `sendMessageStream` when it processes a user's chat prompt and `generateStream` when it's processing the user's voice input. +`generateStream` 调用底层 LLM 而不影响历史。 +`sendMessageStream` 在回复完成时向提供商历史添加两条新消息(用户消息与 LLM 回复)。 +聊天视图处理用户聊天提示词时用 `sendMessageStream`,处理语音输入时用 `generateStream`。 + To see or set the history, you can access the `history` property: +要查看或设置历史,可访问 `history` 属性: + ```dart void _clearHistory() => _provider.history = []; ``` @@ -322,6 +443,8 @@ void _clearHistory() => _provider.history = []; The ability to access a provider's history is also useful when it comes to recreating a provider while maintaining the history: +在保持历史的同时重建提供商时,访问提供商历史也很有用: + ```dart class _HomePageState extends State { ... @@ -338,6 +461,9 @@ previous provider _and_ the new user preferences. It's seamless for the user; they can keep chatting away but now the LLM gives them responses taking their new food preferences into account. For example: +`_createProvider` 方法用上一提供商的历史 **以及** 新用户偏好创建新提供商。 +对用户而言无缝:可继续聊天,而 LLM 回复会考虑新的食物偏好。例如: + ```dart class _HomePageState extends State { @@ -355,18 +481,25 @@ class _HomePageState extends State { To see history in action, check out the [recipes example app][] and the [history example app][]. +实践请参阅 [recipes 示例应用][recipes example app] 与 [history 示例应用][history example app]。 + [history example app]: {{site.github}}/flutter/ai/blob/main/example/lib/history/history.dart [recipes example app]: {{site.github}}/flutter/ai/tree/main/example/lib/recipes ## Chat serialization/deserialization +## 聊天序列化/反序列化 + To save and restore chat history between sessions of an app requires the ability to serialize and deserialize each user prompt, including the attachments, and each LLM response. Both kinds of messages (the user prompts and LLM responses), are exposed in the `ChatMessage` class. Serialization can be accomplished by using the `toJson` method of each `ChatMessage` instance. +要在应用会话间保存与恢复聊天历史,需能序列化与反序列化每条用户提示词(含附件)及每条 LLM 回复。 +两类消息均在 `ChatMessage` 类中暴露。序列化可使用各 `ChatMessage` 实例的 `toJson` 方法。 + ```dart Future _saveHistory() async { // get the latest history @@ -389,6 +522,8 @@ Future _saveHistory() async { Likewise, to deserialize, use the static `fromJson` method of the `ChatMessage` class: +反序列化同理,使用 `ChatMessage` 类的静态 `fromJson` 方法: + ```dart Future _loadHistory() async { // read the history from disk @@ -410,13 +545,20 @@ To ensure fast turnaround when serializing, we recommend only writing each user message once. Otherwise, the user must wait for your app to write every message every time and, in the face of binary attachments, that could take a while. +为确保序列化快速完成,建议每条用户消息只写入一次。 +否则用户每次都要等待应用重写全部消息,面对二进制附件时可能耗时较长。 + To see this in action, check out the [history example app][]. +实践请参阅 [history 示例应用][history example app]。 + [history example app]: {{site.github}}/flutter/ai/blob/main/example/lib/history/history.dart ## Custom response widgets +## 自定义响应 widget + By default, the LLM response shown by the chat view is formatted Markdown. However, in some cases, you want to create a custom widget to show the LLM response that's specific to and integrated with your app. For example, when the @@ -425,11 +567,19 @@ to create a widget that's specific to showing recipes just like the rest of the app does and to provide for an **Add** button in case the user would like to add the recipe to their database: +默认情况下,聊天视图显示的 LLM 回复为格式化 Markdown。 +但有时你需要创建与应用特定且集成的自定义 widget 展示 LLM 回复。 +例如,在 [recipes 示例应用][recipes example app] 中用户请求食谱时, +LLM 回复用于创建与应用其余部分一致的食谱展示 widget, +并提供 **Add** 按钮以便用户将食谱加入数据库: + ![Add recipe button](/assets/images/docs/ai-toolkit/add-recipe-button.png) This is accomplished by setting the `responseBuilder` parameter of the `LlmChatView` constructor: +通过设置 `LlmChatView` 构造函数的 `responseBuilder` 参数实现: + ```dart LlmChatView( provider: _provider, @@ -443,6 +593,8 @@ LlmChatView( In this particular example, the `RecipeResponseView` widget is constructed with the LLM provider's response text and uses that to implement its `build` method: +此例中,`RecipeResponseView` widget 用 LLM 提供商的回复文本构造,并在 `build` 方法中使用: + ```dart class RecipeResponseView extends StatelessWidget { const RecipeResponseView(this.response, {super.key}); @@ -506,12 +658,17 @@ This code parses the text to extract introductory text and the recipe from the LLM, bundling them together with an **Add Recipe** button to show in place of the Markdown. +此代码解析文本,从 LLM 提取介绍文字与食谱,并与 **Add Recipe** 按钮一起替代 Markdown 显示。 + Notice that we're parsing the LLM response as JSON. It's common to set the provider into JSON mode and to provide a schema to restrict the format of its responses to ensure that we've got something we can parse. Each provider exposes this functionality in its own way, but the `FirebaseProvider` class enables this with a `GenerationConfig` object that the recipes example uses as follows: +注意我们将 LLM 回复解析为 JSON。常见做法是将提供商设为 JSON 模式并提供 schema 限制回复格式以确保可解析。 +各提供商以不同方式暴露此功能,`FirebaseProvider` 通过 `GenerationConfig` 实现,recipes 示例如下: + ```dart class _HomePageState extends State { ... @@ -560,15 +717,26 @@ JSON that you're prepared to parse. In addition, it's good practice to also ask for JSON and to provide a description of that JSON schema in the system instructions, which we've done here. +此代码将 `responseMimeType` 设为 `'application/json'`, +`responseSchema` 设为定义可解析 JSON 结构的 `Schema` 实例。 +此外,最好在系统指令中也要求 JSON 并描述 schema,此处已这样做。 + To see this in action, check out the [recipes example app][]. +实践请参阅 [recipes 示例应用][recipes example app]。 + ## Custom styling +## 自定义样式 + The chat view comes out of the box with a set of default styles for the background, the text field, the buttons, the icons, the suggestions, and so on. You can fully customize those styles by setting your own by using the `style` parameter to the `LlmChatView` constructor: +聊天视图自带背景、文本框、按钮、图标、建议等默认样式。 +可通过 `LlmChatView` 构造函数的 `style` 参数完全自定义: + ```dart LlmChatView( provider: FirebaseProvider(...), @@ -579,6 +747,8 @@ LlmChatView( For example, the [custom styles example app][custom-ex] uses this feature to implement an app with a Halloween theme: +例如,[custom styles 示例应用][custom-ex] 用此功能实现万圣节主题应用: + ![Halloween-themed demo app](/assets/images/docs/ai-toolkit/demo-app.png) For a complete list of the styles available in the `LlmChatViewStyle` class, @@ -587,10 +757,16 @@ of the voice recorder using the `voiceNoteRecorderStyle` parameter of the `LlmChatViewStyle` class, which is demonstrated in the [styles example][styles-ex]. +`LlmChatViewStyle` 可用样式完整列表请参阅 [参考文档][reference documentation]。 +还可用 `LlmChatViewStyle` 的 `voiceNoteRecorderStyle` 自定义录音机外观,见 [styles 示例][styles-ex]。 + To see custom styles in action, in addition to the [custom styles example][custom-ex] and the [styles example][styles-ex], check out the [dark mode example][] and the [demo app][]. +除 [custom styles 示例][custom-ex] 与 [styles 示例][styles-ex] 外, +还可参阅 [dark mode 示例][dark mode example] 与 [演示应用][demo app]。 + [custom-ex]: {{site.github}}/flutter/ai/blob/main/example/lib/custom_styles/custom_styles.dart [styles-ex]: {{site.github}}/flutter/ai/blob/main/example/lib/styles/styles.dart @@ -602,11 +778,16 @@ mode example][] and the [demo app][]. ## Chat without UI +## 无 UI 聊天 + You don't have to use the chat view to access the functionality of the underlying provider. In addition to being able to simply call it with whatever proprietary interface it provides, you can also use it with the [LlmProvider interface][]. +不必使用聊天视图即可访问底层提供商功能。 +除使用其专有接口直接调用外,也可通过 [LlmProvider 接口][LlmProvider interface] 使用。 + [LlmProvider interface]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html @@ -616,6 +797,10 @@ your database with your current food preferences. Pressing the button allows you to preview the recommended changes and decide whether you'd like to apply them or not: +例如,recipes 示例应用在编辑食谱页面提供 Magic 按钮, +用于根据当前食物偏好更新数据库中的现有食谱。 +点按按钮可预览建议的更改并决定是否应用: + ![User decides whether to update recipe in database](/assets/images/docs/ai-toolkit/apply-changes-decision.png) @@ -624,6 +809,9 @@ would insert spurious user messages and LLM responses into the user's chat history, the Edit Recipe page instead creates its own provider and uses it directly: +编辑食谱页面不使用应用聊天部分同一提供商(否则会在用户聊天历史中插入多余消息与回复), +而是创建自有提供商并直接使用: + ```dart class _EditRecipePageState extends State { ... @@ -681,19 +869,29 @@ convenient, you can also accomplish the same thing by calling `generateStream`, which allows you to reuse an existing provider without affecting the chat history. +调用 `sendMessageStream` 会在提供商历史中创建条目,但因未关联聊天视图而不会显示。 +也可调用 `generateStream` 复用现有提供商而不影响聊天历史。 + To see this in action, check out the [Edit Recipe page][] of the recipes example. +实践请参阅 recipes 示例的 [Edit Recipe 页面][Edit Recipe page]。 + [Edit Recipe page]: {{site.github}}/flutter/ai/blob/main/example/lib/recipes/pages/edit_recipe_page.dart ## Rerouting prompts +## 重路由提示词 + If you'd like to debug, log, or manipulate the connection between the chat view and the underlying provider, you can do so with an implementation of an [`LlmStreamGenerator`][] function. You then pass that function to the `LlmChatView` in the `messageSender` parameter: +若要调试、记录或操控聊天视图与底层提供商之间的连接, +可实现 [`LlmStreamGenerator`][`LlmStreamGenerator`] 函数,并通过 `messageSender` 参数传给 `LlmChatView`: + [`LlmStreamGenerator`]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmStreamGenerator.html @@ -741,7 +939,13 @@ the underlying provider. If you don't, it won't get the message. This capability allows you to do advanced things like routing to a provider dynamically or Retrieval Augmented Generation (RAG). +此示例记录往返的用户提示词与 LLM 回复。 +将函数作为 `messageSender` 时,你须负责调用底层提供商,否则消息不会送达。 +借此可实现动态路由到提供商或检索增强生成(RAG)等高级能力。 + To see this in action, check out the [logging example app][]. +实践请参阅 [logging 示例应用][logging example app]。 + [logging example app]: {{site.github}}/flutter/ai/blob/main/example/lib/logging/logging.dart diff --git a/sites/docs/src/content/ai/ai-toolkit/index.md b/sites/docs/src/content/ai/ai-toolkit/index.md index afb27c69e9..5f1048216d 100644 --- a/sites/docs/src/content/ai/ai-toolkit/index.md +++ b/sites/docs/src/content/ai/ai-toolkit/index.md @@ -1,61 +1,117 @@ --- -title: Flutter AI Toolkit +# title: Flutter AI Toolkit +title: Flutter AI 工具包 sidenav: ai -shortTitle: AI Toolkit +# shortTitle: AI Toolkit +shortTitle: AI 工具包 +# description: > +# Learn how to add the AI Toolkit chatbot +# to your Flutter application. description: > - Learn how to add the AI Toolkit chatbot - to your Flutter application. + 了解如何将 AI 工具包聊天机器人添加到你的 Flutter 应用中。 next: - title: User experience + # title: User experience + title: 用户体验 path: /ai/ai-toolkit/user-experience +ai-translated: true --- Hello and welcome to the Flutter AI Toolkit! +你好,欢迎使用 Flutter AI 工具包! + The AI Toolkit is a set of AI chat-related widgets that make it easy to add an AI chat window to your Flutter app. The AI Toolkit is organized around an abstract LLM provider API to make it easy to swap out the LLM provider that you'd like your chat provider to use. Out of the box, it comes with support for [Firebase AI Logic][]. +AI 工具包是一组 AI 聊天相关 widget,便于在 Flutter 应用中添加 AI 聊天窗口。 +工具包围绕抽象的 LLM 提供商 API 组织,便于更换聊天所用的 LLM 提供商。 +开箱即支持 [Firebase AI Logic][Firebase AI Logic]。 + [Firebase AI Logic]: https://firebase.google.com/docs/ai-logic ## Key features +## 主要特性 + * **Multiturn chat**: Maintains context across multiple interactions. + + **多轮聊天**:在多次交互间保持上下文。 + * **Streaming responses**: Displays AI responses in real-time as they are generated. + + **流式传输**:在生成过程中实时显示 AI 回复。 + * **Rich text display**: Supports formatted text in chat messages. + + **富文本显示**:支持聊天消息中的格式化文本。 + * **Voice input**: Allows users to input prompts using speech. + + **语音输入**:让用户通过语音输入提示词。 + * **Multimedia attachments**: Enables sending and receiving various media types. + + **多媒体附件**:支持发送与接收多种媒体类型。 + * **Function calling**: Supports tool calls to the LLM provider. + + **函数调用**:支持向 LLM 提供商发起工具调用。 + * **Custom styling**: Offers extensive customization to match your app's design. + + **自定义样式**:提供丰富自定义以匹配应用设计。 + * **Chat serialization/deserialization**: Store and retrieve conversations between app sessions. + + **聊天序列化/反序列化**:在应用会话间存储与恢复对话。 + * **Custom response widgets**: Introduce specialized UI components to present LLM responses. + + **自定义响应 widget**:引入专用 UI 组件展示 LLM 回复。 + * **Pluggable LLM support**: Implement a simple interface to plug in your own LLM. + + **可插拔 LLM 支持**:实现简单接口以接入自有 LLM。 + * **Cross-platform support**: Compatible with Android, iOS, web, and macOS platforms. + **跨平台支持**:兼容 Android、iOS、web 与 macOS。 + ## Demo +## 演示 + Here's what the demo example looks like hosting the AI Toolkit: +托管 AI 工具包的演示示例如下: + AI demo app The [source code for this demo][src-code] is available in the repo on GitHub. +该 [演示源代码][src-code] 可在 GitHub 仓库中获取。 + [src-code]: {{site.github}}/flutter/ai/blob/main/example/lib/demo/demo.dart ## Get started +## 入门 +
        -
      1. Installation +
      2. Installation安装 Add the following dependencies to your `pubspec.yaml` file: +在 `pubspec.yaml` 中添加以下依赖: + ```yaml dependencies: flutter_ai_toolkit: ^latest_version @@ -64,13 +120,17 @@ dependencies: ```
      3. -
      4. Configuration +
      5. Configuration配置 The AI Toolkit supports both the Gemini endpoint (for prototyping) and the Vertex endpoint (for production). Both require a Firebase project and the `firebase_core` package to be initialized, as described in the [Get started with the Gemini API using the Firebase AI Logic SDKs][firebase_ai] docs. +AI 工具包同时支持 Gemini 端点(用于原型)与 Vertex 端点(用于生产)。 +两者均需 Firebase 项目并初始化 `firebase_core` package,详见 +[使用 Firebase AI Logic SDK 开始使用 Gemini API][firebase_ai] 文档。 + [firebase_ai]: https://firebase.google.com/docs/ai-logic/get-started?platform=flutter @@ -78,11 +138,16 @@ Once that's complete, integrate the new Firebase project into your Flutter app using the `flutterfire CLI` tool, as described in the [Add Firebase to your Flutter app][firebase] docs. +完成后,使用 `flutterfire CLI` 将新 Firebase 项目集成到 Flutter 应用,详见 +[将 Firebase 添加到你的 Flutter 应用][firebase] 文档。 + [firebase]: https://firebase.google.com/docs/flutter/setup After following these instructions, you're ready to use Firebase to integrate AI in your Flutter app. Start by initializing Firebase: +按上述说明操作后,即可在 Flutter 应用中通过 Firebase 集成 AI。首先初始化 Firebase: + ```dart import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_ai/firebase_ai.dart'; @@ -105,6 +170,9 @@ With Firebase properly initialized in your Flutter app, you're now ready to create an instance of the Firebase provider. You can do this in two ways. For prototyping, consider the Gemini AI endpoint: +在 Flutter 应用中正确初始化 Firebase 后,即可创建 Firebase provider 实例。有两种方式。 +原型开发可考虑 Gemini AI 端点: + ```dart import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart'; @@ -135,8 +203,13 @@ The `FirebaseProvider` class exposes the Firebase AI Logic SDK to the options][options] from which to choose), but you do not provide an API key. All of that is handled as part of the Firebase project. +`FirebaseProvider` 类向 `LlmChatView` 暴露 Firebase AI Logic SDK。 +注意你需提供模型名称([有多种选项][options] 可选),但无需提供 API 密钥,均由 Firebase 项目处理。 + For production workloads, it's easy to swap in the Firebase Logic AI endpoint: +生产工作负载可轻松切换为 Firebase Logic AI 端点: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -160,6 +233,8 @@ class ChatPage extends StatelessWidget { For a complete example, check out the [gemini.dart] and [vertex.dart][] examples. +完整示例请参阅 [gemini.dart] 与 [vertex.dart][vertex.dart] 示例。 + [options]: https://firebase.google.com/docs/vertex-ai/gemini-models#available-model-names [gemini.dart]: @@ -168,14 +243,18 @@ examples. {{site.github}}/flutter/ai/blob/main/example/lib/vertex/vertex.dart
      6. -
      7. Set up device permissions +
      8. Set up device permissions配置设备权限 To enable your users to take advantage of features like voice input and media attachments, ensure that your app has the necessary permissions: +要启用语音输入与媒体附件等功能,请确保应用具备必要权限: + * **Network access:** To enable network access on macOS, add the following to your `*.entitlements` files: + **网络访问:** 在 macOS 上,向 `*.entitlements` 文件添加: + ```xml @@ -189,6 +268,8 @@ attachments, ensure that your app has the necessary permissions: To enable network access on Android, ensure that your `AndroidManifest.xml` file contains the following: + 在 Android 上,确保 `AndroidManifest.xml` 包含: + ```xml ... @@ -198,13 +279,24 @@ attachments, ensure that your app has the necessary permissions: * **Microphone access**: Configure according to the [record package's permission setup instructions][record]. + + **麦克风访问**:按 [record package 权限设置说明][record] 配置。 + * **File selection**: Follow the [file_selector plugin's instructions][file]. + + **文件选择**:遵循 [file_selector 插件说明][file]。 + * **Image selection**: To take a picture on _or_ select a picture from their device, refer to the [image_picker plugin's installation instructions][image_picker]. + + **图片选择**:拍照或从设备选图请参阅 [image_picker 插件安装说明][image_picker]。 + * **Web photo**: To take a picture on the web, configure the app according to the [camera plugin's setup instructions][camera]. + **Web 拍照**:在 Web 上拍照请按 [camera 插件设置说明][camera] 配置应用。 + [camera]: {{site.pub-pkg}}/camera#setup [file]: {{site.pub-pkg}}/file_selector#usage [image_picker]: {{site.pub-pkg}}/image_picker#installation @@ -214,6 +306,10 @@ attachments, ensure that your app has the necessary permissions: ## Examples +## 示例 + +**firebase_options.dart** + **firebase_options.dart** To use the [Vertex AI example app][vertex-ex], place your Firebase configuration @@ -221,13 +317,22 @@ details into the `example/lib/firebase_options.dart` file. You can do this with the `flutterfire CLI` tool as described in the [Add Firebase to your Flutter app][add-fb] docs **from within the `example` directory**. +要使用 [Vertex AI 示例应用][vertex-ex],将 Firebase 配置写入 `example/lib/firebase_options.dart`。 +可在 **`example` 目录内** 使用 `flutterfire CLI`,按 +[将 Firebase 添加到你的 Flutter 应用][add-fb] 文档操作。 + :::note Security considerations for `firebase_options.dart` +`firebase_options.dart` 安全注意事项 + If your Flutter app calls Gemini or Vertex AI directly from the client, do not commit `firebase_options.dart` to a public repository. Anyone could reuse your app configuration to send requests to your AI endpoint, consuming quota and potentially causing billing costs. +若 Flutter 应用从客户端直接调用 Gemini 或 Vertex AI,请勿将 `firebase_options.dart` 提交到公开仓库。 +他人可能复用你的应用配置向 AI 端点发送请求,消耗配额并可能产生费用。 + While this guide shows direct client-side calls for simplicity, for production apps, you should route AI requests through a backend service (for example [Cloud Functions for Firebase](https://firebase.google.com/docs/functions), [Cloud @@ -235,17 +340,29 @@ Run](https://cloud.google.com/run), or your own server). In that setup, the backend — not the client — controls access, and including `firebase_options.dart` in your repository is safe. +本指南为简便展示客户端直接调用;生产应用应通过后端服务路由 AI 请求 +(例如 [Cloud Functions for Firebase](https://firebase.google.com/docs/functions)、 +[Cloud Run](https://cloud.google.com/run) 或自有服务器)。 +此时由后端而非客户端控制访问,将 `firebase_options.dart` 纳入仓库是安全的。 + You should also review and follow the [Firebase security checklist](https://firebase.google.com/support/guides/security-checklist). + +你还应查阅并遵循 [Firebase 安全清单](https://firebase.google.com/support/guides/security-checklist)。 ::: ## Feedback +## 反馈 + Along the way, as you use this package, please [log issues and feature requests][file-issues] as well as submit any [code you'd like to contribute][submit]. We want your feedback and your contributions to ensure that the AI Toolkit is just as robust and useful as it can be for your real-world apps. +使用本 package 时,请 [记录 issue 与功能请求][file-issues] 并 [提交你希望贡献的代码][submit]。 +我们希望你的反馈与贡献使 AI 工具包尽可能稳健实用,服务于你的真实应用。 + [add-fb]: https://firebase.google.com/docs/flutter/setup [example apps]: {{site.github}}/flutter/ai/tree/main/example/lib [file-issues]: {{site.github}}/flutter/ai/issues diff --git a/sites/docs/src/content/ai/ai-toolkit/user-experience.md b/sites/docs/src/content/ai/ai-toolkit/user-experience.md index 2d4101ee66..b0d3a3dea7 100644 --- a/sites/docs/src/content/ai/ai-toolkit/user-experience.md +++ b/sites/docs/src/content/ai/ai-toolkit/user-experience.md @@ -1,14 +1,20 @@ --- -title: User experience +# title: User experience +title: 用户体验 sidenav: ai +# description: > +# How the user will experience the AI Toolkit in your app. description: > - How the user will experience the AI Toolkit in your app. + 用户在你的应用中如何体验 AI 工具包。 prev: - title: AI Toolkit overview + # title: AI Toolkit overview + title: AI 工具包概览 path: /ai/ai-toolkit/ next: - title: Feature integration + # title: Feature integration + title: 功能集成 path: /ai/ai-toolkit/feature-integration +ai-translated: true --- The [`LlmChatView`][] widget is the entry point for the interactive chat @@ -16,74 +22,140 @@ experience that AI Toolkit provides. Hosting an instance of the `LlmChatView` enables a number of user experience features that don't require any additional code to use: +[`LlmChatView`][`LlmChatView`] widget 是 AI 工具包提供的交互式聊天体验的入口。 +托管 `LlmChatView` 实例即可启用多项无需额外代码的用户体验功能: + * **Multiline text input**: Allows users to paste long text input or insert new lines into their text as they enter it. + + **多行文本输入**:让用户粘贴长文本或在输入时插入换行。 + * **Voice input**: Allows users to input prompts using speech for ease of use. + + **语音输入**:让用户通过语音输入提示词,便于使用。 + * **Multimedia input**: Enables users to take pictures, send images and other file types and attach URLs as link to online resources. + + **多媒体输入**:支持拍照、发送图片与其他文件类型,并将 URL 作为在线资源链接附加。 + * **Image zoom**: Enables users to zoom into image thumbnails. + + **图片缩放**:支持放大图片缩略图。 + * **Copy to clipboard**: Allows the user to copy the text of a message or a LLM response to the clipboard. + + **复制到剪贴板**:让用户将消息或 LLM 回复文本复制到剪贴板。 + * **Message editing**: Allows the user to edit the most recent message for resubmission to the LLM. + + **消息编辑**:让用户编辑最近一条消息并重新提交给 LLM。 + * **Material and Cupertino**: Adapts to the best practices of both design languages. + **Material 与 Cupertino**:适配两种设计语言的最佳实践。 + [`LlmChatView`]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmChatView-class.html ## Multiline text input +## 多行文本输入 + The user has options when it comes to submitting their prompt once they've finished composing it, which again differs depending on their platform: +用户完成输入后提交提示词的方式因平台而异: + * **Mobile**: Tap the **Submit** button + + **移动端**:点击 **Submit** 按钮 + * **Web**: Press **Enter** or tap the **Submit** button + + **Web**:按 **Enter** 或点击 **Submit** 按钮 + * **Desktop**: Press **Enter** or tap the **Submit** button + **桌面端**:按 **Enter** 或点击 **Submit** 按钮 + In addition, the chat view supports text prompts with embedded newlines in them. If the user has existing text with newlines, they can paste them into the prompt text field as normal. +此外,聊天视图支持包含嵌入换行的文本提示词。 +若用户已有带换行的文本,可照常粘贴到提示词文本框。 + If they'd like to embed newlines into their prompt manually as they enter it, they can do so. The gesture for that activity differs based on the platform they're using: +若要在输入时手动插入换行,也可操作;手势因平台而异: + * **Mobile**: Tap Return key on the virtual keyboard + + **移动端**:点按虚拟键盘上的 Return 键 + * **Web**: Press `Shift+Enter` + + **Web**:按 `Shift+Enter` + * **Desktop**: Press `Shift+Enter` + **桌面端**:按 `Shift+Enter` + These options look like the following: +各平台效果如下: + **Web and Desktop**: +**Web 与桌面端**: + ![Screenshot of entering text on desktop](/assets/images/docs/ai-toolkit/desktop-enter-text.png) **Mobile**: +**移动端**: + ![Screenshot of entering text on mobile](/assets/images/docs/ai-toolkit/mobile-enter-text.png) ## Voice input +## 语音输入 + In addition to text input the chat view can take an audio recording as input by tapping the Mic button, which is visible when no text has yet been entered. +除文本输入外,在未输入文字时可见 **Mic** 按钮,点按即可录音作为输入。 + Tap the **Mic** button to start the recording: +点按 **Mic** 开始录音: + ![Screenshot of entering text](/assets/images/docs/ai-toolkit/enter-textfield.png) Select the **Stop** button to translate the user's voice input into text: +选择 **Stop** 将语音转为文本: + This text can then be edited, augmented and submitted as normal. +随后可照常编辑、补充并提交该文本。 + ![Screenshot of entered voice](/assets/images/docs/ai-toolkit/enter-voice-into-textfield.png) ## Multimedia input +## 多媒体输入 + ![Textfield containing "Testing, testing, one, two, three"](/assets/images/docs/ai-toolkit/multi-media-testing-testing.png) @@ -92,72 +164,111 @@ underlying LLM. The user can select the **Plus** button to the left of the text input and choose from the **Take Photo**, **Image Gallery**, **Attach File** and **Attach Link** icons: +聊天视图还可接收图片与文件并传给底层 LLM。 +用户可点按文本输入左侧的 **Plus** 按钮, +选择 **Take Photo**、**Image Gallery**、**Attach File** 与 **Attach Link** 图标: + ![Screenshot of the 4 icons](/assets/images/docs/ai-toolkit/multi-media-icons.png) The **Take Photo** button allows the user to use their device's camera to take a photo: +**Take Photo** 可让用户使用设备相机拍照: + ![Selfie image](/assets/images/docs/ai-toolkit/selfie.png) Select the **Image Gallery** button to let the user upload from their device's image gallery: +选择 **Image Gallery** 可从设备图库上传: + ![Download image from gallery](/assets/images/docs/ai-toolkit/download-from-gallery.png) Select the **Attach File** button to let the user select a file of any type available on their device, like a PDF or TXT file. +选择 **Attach File** 可选择设备上任意类型文件,如 PDF 或 TXT。 + Select the **Attach Link** button to let the user enter a link to a web page or an online file. +选择 **Attach Link** 可输入网页或在线文件链接。 + Once a photo, image, file, or link has been selected, it becomes an attachment and shows up as a thumbnail associated with the currently active prompt: +选定照片、图片、文件或链接后成为附件, +并以缩略图显示在当前提示词旁: + ![Thumbnails of images](/assets/images/docs/ai-toolkit/image-thumbnails.png) The user can remove an attachment by clicking the **X** button on the thumbnail. +用户可点按缩略图上的 **X** 移除附件。 + ## Image zoom +## 图片缩放 + The user can zoom into an image thumbnail by tapping it: +用户可点按图片缩略图放大查看: + ![Zoomed image](/assets/images/docs/ai-toolkit/image-zoom.png) Pressing the **Esc** key or tapping anywhere outside the image dismisses the zoomed image. +按 **Esc** 或在图片外任意处点按可关闭放大视图。 + ## Copy to clipboard +## 复制到剪贴板 + The user can copy any text prompt or LLM response in their current chat in a variety of ways. On the desktop or the web, the user can mouse to select the text on their screen and copy it to the clipboard as normal: +用户可通过多种方式复制当前聊天中的文本提示词或 LLM 回复。 +在桌面或 Web 上,可用鼠标选中文本并照常复制到剪贴板: + ![Copy to clipboard](/assets/images/docs/ai-toolkit/copy-to-clipboard.png) In addition, at the bottom of each prompt or response, the user can select the **Copy** button that pops up when they hover their mouse: +此外,每条提示词或回复底部在鼠标悬停时会显示 **Copy** 按钮: + ![Select the copy button](/assets/images/docs/ai-toolkit/chatbot-prompt.png) On mobile platforms, the user can long-tap a prompt or response and choose the Copy option: +在移动平台上,可长按提示词或回复并选择 Copy: + ![Long tap to see the copy button](/assets/images/docs/ai-toolkit/long-tap-choose-copy.png) ## Message editing +## 消息编辑 + If the user would like to edit their last prompt and cause the LLM to take another run at it, they can do so. On the desktop, the user can tap the **Edit** button alongside the **Copy** button for their most recent prompt: +若用户想编辑最近一条提示词并让 LLM 重新生成回复,可以这样做。 +在桌面上,可在最近一条提示词旁点按 **Edit**(与 **Copy** 并列): + ![How to edit prompt](/assets/images/docs/ai-toolkit/how-to-edit-prompt.png) On a mobile device, the user can long-tap and get access to the **Edit** option on their most recent prompt: +在移动设备上,可长按最近一条提示词以访问 **Edit**: + ![How to access edit menu](/assets/images/docs/ai-toolkit/accessing-edit-menu.png) @@ -166,6 +277,9 @@ both the user's last prompt and the LLM's last response from the chat history, puts the text of the prompt into the text field, and provides an Editing indicator: +点按 **Edit** 后进入编辑模式:从聊天历史中移除用户最近提示词与 LLM 最近回复, +将提示词文本放入文本框,并显示编辑指示器: + ![How to exit editing mode](/assets/images/docs/ai-toolkit/how-to-exit-editing-mode.png) @@ -174,13 +288,22 @@ have the LLM produce a response as normal. Or, if they change their mind, they can tap the **X** near the Editing indicator to cancel their edit and restore their previous LLM response. +编辑模式下,用户可修改提示词并提交以让 LLM 正常生成回复。 +若改变主意,可点按编辑指示器旁的 **X** 取消编辑并恢复先前的 LLM 回复。 + ## Material and Cupertino +## Material 与 Cupertino + When the `LlmChatView` widget is hosted in a [Material app][], it uses facilities provided by the Material design language, such as Material's [`TextField`][]. Likewise, when hosted in a [Cupertino app][], it uses those facilities, such as [`CupertinoTextField`][]. +当 `LlmChatView` 托管于 [Material app][Material app] 时, +会使用 Material 设计语言提供的设施,如 Material 的 [`TextField`][`TextField`]。 +同样,托管于 [Cupertino app][Cupertino app] 时使用 [`CupertinoTextField`][`CupertinoTextField`] 等设施。 + ![Cupertino example app](/assets/images/docs/ai-toolkit/cupertino-chat-app.png) However, while the chat view supports both the Material and Cupertino app types, @@ -188,6 +311,9 @@ it doesn't automatically adopt the associated themes. Instead, that's set by the `style` property of the `LlmChatView` as described in the [Custom styling][] documentation. +聊天视图虽支持 Material 与 Cupertino 应用类型,但不会自动采用关联主题。 +主题由 `LlmChatView` 的 `style` 属性设置,详见 [自定义样式][Custom styling] 文档。 + [Cupertino app]: {{site.api}}/flutter/cupertino/CupertinoApp-class.html [`CupertinoTextField`]: {{site.api}}/flutter/cupertino/CupertinoTextField-class.html diff --git a/sites/docs/src/content/ai/best-practices/developer-experience.md b/sites/docs/src/content/ai/best-practices/developer-experience.md index b85d2987bc..19fb764661 100644 --- a/sites/docs/src/content/ai/best-practices/developer-experience.md +++ b/sites/docs/src/content/ai/best-practices/developer-experience.md @@ -1,38 +1,70 @@ --- -title: Developer experience +# title: Developer experience +title: 开发者体验 sidenav: ai +# description: > +# Learn how to use spec-driven development and Gemini to plan, code, and +# iterate on high-quality Flutter applications. description: > - Learn how to use spec-driven development and Gemini to plan, code, and - iterate on high-quality Flutter applications. + 了解如何使用规格驱动开发与 Gemini 来规划、编码并迭代高质量的 Flutter 应用。 prev: - title: Mode of interaction + # title: Mode of interaction + title: 交互模式 path: /ai/best-practices/mode-of-interaction +ai-translated: true --- Generative AI is not just useful for implementing features in your app; it's also useful for generating the code to implement those features. +生成式 AI 不仅有助于在应用中实现功能,也有助于生成实现这些功能的代码。 + Unfortunately, it's just as easy as prompting an AI coding agent to "build a Flutter app that solves crossword puzzles." I'm sure that prompt would yield something, but I doubt very much that it would give us the powerful AI-assisted, user-validated combination the Crossword Companion provides. +不幸的是,向 AI 编码智能体提示「构建一个能解决填字游戏的 Flutter 应用」同样容易。 +我确信那条提示词会产出一些东西, +但我很怀疑它能给出 Crossword Companion 所提供的那种强大的 AI 辅助与用户校验组合。 + With better prompting, however, the sample app was implemented with Gemini 2.5 Pro for the bulk of the functionality and Gemini 3 Pro Preview to add the final touches. The process to get the best results from both models was the same: +不过,借助更好的提示词, +示例应用通过 Gemini 2.5 Pro 实现了大部分功能, +用 Gemini 3 Pro Preview 做了最后润色。 +从两个模型获得最佳结果的流程相同: + - Plan + + 规划 (Plan) + - Code + + 编码 (Code) + - Validate + + 验证 (Validate) + - Iterate + 迭代 (Iterate) + ### Plan +### 规划 + The goal of the planning process is to kick off the coding process with enough detail to let the agent know what you have in mind. The Crossword Companion planning process was started with the following prompt: +规划过程的目标是以足够细节启动编码过程,让智能体了解你的想法。 +Crossword Companion 的规划过程从以下提示词开始: + ```plaintext I'd like to create a file called requirements.md in the plans folder at the root of the project. here's a description of the project: @@ -43,10 +75,16 @@ Ask any questions you may have before you get started. This prompt, with a little bit of Q&A, manual edits by a human, and some updates during the coding process, yielded [the requirements file][requirements]. +这条提示词,加上少量问答、人工编辑以及编码过程中的一些更新, +产出了[需求文件][requirements]。 + Before jumping into architectural design, the Gemini CLI was asked to initialize the GEMINI.md rules file and then to update it with a list of architectural principles: +在进入架构设计之前,我们让 Gemini CLI 初始化 GEMINI.md 规则文件, +然后用一系列架构原则更新它: + ```plaintext DRY (Don't Repeat Yourself) – eliminate duplicated logic by extracting shared utilities and modules. @@ -72,20 +110,33 @@ provides the set of rules you want it to remember for any activity. Gemini was running inside of an empty Flutter app project, so the `/init` command documented how to build, test and run it, which was useful during coding. +GEMINI.md 文件会加载到你用 Gemini 创建的每条新提示词中; +它提供你希望 Gemini 在任何活动中记住的规则集。 +Gemini 运行在一个空的 Flutter 应用项目中, +因此 `/init` 命令记录了如何构建、测试和运行,这在编码过程中很有用。 + If you're building something more than a sample, I also recommend adding something for test-driven development: +如果你构建的不只是示例,我还建议加入测试驱动开发相关内容: + ```markdown - **TDD (Test-Driven Development)** - write the tests first; the implementation code isn't done until the tests pass. + +- **TDD(Test-Driven Development,测试驱动开发)** - 先写测试;测试通过前实现代码不算完成。 ``` This helps to build guardrails to ensure the coding agent is writing solid code over time. +这有助于建立护栏,确保编码智能体长期写出扎实代码。 + With the requirements and rules in place, prompting for the design.md file was next: +需求和规则就绪后,下一步是提示生成 design.md 文件: + ```plaintext great. i'd like to work on the design with you to be created in a design.md file to be stored in the plans folder. please use the @GEMINI.md and @requirements.md files as input. ask any questions you may have before you get started. ``` @@ -93,6 +144,8 @@ great. i'd like to work on the design with you to be created in a design.md file After inspecting and editing the generated app design, Gemini was prompted to break it down into [tasks][tasks-spec]: +检查并编辑生成的应用设计后,我们提示 Gemini 将其拆解为[任务][tasks-spec]: + ```plaintext please read the files in the @specs folder and create a corresponding tasks.md file in the same folder that lays out a set of tasks and subtasks representing the functionality of this app. lay out the top-level tasks as minimal new functionality that the user can see in the running app, step-by-step as each top-level task is completed. each top-level task should include sub-tasks for creating and running tests and updating the @README.md with a description of the current functionality of the app. ask any questions you may have before you get started. ``` @@ -104,16 +157,29 @@ your expectations. This is called "Spec-Driven Development" and it's currently the best way we know of to upgrade your process from "vibe coding" to "AI-assisted software development." +这一切都在写任何代码之前完成。 +你不必拆成独立文件,但通过认真考虑需求、设计和任务拆解,你是在帮助智能体提供符合你预期的结果。 +这称为「规格驱动开发(Spec-Driven Development)」, +目前是我们所知的将流程从「vibe coding」升级为「AI 辅助软件开发」的最佳方式。 + Also, the sentence that says "ask any questions you may have before you get started" is a great way for the agent to clarify anything that it doesn't understand instead of just making up the answers as it goes. It's also useful to help you to decide on details you might not otherwise have considered. +此外,「在开始之前,如有任何问题请先提问」 +这句话是让智能体澄清不理解之处、而不是一路编造答案的好方法。 +它也有助于你决定原本可能未考虑到的细节。 + ### Code +### 编码 + With the requirements, rules, design and tasks in place, kicking off the coding part is easy: +需求、规则、设计和任务就绪后,启动编码部分很简单: + ```plaintext Read the @tasks.md file and implement the first milestone. ``` @@ -121,31 +187,63 @@ Read the @tasks.md file and implement the first milestone. You can watch the coding agent at work, jumping in to correct it as it works, or just let it go. Either way, when it's done, it's time to check its work. +你可以观察编码智能体工作,在其工作时介入纠正,也可以放手让它跑。 +无论哪种方式,完成后都该检查它的工作。 + ### Validate +### 验证 + At this point, you have some code and (in the world outside of samples) some tests. To validate, ask yourself some questions: +此时你已有一些代码,(在示例之外的世界里)还有一些测试。验证时,问自己几个问题: + - Does the analyzer show it to be free of errors? Of warnings? + + 分析器是否显示无错误?无警告? + - Does the app run? + + 应用能否运行? + - Does it have the features you asked for? Do they work? + + 是否具备你要求的功能?它们是否正常工作? + - Do the tests pass? + + 测试是否通过? + - Does the code pass your review? + 代码是否通过你的审查? + The answers to these questions are the input for the next phase. +这些问题的答案是下一阶段的输入。 + ### Iterate +### 迭代 + Gather the issues that need to be addressed and hand the ones that need fixing back to the coding agent, iterating between it coding and your validation until you get to a good place from a functional point of view. +收集需要处理的问题,把需要修复的交回编码智能体, +在它编码与你的验证之间迭代,直到功能上达到满意状态。 + Now take another pass through validation from an architectural principles point of view, spinning up a new agent to check the code. By clearing out the agent's context, you remove the biases the original agent gathered choosing what code to write in the first place. To ground it on just the code changes the agent has just made, use a prompt like this: +现在从架构原则角度再做一轮验证,启动新智能体检查代码。 +清空智能体上下文可消除原智能体在最初选择写哪些代码时积累的偏见。 +若只让它关注智能体刚做的代码变更,可使用类似这样的提示词: + ```plaintext Use git diff to find the new code and check it against the architectural principles listed here: @GEMINI.md. Make recommendations for important improvements. ``` @@ -153,6 +251,8 @@ Use git diff to find the new code and check it against the architectural princip Doing this a few times keeps the code in good shape for AI agents and humans alike. +多做几次能让代码对 AI 智能体和人类都保持良好状态。 + [requirements]: {{site.github}}/flutter/demos/blob/main/crossword_companion/specs/requirements.md [tasks-spec]: {{site.github}}/flutter/demos/blob/main/crossword_companion/specs/tasks.md diff --git a/sites/docs/src/content/ai/best-practices/index.md b/sites/docs/src/content/ai/best-practices/index.md index 96666921ef..638abafd2c 100644 --- a/sites/docs/src/content/ai/best-practices/index.md +++ b/sites/docs/src/content/ai/best-practices/index.md @@ -1,14 +1,21 @@ --- -title: Flutter AI best practices +# title: Flutter AI best practices +title: Flutter AI 最佳实践 sidenav: ai -shortTitle: AI best practices -breadcrumb: Best practices +# shortTitle: AI best practices +shortTitle: AI 最佳实践 +# breadcrumb: Best practices +breadcrumb: 最佳实践 +# description: > +# Learn best practices for building AI-powered Flutter apps using guardrails to +# verify and correct AI-generated data. description: > - Learn best practices for building AI-powered Flutter apps using guardrails to - verify and correct AI-generated data. + 了解如何使用护栏构建 AI 驱动的 Flutter 应用,以验证并纠正 AI 生成的数据。 next: - title: Prompting - path: /ai/best-practices/prompting + # title: Prompting + title: 提示词 + path: /ai/best-practices/prompting +ai-translated: true --- @@ -18,46 +25,85 @@ target multiple platforms. And if you're harnessing Gemini to implement features in your app, the Firebase AI Logic SDK makes that simple, with an easy-to-use API, and secure, by keeping the API keys out of your code. +Flutter 与 AI 在多个层面都能很好地配合。 +如果你用 AI 生成 Flutter 代码,只需为单个应用生成一次代码,即可面向多个平台。 +如果你借助 Gemini 在应用中实现功能, +Firebase AI Logic SDK 会让这一切变得简单: +API 易用,且通过将 API 密钥排除在代码之外来保证安全。 + If you're new to AI for either of these two use cases, you should know: as good as it is (and the Gemini 3 Pro Preview is *very* good), AI still makes mistakes. If you're using AI to write your code, then you can use guardrails to keep AI on track using tools like the Flutter analyzer and unit tests. +如果你对这两种用例中的 AI 还不熟悉, +需要知道:尽管 AI 已经很强(Gemini 3 Pro Preview **非常** 出色),它仍会犯错。 +如果你用 AI 写代码,可以用 Flutter 分析器和单元测试等工具建立护栏,让 AI 保持在正轨上。 + But what do you do when you're using AI to implement the features in your app, knowing that sometimes it's going to get things wrong? Or, to quote a friend of mine: +但当你用 AI 实现应用中的功能,并且知道它有时会出错时,你该怎么办? +用我一位朋友的话来说就是: + ***Morgan's Law*** *"Eventually, due to the nature of sampling from a probability distribution, [AI] will fail to do the thing that must be done."* *–Brett Morgan, Flutter Developer Relations Engineer, July, 2025\.* +***Morgan 定律 (Morgan's Law)*** +*「最终,由于从概率分布中采样的本质,[AI] 会无法完成必须完成的事情。」* +*—— Brett Morgan,Flutter 开发者关系工程师,2025 年 7 月* + The good news is that, just as you can use developer tools to build guardrails around the AI writing your code, you can use Flutter to build guardrails around the AI you use to implement your features. The [Crossword Companion app][crossword-app] was built to demonstrate these techniques. + +好消息是,就像你可以用开发者工具为写代码的 AI 建立护栏一样, +你也可以用 Flutter 为实现功能的 AI 建立护栏。 +[Crossword Companion 应用][crossword-app] 就是为了演示这些技术而构建的。 + Crossword Companion app interface showing a 5-step setup process starting
 with selecting a crossword image. + The goal of the Crossword Companion app is not to help you cheat at mini-crosswords – although it's darn good at that – but to illustrate how to channel the power of AI using Flutter. As an example, the first thing you do when running the app is upload the screenshot of a mini-crossword puzzle. When you press the **Next** button, the AI uses that image to infer the size, contents and clues of the puzzle: + +Crossword Companion 应用的目标不是帮你在迷你填字游戏中作弊—— +尽管它确实很擅长—— +而是说明如何用 Flutter 驾驭 AI 的力量。 +例如,运行应用时你首先要上传迷你填字游戏谜题的截图。 +按下 **Next** 按钮后,AI 会根据该图像推断谜题的尺寸、内容和线索: + Crossword Companion app showing a 5x5 grid with settings incorrectly
 displaying 4 rows and 5 columns. + Notice that while the crossword puzzle is a 5x5 grid, the AI says it's 4x5. Because we know that mistakes happen (apparently AIs are only human, too), we built the app to allow the user to verify and correct the AI-generated data. That's important; bad data leads to bad results. +请注意,虽然填字游戏是 5×5 网格,AI 却说是 4×5。 +因为我们知道错误会发生(显然 AI 也只是“人类”), +我们让应用允许用户验证并纠正 AI 生成的数据。 +这很重要;坏数据会导致坏结果。 + So this write-up is not about the app in detail but rather about the best practices to use when you're building your own AI apps with Flutter. So let's get to it! +因此,本文不会详细介绍该应用,而是介绍你用 Flutter 构建自己的 AI 应用时应遵循的最佳实践。 +那就开始吧! + [crossword-app]: {{site.github}}/flutter/demos/tree/main/crossword_companion diff --git a/sites/docs/src/content/ai/best-practices/mode-of-interaction.md b/sites/docs/src/content/ai/best-practices/mode-of-interaction.md index a3e04461c0..f5239daf6a 100644 --- a/sites/docs/src/content/ai/best-practices/mode-of-interaction.md +++ b/sites/docs/src/content/ai/best-practices/mode-of-interaction.md @@ -1,15 +1,21 @@ --- -title: Mode of interaction +# title: Mode of interaction +title: 交互模式 sidenav: ai +# description: > +# Learn to balance LLM capabilities with traditional code and implement +# guardrails to manage nondeterministic AI behavior. description: > - Learn to balance LLM capabilities with traditional code and implement - guardrails to manage nondeterministic AI behavior. + 了解如何平衡 LLM 能力与传统代码,并实现护栏以管理非确定性的 AI 行为。 prev: - title: Tool calls (aka function calls) + # title: Tool calls (aka function calls) + title: 工具调用(又称函数调用) path: /ai/best-practices/tool-calls next: - title: Developer experience + # title: Developer experience + title: 开发者体验 path: /ai/best-practices/developer-experience +ai-translated: true --- @@ -18,22 +24,38 @@ function. Given the same set of inputs in the same order, a function acts predictably. We can write tests and inject faults and harden a function for a wide variety of inputs. +把对 LLM 的请求想成调用函数是一个错误。 +在相同顺序下给定相同输入,函数行为可预测。 +我们可以编写测试、注入故障,并针对多种输入加固函数。 + An LLM is not like that. A better way to think about it is as if the LLM were a user and to treat the data we get from them as such. Like a user, an LLM is nondeterministic, often wrong (partially or wholly) and sometimes plain random. To guard our apps under these conditions, we need to build the same guardrails around LLM input as we do around user input. +LLM 并非如此。更好的做法是把 LLM 当作用户,并同样对待从它获得的数据。 +与用户一样,LLM 是非确定性的,常常出错(部分或全部), +有时纯属随机。要在这些条件下保护应用, +我们需要围绕 LLM 输入建立与用户输入相同的护栏。 + If we can do that successfully, then we can bring extraordinary abilities to apps in the form of problem solving and creativity that can rival that of a human. +若能做到这一点,我们就能为应用带来可与人类媲美的解题与创造力等非凡能力。 + ### Separation of concerns +### 关注点分离 + LLMs are good at some things and bad at others; the key is to bring them into your apps for the good while mitigating the bad. As an example, let's consider the task list in the Crossword Companion: +LLM 有擅长与不擅长之事;关键是在应用中发挥长处、减轻短处。 +以 Crossword Companion 中的任务列表为例: + Crossword task list showing solved clues in green with confidence
@@ -49,44 +71,84 @@ between. No amount of prompting could convince it to update the tasks as it
 went. You'll see the same behavior with modern AI agents managing their own task
 lists; that's just where we are in the evolution of LLMs at the moment.
 
+任务列表是需要求解的线索集合。
+目标是用任务列表中的颜色和解法在求解过程中展示进度。
+最初实现为模型提供了管理任务列表的工具,要求它在进行时更新进度。
+Flash 无法以此方式解谜,Pro 可以。
+不幸的是,它大块求解,只记得更新一两次任务列表,中间间隔很长。
+无论怎么提示都无法让它边做边更新任务。
+你会在现代 AI 智能体管理自己的任务列表时看到同样行为;
+这正是当前 LLM 演进所处的阶段。
+
 So how do we get consistent, deterministic updates of the task list? Take task
 management out of the LLM's hands and handle it in the code.
 
+那么如何一致、确定地更新任务列表?
+把任务管理从 LLM 手中拿走,在代码中处理。
+
 To generalize, before applying an LLM solution to a problem you're facing, ask yourself whether an LLM is the best tool for the job. Is human-like problem solving and creativity worth the tradeoff in unpredictability?
 
+概括而言,在对你面临的问题应用 LLM 方案之前,先问自己 LLM 是否最适合该工作。
+类人的解题与创造力是否值得不可预测性的代价?
+
 The answer to that question comes with experimentation. Here are some examples
 from the sample:
 
-| Task                                              | LLM Suitability                                              | Code Suitability                                                                  |
+答案需要实验。以下是示例中的一些例子:
+
+| <t>Task</t><t>任务</t> | <t>LLM Suitability</t><t>LLM 适用性</t> | <t>Code Suitability</t><t>代码适用性</t> |
 | ------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------- |
-| **Parsing the grid for size, contents and clues** | Great for an LLM by using  vision and language understanding | Difficult to write the code to do this                                            |
-| **Validating grid contents**                      | Possible to do with another LLM checking the work            | Easier for a human to glance at and adjust                                        |
-| **Handling the task list**                        | An LLM is unlikely to do this consistently                   | Easy to write the code to loop through a task list, updating as it goes           |
-| **Solving each clue**                             | Great for an LLM using language understanding and generation | Difficult to do given real world clues that depend on word play, names, and slang |
-| **Resolving conflicts**                           | An LLM is inconsistent on this kind of looping               | Easy for a human to glance at and adjust                                          |
+| **Parsing the grid for size, contents and clues** | Great for an LLM by using vision and language understanding | Difficult to write the code to do this |
+| **解析网格的尺寸、内容与线索** | 借助视觉与语言理解,非常适合 LLM | 用代码实现很困难 |
+| **Validating grid contents** | Possible to do with another LLM checking the work | Easier for a human to glance at and adjust |
+| **验证网格内容** | 可用另一个 LLM 检查工作 | 人类扫一眼并调整更容易 |
+| **Handling the task list** | An LLM is unlikely to do this consistently | Easy to write the code to loop through a task list, updating as it goes |
+| **处理任务列表** | LLM 很难一致完成 | 用代码遍历任务列表并边做边更新很容易 |
+| **Solving each clue** | Great for an LLM using language understanding and generation | Difficult to do given real world clues that depend on word play, names, and slang |
+| **求解每条线索** | 借助语言理解与生成,非常适合 LLM | 现实线索依赖文字游戏、人名、俚语时很难用代码做 |
+| **Resolving conflicts** | An LLM is inconsistent on this kind of looping | Easy for a human to glance at and adjust |
+| **解决冲突** | LLM 在这种循环上不一致 | 人类扫一眼并调整很容易 |
 
 It's a judgement call for sure, but if you can reasonably write the code to do
 it, your results will be predictable. However, if writing the code would be
 unreasonably difficult, then consider an LLM, knowing you'll have to build the
 guardrails like we did in the sample.
 
+这当然是判断题,但若能合理用代码实现,结果就可预测。
+若写代码不合理地困难,则考虑 LLM,并知道你必须像示例中那样建立护栏。
+
 ### Ask vs agent
 
+### 询问 (Ask) 与智能体 (Agent)
+
 There's more than just one pivot to consider besides code vs. LLM. Models
 operate in roughly two modes: +# Learn how to build and optimize generative AI prompts in Flutter using system +# instructions, dynamic parameters, and versioning techniques. description: > - Learn how to build and optimize generative AI prompts in Flutter using system - instructions, dynamic parameters, and versioning techniques. + 了解如何在 Flutter 中使用系统指令、动态参数与版本化技术构建并优化生成式 AI 提示词。 prev: - title: Flutter AI best practices + # title: Flutter AI best practices + title: Flutter AI 最佳实践 path: /ai/best-practices next: - title: Structure & output + # title: Structure & output + title: 结构 & 输出 path: /ai/best-practices/structure-output +ai-translated: true --- @@ -21,6 +27,11 @@ neural network trained on a large set of human language to produce a Large Language Model (LLM). At this point, the best models (like Google Gemini) are trained on what is essentially the entire internet. +假设你已用 Firebase 项目及配置好 Flutter 应用以使用 Firebase AI Logic SDK +(可在 [README][crossword-readme] 中了解),即可开始使用生成式 AI。 +生成式 AI 是机器学习 (ML) 的一个分支,用在大规模人类语言上训练的神经网络产出大语言模型 (LLM)。 +目前最好的模型(如 Google Gemini)训练数据本质上覆盖整个互联网。 + At that scale, a model trained with that much data has created models that can interpret human language and produce useful human language outputs. By now I'm sure you've used [the Gemini chat app][gemini-app] (or ChatGPT or Claude or @@ -28,8 +39,15 @@ other chat apps), so you know that if you talk to an LLM using vague language, you're likely to get vague, often incorrect, results. If you want to get good results, you'll have to use good prompts. +在这种规模下,用如此多数据训练的模型能够解读人类语言并产出有用的人类语言输出。 +你肯定用过 [Gemini 聊天应用][gemini-app](或 ChatGPT、Claude 等), +因此知道若用模糊语言与 LLM 对话,往往得到模糊且常错误的结果。 +要获得好结果,就得用好提示词。 + ### Prompt construction +### 提示词构建 + A prompt is the input you provide to an LLM to get the output you want. It will include text as well zero or more files, like images or PDF files. If you're building chat into your app, then the user will be entering the prompts (and @@ -38,8 +56,15 @@ you're using an LLM to implement the features of your app, like parsing an image for crossword puzzle data, then you're going to be building the prompts yourself. How you build them matters. +提示词是你提供给 LLM 以获得期望输出的输入,可包含文本以及零个或多个文件(如图像或 PDF)。 +若你在应用中构建聊天,用户将输入提示词([Flutter AI Toolkit][ai-toolkit] 有助于构建聊天 UI)。 +若你用 LLM 实现应用功能(如从图像解析填字游戏数据),则需自己构建提示词。 +如何构建很重要。 + As an example, in building the Crossword Companion, the original clue solving prompt looked like this: +例如,构建 Crossword Companion 时,最初的线索求解提示词如下: + ```dart You are a crossword puzzle solver. Your goal is to solve the puzzle by filling in the grid with the correct answers. Given the current state of the crossword grid and a single clue, provide the answer for that clue. The answer should be a single word, returned in a JSON object that matches the following schema: '{"type": "object", "properties": {"answer": {"type": "string"}}}'. @@ -54,13 +79,26 @@ ${clue.number} ${clue.direction == ClueDirection.across ? 'Across' : 'Down'}: ${ This prompt isn't all bad – it has some useful pieces: +这条提示词并非一无是处——它有一些有用部分: + - **Persona:** the phrase "You are a crossword puzzle solver" narrows the model's focus + + **角色 (Persona):**「You are a crossword puzzle solver」缩小模型关注点 + - **Context:** the current state of the puzzle + + **上下文 (Context):** 谜题的当前状态 + - **Query:** asking for a solution to a clue + + **查询 (Query):** 请求某条线索的解答 + - **Format:** provide the output in JSON so the result could be parsed programmatically + **格式 (Format):** 以 JSON 输出结果,供程序解析 + However, because of the two-dimensional nature of the data, this is a hard prompt for some models to solve. The results from Gemini 2.5 Flash (the more efficient of the models available at the time) were inconsistent. The quality of @@ -69,9 +107,17 @@ expensive to obtain. Debugging revealed that Pro was essentially solving the entire puzzle every time it was called, responding with just the solution to a single clue. +然而,由于数据的二维性质,对部分模型这是难解的提示词。 +Gemini 2.5 Flash(当时可用模型中更高效者)结果不一致。 +Gemini 2.5 Pro 质量优秀但更慢更贵。 +调试发现 Pro 每次调用几乎解完整张谜题,却只返回单条线索的答案。 + What was needed was the efficiency of Flash with the quality of Pro. To do that required some work on the prompt: +需要的是 Flash 的效率与 Pro 的质量。 +为此需要改进提示词: + ```markdown Your task is to solve the following crossword clue. @@ -90,6 +136,11 @@ two-dimensional grid, the input was narrowed to the length requirement and a pattern, such as "_ R _ Y". These simplifications produce high quality results from Flash that come back quickly enough to make it [fun to watch][crossword-demo]. + +该提示词要求解线索、提供重要上下文并指定输出格式。 +不再传入整个二维网格状态,输入收窄为长度要求与模式(如 "_ R _ Y")。 +这些简化让 Flash 产出高质量且足够快的结果,使观看过程[有趣][crossword-demo]。 + Crossword Companion interface showing a partially solved grid and clues
@@ -97,15 +148,23 @@ with AI-generated answers and confidence scores ### Layering your prompts +### 分层提示词 + The prompt used to solve the clues is not the only prompt the model sees. It also has the system instruction (also known as the system message or the system prompt) which is set as part of model instance creation. Think of the system instruction as "this is what you do" while the individual prompts are "now do this." +用于求解线索的提示词不是模型看到的唯一提示词。 +还有系统指令(也称 system message 或 system prompt),在创建模型实例时设置。 +可将系统指令视为「这是你要做的」,而各条提示词是「现在做这个」。 + Here is the partial system instruction for the clue solver model (you'll see the rest later): +以下是线索求解模型的部分系统指令(其余稍后可见): + ```dart final clueSolverSystemInstruction = ''' @@ -126,6 +185,8 @@ You are an expert crossword puzzle solver. Given the model we want to use and the system instruction, we now have everything we need to create an instance: +有了要用的模型与系统指令,即可创建实例: + ```dart // The model for solving clues. _clueSolverModel = FirebaseAI.googleAI().generativeModel( @@ -138,12 +199,18 @@ _clueSolverModel = FirebaseAI.googleAI().generativeModel( While the system instruction is often static, the individual prompts are usually created dynamically based on data. +系统指令常为静态,各条提示词通常根据数据动态创建。 + ### Parameterizing your prompts +### 参数化提示词 + Each clue solver prompt is created using the text from the clue, the target length of the answer and the pattern so far given previously solved clues, such as "_R_Y": +每条线索求解提示词用线索文本、答案目标长度以及此前已解线索形成的模式(如 "_R_Y")创建: + ```dart String getSolverPrompt(Clue clue, int length, String pattern) => ''' @@ -161,6 +228,8 @@ Return your answer and confidence score in the required JSON format. With the prompt in hand, we can pass it along to the model for our clue answer: +有了提示词,即可传给模型获取线索答案: + ```dart final result = await _clueSolverModel.generateContent( prompt: getSolverPrompt(clue, length, pattern), @@ -169,6 +238,8 @@ final result = await _clueSolverModel.generateContent( ### Prompt versioning +### 提示词版本化 + This basic app keeps the prompt strings in code. This makes them hard to track down and update. For production apps, it's better to keep your prompts separated from the code, @@ -177,6 +248,11 @@ One way to arrange prompt files is to use [the Google dotprompt format][dotprompt], which allows you to write `.prompt` files that look like this: +这个基础应用把提示词字符串放在代码里,难以查找和更新。 +生产应用最好将提示词与代码分离,例如打包为 Flutter 资源。 +组织提示词文件的一种方式是用 [Google dotprompt 格式][dotprompt], +可编写如下 `.prompt` 文件: + ```markdown --- model: googleai/gemini-2.5-flash @@ -199,6 +275,8 @@ Text: {{text}} To expand a `.prompt` file for use in your Dart and Flutter projects, you can use [the dotprompt_dart package][dotprompt-dart]. +要在 Dart 和 Flutter 项目中展开 `.prompt` 文件, +可使用 [dotprompt_dart package][dotprompt-dart]。 [crossword-readme]: {{site.github}}/flutter/demos/tree/main/crossword_companion diff --git a/sites/docs/src/content/ai/best-practices/structure-output.md b/sites/docs/src/content/ai/best-practices/structure-output.md index d80bea60f0..3893074582 100644 --- a/sites/docs/src/content/ai/best-practices/structure-output.md +++ b/sites/docs/src/content/ai/best-practices/structure-output.md @@ -1,33 +1,66 @@ --- -title: Structure & output +# title: Structure & output +title: 结构与输出 sidenav: ai +# description: > +# Learn how to use structured input and output schemas to receive reliable, +# parsable JSON data from an LLM. description: > - Learn how to use structured input and output schemas to receive reliable, - parsable JSON data from an LLM. + 了解如何使用结构化输入与输出 schema,从 LLM 获得可靠、可解析的 JSON 数据。 prev: - title: Prompting + # title: Prompting + title: 提示词 path: /ai/best-practices/prompting next: - title: Tool calls (aka function calls) + # title: Tool calls (aka function calls) + title: 工具调用(又称函数调用) path: /ai/best-practices/tool-calls +ai-translated: true --- When you're writing programs against an LLM, you want to provide unambiguous input and get unambiguous output. +针对 LLM 编写程序时,你希望提供明确的输入并获得明确的输出。 + ### Structured input +### 结构化输入 + As input, an LLM can take pretty much anything you can render as text. That includes free form text and semi-structured text like Markdown, but also includes structured formats like CSV, JSON, and XML. If you have data with structure, format the data with that structure and the LLM is going to give you better results. +作为输入,LLM 几乎可以接收任何能渲染为文本的内容,包括自由文本、Markdown 等半结构化文本,以及 CSV、JSON、XML 等结构化格式。若数据有结构,用该结构格式化数据,LLM 会给出更好结果。 + In addition to structured text input, you can also pass binary data, like images or PDFs. In the sample, the app passes the crossword puzzle screenshot images to Gemini for it to infer the grid data: +除结构化文本输入外,还可传入二进制数据(如图像或 PDF)。在示例中,应用将填字游戏截图传给 Gemini 以推断网格数据: + +```dart +final imageParts = []; +for (final image in images) { + final imageBytes = await image.readAsBytes(); + final mimeType = lookupMimeType(image.path, headerBytes: imageBytes)!; + imageParts.add(InlineDataPart(mimeType, imageBytes)); +} + +final content = [ + Content.multi([ + TextPart(_crosswordPrompt), + ...imageParts, + ]), +]; + +final response = await _crosswordModel.generateContent(content); +... +``` + ```dart final imageParts = []; for (final image in images) { @@ -50,14 +83,23 @@ final response = await _crosswordModel.generateContent(content); This code passes the prompt and the images to Gemini as part of the same request. +该代码在同一请求中将提示词与图像一并传给 Gemini。 + ### Structured output +### 结构化输出 + An LLM can have a harder time with structured output than with structured input. You want to be clear and thorough when asking the model for JSON output to ensure you get something that you can reliably parse in your apps. +LLM 处理结构化输出往往比结构化输入更难。 +向模型请求 JSON 输出时要清晰详尽,确保得到应用中可靠解析的内容。 + Start by initializing the model instance with your expected output format: +先在初始化模型实例时指定期望的输出格式: + ```dart // the schema for the clue solver output static final _crosswordSchema = Schema( @@ -119,6 +161,8 @@ _crosswordModel = FirebaseAI.googleAI().generativeModel( And while this might be enough, the most reliable results come when you also specify the output schema in the system instruction: +仅这样可能已足够,最可靠的结果来自在系统指令中也指定输出 schema: + ```dart final _crosswordPrompt = ''' @@ -131,8 +175,30 @@ The JSON schema is as follows: ${jsonEncode(_crosswordSchema.toJson())} ''' ``` +```dart +final _crosswordPrompt = +''' +分析以下填字游戏图像,返回表示网格尺寸、内容与线索的 JSON 对象。图像可能包含同一谜题的不同部分(如网格、横向线索、纵向线索)。将它们合并为完整谜题。 + +JSON schema 如下:${jsonEncode(_crosswordSchema.toJson())} +''' +``` + Now you can parse the model's text response as JSON: +现在可将模型的文本响应解析为 JSON: + +```dart +final response = await _crosswordModel.generateContent(content); + +final json = jsonDecode(response.text!); +final width = json['width'] as int; +final height = json['height'] as int; +final gridData = json['grid'] as List; +final cluesData = json['clues'] as Map; +... +``` + ```dart final response = await _crosswordModel.generateContent(content); @@ -147,3 +213,5 @@ final cluesData = json['clues'] as Map; Reliable JSON output from the model is what makes it possible to integrate AI into your app. The data might or might not be correct, but it will be in a format your app can work with. + +模型可靠的 JSON 输出使 AI 能集成到应用中。数据可能对也可能错,但会是应用可处理的格式。 diff --git a/sites/docs/src/content/ai/best-practices/tool-calls.md b/sites/docs/src/content/ai/best-practices/tool-calls.md index e7590b68ce..3ee321555d 100644 --- a/sites/docs/src/content/ai/best-practices/tool-calls.md +++ b/sites/docs/src/content/ai/best-practices/tool-calls.md @@ -1,36 +1,58 @@ --- -title: Tool calls (aka function calls) +# title: Tool calls (aka function calls) +title: 工具调用(又称函数调用) sidenav: ai -shortTitle: Tool calls +# shortTitle: Tool calls +shortTitle: 工具调用 +# description: > +# Learn how to implement tool calling, manage agentic loops, and incorporate +# human-in-the-loop interactions using the Firebase AI Logic SDK. description: > - Learn how to implement tool calling, manage agentic loops, and incorporate - human-in-the-loop interactions using the Firebase AI Logic SDK. + 了解如何使用 Firebase AI Logic SDK 实现工具调用、管理智能体循环,并融入人机回圈交互。 prev: - title: Structure & output + # title: Structure & output + title: 结构 & 输出 path: /ai/best-practices/structure-output next: - title: Mode of interaction + # title: Mode of interaction + title: 交互模式 path: /ai/best-practices/mode-of-interaction +ai-translated: true --- While it's true that LLMs are trained essentially on the entire internet, they -don't know everything. They know what was on the public internet the day they +don't know everything. + +诚然,LLM 的训练数据本质上覆盖整个互联网,但它们并非无所不知。 + +They know what was on the public internet the day they were trained, but they don't know anything more recent than that. They don't know anything that's private to you or your organization. And even things they do know can easily get tangled up with other things they know. +它们知道训练当日公开互联网上的内容,但不知道更晚的信息; +不知道你或组织的私有信息;即使已知内容也易与其他知识纠缠。 + For those scenarios, and many others, we often provide an LLM with one or more tools. +在这些场景及许多其他情况下,我们常向 LLM 提供一个或多个工具。 + ### Tool defined +### 工具的定义 + A tool is a name, a description and a JSON schema for the format of the input data when the LLM "calls" the tool. For example, if we prompt the LLM to "Reduce the carbs in Grandma's All America Breakfast recipe", it won't know what grandma's recipe is unless we give it a "lookupRecipe" tool that takes a query string we can use to look up the recipe. +工具由名称、描述以及 LLM「调用」工具时输入数据格式的 JSON schema 组成。 +例如,若我们提示 LLM「减少 Grandma's All America Breakfast 食谱中的碳水」, +它不知道奶奶的菜谱是什么,除非我们提供接受查询字符串的 `lookupRecipe` 工具来查找食谱。 + Conceptually, a tool is something we give the LLM to call when it needs that data or service. The way an LLM calls a tool is by responding to the app's request with a specially formatted message that means "tool call". A tool call @@ -38,25 +60,46 @@ message includes the name and JSON arguments for the tool. The app handles the tool call and bundles the result in another LLM request, to which the LLM then responds. -This can go on for a while. An app can configure a model instance with any +概念上,工具是我们在 LLM 需要该数据或服务时供其调用的东西。 +LLM 调用工具的方式是:以表示「工具调用」的特殊格式消息响应应用请求; +工具调用消息包含工具名称与 JSON 参数。 +应用处理工具调用,将结果打包进另一条 LLM 请求,LLM 再据此响应。 + +This can go on for a while. + +这可能持续多轮。 + +An app can configure a model instance with any number of tools (although the LLM does better with a smaller set of targeted tools that don't overlap in functionality). The LLM can bundle up any number of tool calls in its response and can take any number of tool results in a request. The LLM consolidates multiple round-trips for prompts and tool call results via a stack of messages that form a history of request/response pairs. +应用可为模型实例配置任意数量的工具(尽管 LLM 在较小、目标明确且功能不重叠的工具集上表现更好)。 +LLM 可在响应中打包任意数量的工具调用,也可在请求中接收任意数量的工具结果。 +LLM 通过构成请求/响应对历史的消息栈,整合提示词与工具调用结果的多轮往返。 + When it's done with the tool calls, the LLM returns its final response, for example "Here's a version of Grandma's All American Breakfast recipe that's high on protein and low on carbs…". +工具调用结束后,LLM 返回最终响应,例如「这是 Grandma's All American Breakfast 食谱的高蛋白低碳水版本……」。 + ### Gemini functions +### Gemini 函数 + In the Firebase AI Logic SDK, a tool is called a "function", but it's the same thing. In the sample, the clue solver model is configured with a function to look up word details. If the LLM wants details about a word to help with the solving process, calling the function provides it with data from [the Free Dictionary API][dictionary-api]: +在 Firebase AI Logic SDK 中,工具称为「function(函数)」,但含义相同。 +在示例中,线索求解模型配置了查找单词详情的函数。 +若 LLM 需要单词详情辅助求解,调用该函数可从 [Free Dictionary API][dictionary-api] 获取数据: + ```json [ { @@ -88,6 +131,8 @@ Dictionary API][dictionary-api]: The app has a Dart function that does the look up: +应用中有执行查找的 Dart 函数: + ```dart // Look up the metadata for a word in the dictionary API. Future> _getWordMetadataFromApi(String word) async { @@ -104,6 +149,8 @@ Future> _getWordMetadataFromApi(String word) async { The model is configured with the look up function as part of initialization: +模型在初始化时配置了该查找函数: + ```dart // The model for solving clues. _clueSolverModel = FirebaseAI.googleAI().generativeModel( @@ -128,6 +175,8 @@ _clueSolverModel = FirebaseAI.googleAI().generativeModel( For reliability, it's also a good idea to list the tools in the system instruction: +为可靠起见,在系统指令中列出工具也是好主意: + ````dart static String get clueSolverSystemInstruction => ''' @@ -162,27 +211,54 @@ When the app makes a request, the model now has a tool to use when it decides that it will be helpful. To support tool calls, we need to implement an agentic loop. +应用发起请求时,模型在认为有帮助时可使用该工具。要支持工具调用,需实现智能体循环。 + ## The Agentic Loop +## 智能体循环 + An LLM is functionally stateless, which means that you have to give it all of the data it needs with every request. For a request that's just the prompt and any files you want to send along, the Firebase AI Logic SDK exposes the `generateContent` method on your model instance. +LLM 在功能上是无状态的,意味着每次请求都必须提供其所需的全部数据。 +若请求仅含提示词及附带文件,Firebase AI Logic SDK 在模型实例上提供 `generateContent` 方法。 + However, tool calling requires a history of messages that form the initial prompt, as well as the response/request pairs that make up tool calls and tool results. To support this Firebase Logic AI provides a "chat" object to gather the history. We use it to build the agentic loop: +然而,工具调用需要由初始提示词以及构成工具调用与工具结果的响应/请求对组成的消息历史。 +为此 Firebase AI Logic 提供用于收集历史的「chat」对象。 +我们用它构建智能体循环: + - Start a chat to hold the message history across multiple request/response pairs + + 启动 chat,在多个请求/响应对之间保存消息历史 + - Gather the tool results for any tool calls it provides + + 收集其提供的任意工具调用的工具结果 + - Bundle the tool results into a new request + + 将工具结果打包进新请求 + - Loop until the model provides a response without tool calls + + 循环直到模型返回不含工具调用的响应 + - Return the text accumulated across all responses + 返回所有响应累积的文本 + Here's that algorithm expressed as an extension method on the `GenerativeModel` class so we can call it just like we call `generateContent`: +以下算法以 `GenerativeModel` 类的扩展方法表达,可像调用 `generateContent` 一样调用: + ```dart extension on GenerativeModel { Future generateContentWithFunctions({ @@ -236,6 +312,9 @@ extension on GenerativeModel { This method takes a prompt and a callback for handling the specific tool calls, which the sample calls to handle the word lookup function: +该方法接收提示词与处理具体工具调用的回调; +示例用它处理单词查找函数: + ```dart await _clueSolverModel.generateContentWithFunctions( prompt: getSolverPrompt(clue, length, pattern), @@ -251,13 +330,21 @@ await _clueSolverModel.generateContentWithFunctions( Structured output makes an LLM useful to program against but it's the tools that turn an LLM into an "agent" (more on this in the Mode of interaction section). +结构化输出使 LLM 便于编程对接,而工具将 LLM 变为「智能体 (agent)」(详见交互模式一节)。 + ### Structured output and tool calls +### 结构化输出与工具调用 + Combining structured output and tool calls produce a powerful combination. In the sample, the clue solver has a tool to look up word details. It's also asked to return JSON that bundles the solution with a confidence score, both of which are shown in the app's task list: +结合结构化输出与工具调用是强大组合。 +在示例中,线索求解器有查找单词详情的工具,还被要求返回捆绑解法与置信度的 JSON, +二者显示在应用任务列表中: + App task list showing crossword clues followed by bold answers and
@@ -266,6 +353,8 @@ confidence scores in parentheses Unfortunately, as of this writing, combining structured output and functions when using the Firebase AI Logic SDK produces an exception: +遗憾的是,截至本文撰写时,在 Firebase AI Logic SDK 中同时启用结构化输出与函数会抛出异常: + ```plaintext Function calling with a response mime type: 'application/json' is unsupported ``` @@ -274,6 +363,8 @@ As a (hopefully temporary) work-around to this issue, the sample removes the structured output configuration, instead using a tool called `returnResult` to simulate structured output: +作为(希望临时的)变通,示例移除结构化输出配置,改用名为 `returnResult` 的工具模拟结构化输出: + ```dart // The model for solving clues. _clueSolverModel = FirebaseAI.googleAI().generativeModel( @@ -303,6 +394,8 @@ _clueSolverModel = FirebaseAI.googleAI().generativeModel( The `returnResult` method is also mentioned in the system instruction: +`returnResult` 也在系统指令中说明: + ````dart static String get clueSolverSystemInstruction => ''' @@ -328,6 +421,9 @@ ${jsonEncode(_returnResultFunction.toJson())} When the model calls `returnResult`, the sample caches the result, which the `solveClue` looks up after calling `generateContentWithFunctions`: +模型调用 `returnResult` 时,示例缓存结果, +`solveClue` 在调用 `generateContentWithFunctions` 后读取: + ```dart // Buffer for the result of the clue solving process. final _returnResult = {}; @@ -367,23 +463,40 @@ Future solveClue(Clue clue, int length, String pattern) async { We have to work a little harder to get the combo of structured output and tool calls using Firebase AI Logic, but the results are worth it! +用 Firebase AI Logic 同时获得结构化输出与工具调用需要多费些功夫,但结果值得。 + ### Human in the loop +### 人机回圈 (Human in the loop) + So far, we've seen tools used for gathering data and formatting output. We can also use them to get a human involved. +到目前为止,工具用于收集数据与格式化输出。 +也可用它让人类参与。 + As an example, sometimes when the sample will pass in a pattern the solution should take – like "_R_Y" – the model wants to suggest an answer that doesn't fit this pattern – like "RENT". A conflict like this is a good time to ask for help from the user: + +例如,示例有时传入解法应匹配的模式(如 "_R_Y"), +模型却想建议不匹配的模式(如 "RENT")。 +此类冲突适合向用户求助: + Crossword Companion app displaying a Conflict Detected dialog asking for
 user input to resolve a clue pattern + This is called putting the "human in the loop" and it's yet another way for humans and LLMs to collaborate. Flutter and the Firebase AI Logic SDK make this easy to do. First, the sample defines a function and configures the model: +这称为「人机回圈(human in the loop)」,是人类与 LLM 协作的又一方式。 +Flutter 与 Firebase AI Logic SDK 使其实现简单。 +首先,示例定义函数并配置模型: + ````dart // The new function to let the LLM resolve solution conflicts @@ -441,6 +554,8 @@ ${jsonEncode(_resolveConflictFunction.toJson())} Now when the model sees a conflict, it will call the tool: +模型发现冲突时会调用该工具: + ```dart // handle the LLM's request to resolve the conflict await _clueSolverModel.generateContentWithFunctions( @@ -482,22 +597,39 @@ their time with the UI while the sample waits on the `Future` returned message history and the most recent request, which in this case happens to be data gathered interactively from the user. +示例通过 `onConflict` 实现处理该工具,调用 `showDialog` 收集用户数据。 +这一切发生在智能体循环中间,但没问题——模型并未等待; +它已向应用的初始请求返回响应。 +用户可慢慢操作 UI,示例等待 `showDialog` 返回的 `Future`。 +完成后,模型借助消息历史与最近请求(此处为与用户交互收集的数据)从断点继续。 + A modal dialog box is a simple way to put the human in the loop but is not the only way in Flutter to do so. If you'd prefer, an instance of [a `Completer`][completer] lets you set some state in your app that puts it into "gathering data from the user" mode. When the app has the data, it can call `complete` on the `Completer` and resume the agentic loop. +模态对话框是人机回圈 (human in the loop) 的简单方式,但不是 Flutter 的唯一方式。 +若你愿意,[`Completer`][completer] 实例可让应用进入「向用户收集数据」模式; +有数据后对 `Completer` 调用 `complete` 并恢复智能体循环。 + Or, since you own the agentic loop, you can check for a call to a "special" function that indicates that you need to gather data from the user. This kind of special function is sometimes called an "interrupt" and you "resume" the conversation with the model when you have the data from the user. +或者,既然你拥有智能体循环,可检查对「特殊」函数的调用——表示需向用户收集数据。 +此类特殊函数有时称为「interrupt(中断)」,获得用户数据后你「resume(恢复)」与模型的对话。 + Remember that the LLM is stateless. It's not waiting on you, so you can handle the agentic loop in whatever way makes the most sense for your app. You can come back to the LLM with an updated message history and a new prompt at any time, whether it's been a minute or in a month. +请记住 LLM 是无状态的。 +它不会等你,因此可按最适合应用的方式处理智能体循环。 +你可随时带着更新的消息历史与新提示词回到 LLM,无论间隔一分钟还是一个月。 + [dictionary-api]: https://dictionaryapi.dev/ diff --git a/sites/docs/src/content/ai/coding-assistants.md b/sites/docs/src/content/ai/coding-assistants.md index 33004fee4f..cdb1358284 100644 --- a/sites/docs/src/content/ai/coding-assistants.md +++ b/sites/docs/src/content/ai/coding-assistants.md @@ -1,25 +1,45 @@ --- -title: AI Coding Assistants +# title: AI Coding Assistants +title: AI 编程助手 sidenav: ai +# description: > +# Learn how to use AI-powered coding assistants like Antigravity and Gemini CLI +# to accelerate your Flutter development. description: > - Learn how to use AI-powered coding assistants like Antigravity and Gemini CLI - to accelerate your Flutter development. + 了解如何使用 Antigravity、Gemini CLI 等 AI 编程助手来加速 Flutter 开发。 +ai-translated: true --- AI tools are not only features in your app, but can also be powerful assistants in your development workflow. +AI 工具不仅是应用中的功能, +也可以成为你开发流程中的得力助手。 + Tools like Antigravity and Gemini CLI can help you write code faster, understand complex concepts, and reduce boilerplate. +Antigravity、Gemini CLI 等工具能帮你更快写代码、 +理解复杂概念,并减少样板代码。 + ## Antigravity [Antigravity](https://antigravity.google/) is an in-IDE AI agent that can read and write code, run terminal commands, and help you build complex features. Some of its capabilities include: +[Antigravity](https://antigravity.google/) 是一款 IDE 内的 AI 智能体,可以读写代码、运行终端命令,并帮助你构建复杂功能。部分能力包括: + * **Agentic capabilities**: Unlike chat-based assistants, Antigravity can proactively edit files and run terminal commands to complete tasks. + + **智能体能力**:与基于聊天的助手不同,Antigravity 可主动编辑文件并运行终端命令以完成任务。 + * **Complex reasoning**: It can plan and execute multi-step workflows which makes it suitable for larger refactors or feature implementations. + + **复杂推理**:它能规划并执行多步工作流,适合较大规模的重构或功能实现。 + * **Verification**: It can run tests and verify its own changes to ensure correctness. + **验证**:它能运行测试并验证自身修改,以确保正确性。 + @@ -29,20 +49,45 @@ understand complex concepts, and reduce boilerplate. The [Gemini CLI](https://geminicli.com/) is a command-line AI workflow tool. It allows you to interact with Gemini models for a variety of tasks without leaving your development environment. You can use it to: +[Gemini CLI](https://geminicli.com/) 是一款命令行 AI 工作流工具。你无需离开开发环境即可与 Gemini 模型交互以完成多种任务。你可以用它: + * Quickly scaffold a new Flutter widget, Dart function, or a complete app. + + 快速搭建新的 Flutter widget、Dart 函数或完整应用。 + * Use MCP server tools, such as the Dart and Flutter MCP server. + + 使用 MCP 服务器工具,例如 Dart 与 Flutter MCP 服务器。 + * Automate tasks like committing and pushing changes to a Git repository. + 自动化提交并将变更推送到 Git 仓库等任务。 + To get started, visit the [Gemini CLI](https://geminicli.com/) website, or try this [Gemini CLI codelab](https://codelabs.developers.google.com/gemini-cli-hands-on). +入门请访问 [Gemini CLI](https://geminicli.com/) 网站, +或尝试这篇 [Gemini CLI codelab](https://codelabs.developers.google.com/gemini-cli-hands-on)。 + ## Flutter extension for Gemini CLI +## 适用于 Gemini CLI 的 Flutter 扩展 + The [Flutter extension for Gemini CLI]({{site.github}}/gemini-cli-extensions/flutter) combines the [Dart and Flutter MCP server]({{site.dart-site}}/tools/mcp-server) with rules and commands. It uses the default set of [AI rules for Flutter and Dart](/ai/ai-rules), adds commands like `/create-app` and `/modify` to make structured changes to your app, and automatically configures the [Dart and Flutter MCP server]({{site.dart-site}}/tools/mcp-server). +[适用于 Gemini CLI 的 Flutter 扩展]({{site.github}}/gemini-cli-extensions/flutter) +将 [Dart 与 Flutter MCP 服务器]({{site.dart-site}}/tools/mcp-server) 与规则和命令结合。 +它使用默认的 [Flutter 与 Dart AI 规则](/ai/ai-rules), +并添加 `/create-app`、`/modify` 等命令以对应用进行结构化修改, +同时自动配置 [Dart 与 Flutter MCP 服务器]({{site.dart-site}}/tools/mcp-server)。 + You can install it by running the following command: +运行以下命令即可安装: + ```bash gemini extensions install https://github.com/gemini-cli-extensions/flutter ``` To learn more, check out [Flutter extension for Gemini CLI](/ai/gemini-cli-extension). + +了解更多请参阅 [适用于 Gemini CLI 的 Flutter 扩展](/ai/gemini-cli-extension)。 diff --git a/sites/docs/src/content/ai/create-with-ai.md b/sites/docs/src/content/ai/create-with-ai.md index e2845330bd..3401913eb5 100644 --- a/sites/docs/src/content/ai/create-with-ai.md +++ b/sites/docs/src/content/ai/create-with-ai.md @@ -1,56 +1,104 @@ --- -title: Create with AI +# title: Create with AI +title: 使用 AI 创建 sidenav: ai +# description: > +# Learn how to use AI to build Flutter apps, from powerful SDKs that integrate +# AI features directly into your app to tools that accelerate your development +# workflow. description: > - Learn how to use AI to build Flutter apps, from powerful SDKs that integrate - AI features directly into your app to tools that accelerate your development - workflow. + 了解如何使用 AI 构建 Flutter 应用:从将 AI 功能直接集成到应用中的强大 SDK, + 到加速开发工作流的工具。 +ai-translated: true --- This guide covers how you can leverage AI tools to build AI-powered features for your Flutter apps and streamline your Flutter and Dart development. +本指南介绍如何利用 AI 工具为 Flutter 应用构建 AI 驱动功能, +并简化 Flutter 与 Dart 开发流程。 + AI can be used for building AI-powered apps with Flutter and for accelerating your development workflow. +AI 既可用于用 Flutter 构建 AI 驱动应用, +也可用于加速开发工作流。 + You can integrate AI-powered features like natural language understanding and content generation directly into your Flutter app using powerful SDKs, like the Firebase SDK for Generative AI. +你可以使用 Firebase 生成式 AI SDK 等强大 SDK, +将自然语言理解与内容生成等 AI 功能直接集成到 Flutter 应用中。 + You can also use AI tools, such as Gemini Code Assist and Gemini CLI, to help with code generation and scaffolding. +你还可以使用 Gemini Code Assist、Gemini CLI 等 AI 工具, +辅助代码生成与项目脚手架搭建。 + These tools are powered by the Dart and Flutter MCP server, which provides AI with a rich context about your codebase. +这些工具由 Dart 与 Flutter MCP server 驱动, +为 AI 提供关于代码库的丰富上下文。 + The Flutter Extension for Gemini CLI makes it easy to leverage official rules, the MCP server, and custom commands for building your app. +适用于 Gemini CLI 的 Flutter 扩展便于你使用官方规则、 +MCP server 与自定义命令来构建应用。 + Additionally, rules files help fine-tune the AI's behavior and enforce project-specific best practices. +此外,规则文件有助于微调 AI 行为, +并落实项目特定的最佳实践。 + ## Build AI-powered experiences with Flutter +## 使用 Flutter 构建 AI 驱动体验 + Using AI in your Flutter app unlocks new user experiences that allow your app to support natural language understanding and content generation. +在 Flutter 应用中使用 AI 可解锁新用户体验, +使应用支持自然语言理解与内容生成。 + To get started building AI-powered experiences in Flutter, check out these resources: +要在 Flutter 中开始构建 AI 驱动体验,请参阅以下资源: + * [Firebase AI Logic Showcase][] - An application that demonstrates Firebase AI Logic capabilities through a series of interactive demos. + + [Firebase AI Logic Showcase][] — 通过一系列交互式演示展示 Firebase AI Logic 能力的应用。 + * [Firebase AI Logic][] - The official Firebase SDK for using generative AI features directly in Flutter. Compatible with the Gemini Developer API or Vertex AI. To get started, check out the [official documentation][firebase-ai-logic-docs]. + + [Firebase AI Logic][] — 在 Flutter 中直接使用生成式 AI 功能的官方 Firebase SDK。 + 兼容 Gemini Developer API 或 Vertex AI。 + 入门请参阅 [官方文档][firebase-ai-logic-docs]。 + * [Genkit Dart][] - An open-source framework for building AI-powered features in Dart and Flutter with support for multiple model providers, type-safe schemas, and built-in observability. To get started, check out the [quickstart guide][genkit-dart-quickstart]. + + [Genkit Dart][] — 在 Dart 与 Flutter 中构建 AI 功能的开源框架, + 支持多种模型提供商、类型安全 schema 与内置可观测性。入门请参阅 + [快速入门指南][genkit-dart-quickstart]。 + * [Flutter AI Toolkit][] - A sample app with pre-built widgets to help you build AI-powered features in Flutter. + [Flutter AI Toolkit][] — 附带预构建 widget 的示例应用,帮助你在 Flutter 中构建 AI 功能。 + [Firebase AI Logic]: {{site.firebase}}/docs/ai-logic [Firebase AI Logic Showcase]: {{site.github}}/flutter/demos/tree/main/firebase_ai_logic_showcase [firebase-ai-logic-docs]: {{site.firebase}}/docs/ai-logic/get-started @@ -60,12 +108,18 @@ resources: ## AI development tools +## AI 开发工具 + AI isn't only a feature in your app, but can also be a powerful assistant in your development workflow. Tools like [Antigravity][], [Gemini Code Assist][], [Gemini CLI][], [Claude Code][], [Cursor][], and [Windsurf][] can help you write code faster, understand complex concepts, and reduce boilerplate. +AI 不仅是应用中的功能,也可成为开发工作流中的得力助手。 +[Antigravity][]、[Gemini Code Assist][]、[Gemini CLI][]、[Claude Code][]、 +[Cursor][] 与 [Windsurf][] 等工具能帮你更快写代码、理解复杂概念并减少样板代码。 + [Antigravity]: /ai/coding-assistants [Gemini Code Assist]: /ai/coding-assistants [Gemini CLI]: /ai/coding-assistants @@ -75,20 +129,29 @@ concepts, and reduce boilerplate. ### GenUI SDK for Flutter {: #genui } +### 适用于 Flutter 的 GenUI SDK {: #genui } + The GenUI SDK transforms text-based conversations into rich, interactive experiences. Essentially, it acts as an orchestration layer that coordinates the flow of information between your user, your Flutter widgets, and an AI agent. +GenUI SDK 将基于文本的对话转化为丰富的交互体验。 +本质上,它作为编排层,协调用户、Flutter widget 与 AI 智能体之间的信息流。 + :::experimental The `genui` package is in alpha and is likely to change. + +`genui` package 处于 alpha 阶段,可能会变更。 ::: To learn more, visit the [GenUI SDK for Flutter][] documentation. +了解更多请访问 [适用于 Flutter 的 GenUI SDK][GenUI SDK for Flutter] 文档。 + [GenUI SDK for Flutter]: /ai/genui ### Genkit Dart @@ -99,39 +162,78 @@ It provides a structured way to integrate AI features into your app with support for multiple model providers, including Google Gemini, Anthropic Claude, and OpenAI. +[Genkit Dart](https://genkit.dev) 是用于在 Dart 与 Flutter 中构建 AI 应用的开源、与模型无关的框架。 +它提供结构化方式将 AI 功能集成到应用中, +并支持 Google Gemini、Anthropic Claude、OpenAI 等多种模型提供商。 + Key features include: +主要特性包括: + * **Model-agnostic API**: Switch between AI providers with minimal code changes. + + **与模型无关的 API**:以最少代码变更在不同 AI 提供商之间切换。 + * **Type-safe schemas**: Define strongly-typed inputs and outputs for AI interactions using the [`schemantic`](https://pub.dev/packages/schemantic) package. + + **类型安全 schema**:使用 [`schemantic`](https://pub.dev/packages/schemantic) package + 为 AI 交互定义强类型输入与输出。 + * **Flows**: Testable, observable, and deployable functions that wrap AI logic with typed inputs and outputs. + + **Flows**:可测试、可观测、可部署的函数,以类型化输入输出封装 AI 逻辑。 + * **Tools**: Define functions that models can invoke to fetch live data or perform actions. + + **Tools**:定义模型可调用的函数以获取实时数据或执行操作。 + * **Developer UI**: A built-in web UI for testing prompts, viewing execution traces, and debugging flows. + **开发者 UI**:内置 Web UI,用于测试提示词、查看执行轨迹与调试 flow。 + Genkit Dart supports multiple deployment architectures for Flutter, including running AI logic entirely in the app, calling backend flows from Flutter, or proxying model requests through a Genkit backend. +Genkit Dart 支持 Flutter 的多种部署架构, +包括在应用内完全运行 AI 逻辑、从 Flutter 调用后端 flow, +或通过 Genkit 后端代理模型请求。 + To get started, check out the [Genkit Dart quickstart](https://genkit.dev/docs/dart/get-started). +入门请参阅 [Genkit Dart 快速入门](https://genkit.dev/docs/dart/get-started)。 + ### Antigravity [Antigravity](https://antigravity.google/) is an in-IDE AI agent that can read and write code, run terminal commands, and help you build complex features. Some of its capabilities include: +[Antigravity](https://antigravity.google/) 是一款 IDE 内的 AI 智能体, +可以读写代码、运行终端命令,并帮助你构建复杂功能。 +部分能力包括: + * **Agentic capabilities**: Unlike chat-based assistants, Antigravity can proactively edit files and run terminal commands to complete tasks. + + **智能体能力**:与基于聊天的助手不同,Antigravity 可主动编辑文件并运行终端命令以完成任务。 + * **Complex reasoning**: It can plan and execute multi-step workflows which makes it suitable for larger refactors or feature implementations. + + **复杂推理**:它能规划并执行多步工作流,适合较大规模的重构或功能实现。 + * **Verification**: It can run tests and verify its own changes to ensure correctness. + **验证**:它能运行测试并验证自身修改,以确保正确性。 + @@ -139,38 +241,70 @@ include: To learn more, check out the [AI Coding Assistants](/ai/coding-assistants) guide. +了解更多请参阅 [AI 编程助手](/ai/coding-assistants) 指南。 + ### Gemini Code Assist [Gemini Code Assist](https://codeassist.google/) is an AI-powered collaborator available for IDEs like Visual Studio Code, JetBrains IDEs, and Android Studio. It has a deep understanding of your project's codebase and can help you with: +[Gemini Code Assist](https://codeassist.google/) +是适用于 Visual Studio Code、JetBrains IDE、Android Studio 等 IDE 的 AI 协作工具。 +它深度理解项目代码库,可帮助你: + * **Code completion and generation**: It suggests and generates entire blocks of code based on the context of what you're writing. + + **代码补全与生成**:根据你正在编写的内容的上下文建议并生成完整代码块。 + * **In-editor chat**: You can ask questions about your code, Flutter concepts, or best practices directly within your IDE. + + **编辑器内聊天**:你可以在 IDE 内直接询问代码、Flutter 概念或最佳实践相关问题。 + * **Debugging and explanation**: If you encounter an error, you can ask Gemini Code Assist to explain it and suggest a fix. + **调试与解释**:遇到错误时,可请 Gemini Code Assist 解释并建议修复方案。 + To learn more, check out the [AI Coding Assistants](/ai/coding-assistants) guide. +了解更多请参阅 [AI 编程助手](/ai/coding-assistants) 指南。 + ### Gemini CLI The [Gemini CLI](https://geminicli.com/) is a command-line AI workflow tool. It allows you to interact with Gemini models for a variety of tasks without leaving your development environment. You can use it to: +[Gemini CLI](https://geminicli.com/) 是一款命令行 AI 工作流工具。 +你无需离开开发环境即可与 Gemini 模型交互以完成多种任务。 +你可以用它: + * Quickly scaffold a new Flutter widget, Dart function, or a complete app. + + 快速搭建新的 Flutter widget、Dart 函数或完整应用。 + * Use MCP server tools, such as the Dart and Flutter MCP server + + 使用 MCP server 工具,例如 Dart 与 Flutter MCP server + * Automate tasks like committing and pushing changes to a Git repository + 自动化提交并将变更推送到 Git 仓库等任务 + To get started, visit the [Gemini CLI](https://geminicli.com/) website, or try this [Gemini CLI codelab][]. +入门请访问 [Gemini CLI](https://geminicli.com/) 网站,或尝试这篇 [Gemini CLI codelab][]。 + [Gemini CLI codelab]: https://codelabs.developers.google.cn/gemini-cli-hands-on #### Flutter extension for Gemini CLI +#### 适用于 Gemini CLI 的 Flutter 扩展 + The [Flutter extension for Gemini CLI][flutter-extension] combines the [Dart and Flutter MCP server][dart-mcp-dart-docs] with rules and commands. It uses the default set of [AI rules for Flutter and Dart][], @@ -178,8 +312,16 @@ adds commands like `/create-app` and `/modify` to make structured changes to your app, and automatically configures the [Dart and Flutter MCP server][dart-mcp-dart-docs]. +[适用于 Gemini CLI 的 Flutter 扩展][flutter-extension] 将 +[Dart 与 Flutter MCP server][dart-mcp-dart-docs] 与规则和命令结合。 +它使用默认的 [Flutter 与 Dart AI 规则][AI rules for Flutter and Dart], +并添加 `/create-app`、`/modify` 等命令以对应用进行结构化修改, +同时自动配置 [Dart 与 Flutter MCP server][dart-mcp-dart-docs]。 + You can install it by running the following command: +运行以下命令即可安装: + ```bash gemini extensions install https://github.com/gemini-cli-extensions/flutter ``` @@ -187,6 +329,9 @@ gemini extensions install https://github.com/gemini-cli-extensions/flutter To learn more, check out [Flutter extension for Gemini CLI](/ai/gemini-cli-extension). +了解更多请参阅 +[适用于 Gemini CLI 的 Flutter 扩展](/ai/gemini-cli-extension)。 + [flutter-extension]: {{site.github}}/gemini-cli-extensions/flutter [dart-mcp-dart-docs]: /ai/mcp-server [AI rules for Flutter and Dart]: /ai/ai-rules @@ -200,22 +345,51 @@ The MCP (model context protocol) specification outlines how development tools can share the context of a user's code with an AI model, which allows the AI to better understand and interact with the code. +要在 Flutter 开发中提供辅助,AI 工具需要与 Dart 与 Flutter 开发者工具通信。 +Dart 与 Flutter MCP server 促成这一通信。 +MCP(model context protocol,模型上下文协议)规范说明开发工具如何与 AI 模型共享用户代码上下文, +从而使 AI 更好地理解并与代码交互。 + The Dart and Flutter MCP server unlocks the full potential of your AI assistant by connecting it directly to your development environment. It enables the AI to: +Dart 与 Flutter MCP server 将 AI 助手直接连接到开发环境,释放其全部潜力。它使 AI 能够: + * **Introspect the widget tree**: Visualize and debug layout issues in your running app. + + **内省 widget 树**:可视化并调试运行中应用的布局问题。 + * **Manage dependencies**: Search pub.dev for packages and add them to your project. + + **管理依赖**:在 pub.dev 搜索 package 并添加到项目。 + * **Control the runtime**: Trigger hot reloads and restarts to see changes instantly. + + **控制运行时**:触发热重载与重启以即时查看变更。 + * **Fix complex errors**: Analyze static and runtime errors with deep context. + **修复复杂错误**:结合深度上下文分析静态与运行时错误。 + This bridges the gap between the AI's natural language understanding, and Dart and Flutter's suite of developer tools. +这在 AI 的自然语言理解与 Dart、Flutter 开发者工具套件之间架起桥梁。 + To get started, check out the official documentation for the [Dart and Flutter MCP server][dart-mcp-dart-docs]. +入门请参阅 +[Dart 与 Flutter MCP server][dart-mcp-dart-docs] 官方文档。 + ### Rules for Flutter and Dart +### Flutter 与 Dart 规则 + You can use a rules file with AI-powered editors to provide context and instructions to an underlying LLM. To get started, visit the [AI rules for Flutter and Dart][] guide. + +你可以在 AI 驱动的编辑器中使用规则文件, +为底层 LLM 提供上下文与指令。入门请参阅 +[Flutter 与 Dart AI 规则][AI rules for Flutter and Dart] 指南。 diff --git a/sites/docs/src/content/ai/gemini-cli-extension.md b/sites/docs/src/content/ai/gemini-cli-extension.md index 1063681550..703a151eb6 100644 --- a/sites/docs/src/content/ai/gemini-cli-extension.md +++ b/sites/docs/src/content/ai/gemini-cli-extension.md @@ -1,19 +1,28 @@ --- -title: Flutter extension for Gemini CLI +# title: Flutter extension for Gemini CLI +title: 适用于 Gemini CLI 的 Flutter 扩展 sidenav: ai +# description: > +# Learn how to use the Flutter extension for Gemini CLI +# to make structured changes to your app at the command line +# using the Dart and Flutter MCP server. description: > - Learn how to use the Flutter extension for Gemini CLI - to make structured changes to your app at the command line - using the Dart and Flutter MCP server. + 了解如何使用适用于 Gemini CLI 的 Flutter 扩展, + 借助 Dart 与 Flutter MCP server 在命令行对应用进行结构化修改。 +ai-translated: true --- You might be familiar with Gemini CLI, a command-line AI workflow tool that enables you to interact with Gemini AI models without leaving your development environment. -(If you aren’t familiar with Gemini, you can learn more +(If you aren't familiar with Gemini, you can learn more by working through the [Hands on with Gemini][] codelab.) +你可能已熟悉 Gemini CLI—— +一款命令行 AI 工作流工具,让你无需离开开发环境即可与 Gemini AI 模型交互。 +(若尚不熟悉 Gemini,可通过 [Gemini 动手实验][Hands on with Gemini] codelab 了解。) + [Hands on with Gemini]: {{site.codelabs}}/gemini-cli-hands-on AI agents are changing the way we build Flutter apps by @@ -27,45 +36,78 @@ Gemini CLI extensions allow you to build integrations with Gemini CLI and your tools, and the Flutter extension expands on these capabilities. +AI 智能体正在改变我们构建 Flutter 应用的方式, +可协助功能原型、代码评审以及编写与运行测试等任务。 +要有效使用 AI 智能体,需要为其提供上下文与工具访问,使其成为高效的 Flutter 编程助手。 +这正是适用于 Gemini CLI 的 Flutter 扩展的用武之地。 +Gemini CLI 扩展让你将 Gemini CLI 与自有工具集成, +Flutter 扩展则在此基础上扩展能力。 + The Flutter Extension for Gemini CLI provides commands to accelerate app development, follows explicit rules to write high-quality code following Dart and Flutter best practices, and runs tools from the Dart and Flutter MCP server to directly -access Dart and Flutter’s developer tools. You spend less time +access Dart and Flutter's developer tools. You spend less time on setup and more time building high quality Flutter apps. +适用于 Gemini CLI 的 Flutter 扩展提供命令以加速应用开发, +遵循明确规则按 Dart 与 Flutter 最佳实践编写高质量代码, +并运行 Dart 与 Flutter MCP server 中的工具以直接访问 Dart 与 Flutter 开发者工具。 +你可将更少时间花在搭建上,把更多时间用于构建高质量 Flutter 应用。 + The following video showcases [how to build multiplatform apps with Gemini CLI][gemini-cli-video]: +以下视频展示 +[如何使用 Gemini CLI 构建多平台应用][gemini-cli-video]: + [gemini-cli-video]: https://youtu.be/RZPkE5sllck?si=lM0sGs-V6nx7Tw6T ## Prerequisites +## 前提条件 + 1. Install Gemini CLI 0.4.0 or later. You can do this with npm or brew, depending on your platform, preference, and system configuration. + 安装 Gemini CLI 0.4.0 或更高版本。 + 可根据平台、偏好与系统配置使用 npm 或 brew 安装。 + 2. Install the Flutter SDK, which includes the Dart SDK. If Flutter is already installed, make sure that you have the latest versions of Flutter and Dart by running flutter upgrade. -3. Install Git and make sure it’s available on your PATH. + 安装包含 Dart SDK 的 Flutter SDK。 + 若已安装 Flutter,请运行 `flutter upgrade` 确保 Flutter 与 Dart 为最新版本。 + +3. Install Git and make sure it's available on your PATH. + + 安装 Git 并确保其在 PATH 中可用。 ## Get started +## 入门 + :::experimental The Flutter extension for Gemini CLI is in alpha and is likely to change. + +适用于 Gemini CLI 的 Flutter 扩展处于 alpha 阶段,可能会变更。 ::: Once the prerequisites are satisfied, install the Flutter extension for Gemini CLI by using one of the following commands: +满足前提条件后,使用以下命令之一安装适用于 Gemini CLI 的 Flutter 扩展: + 1. To install the current version, run the following: + 要安装当前版本,运行: + ```console gemini extensions install https://github.com/gemini-cli-extensions/flutter ``` @@ -73,50 +115,83 @@ extension for Gemini CLI by using one of the following commands: 2. To install the current version and ensure that future updates are automatically installed, use the `auto-update` tag: + 要安装当前版本并确保未来更新自动安装,使用 `auto-update` 标签: + ```console gemini extensions install https://github.com/gemini-cli-extensions/flutter.git --auto-update ``` + After asking if you are sure you want to proceed, you will see a message that the Flutter extension is installed and enabled. +确认是否继续后,你会看到 Flutter 扩展已安装并启用的消息。 + 3. You can manage the extension with the following commands: + 可使用以下命令管理扩展: + - Update to the latest version: + 更新到最新版本: + ```console gemini extensions update flutter ``` - Uninstall the extension: + 卸载扩展: + ```console gemini extensions uninstall flutter ``` ## Available commands +## 可用命令 + After installing the extension, these commands are available when you open a new Gemini CLI session: +安装扩展后,打开新的 Gemini CLI 会话时即可使用这些命令: + * `/create-app` - Guides you through bootstrapping a new Flutter project with best practices. + + `/create-app` — 引导你按最佳实践搭建新的 Flutter 项目。 + * `/create-package` - Guides you through bootstrapping a new Dart package with best practices. + + `/create-package` — 引导你按最佳实践搭建新的 Dart package。 + * `/modify` - Manages a structured modification session with automated planning. + + `/modify` — 管理带自动规划的结构化修改会话。 + * `/commit` - Automates pre-commit checks and generates a descriptive commit message. + `/commit` — 自动化提交前检查并生成描述性提交消息。 + ## Create an app +## 创建应用 + You can create a new application using the `/create-app` command. This command bootstraps a brand-new, production-ready Flutter app. -It goes beyond flutter create by asking for your app’s purpose, +It goes beyond flutter create by asking for your app's purpose, setting up recommended linter rules, and generating detailed `DESIGN.md` and `IMPLEMENTATION.md` files for your review before any code is written. +你可以使用 `/create-app` 命令创建新应用。 +该命令会搭建全新的、可用于生产的 Flutter 应用。 +它超越 `flutter create`:会询问应用目的、配置推荐的 linter 规则, +并在编写任何代码前生成详细的 `DESIGN.md` 与 `IMPLEMENTATION.md` 供你审阅。 + ```console /create-app ``` @@ -125,7 +200,11 @@ The `DESIGN.md` file is a design document for the app; it specifies the problems that the app solves and provides technical details about how it will work. You can edit this file before you continue with the implementation steps, -allowing you to guide Gemini to build the exact app that you’re looking for. +allowing you to guide Gemini to build the exact app that you're looking for. + +`DESIGN.md` 是应用的设计文档; +它说明应用要解决的问题并提供工作原理的技术细节。你可以在继续实现步骤前编辑该文件, +以引导 Gemini 构建你期望的确切应用。 Once the design is ready, `/create-app` generates an `IMPLEMENTATION.md` file, a step-by-step implementation plan, @@ -137,38 +216,66 @@ After each phase, Gemini will analyze and format the code, run tests, and commit the changes. It also updates this file after it completes a phase in the Journal section. +设计就绪后,`/create-app` 会生成 `IMPLEMENTATION.md`(分步实现计划), +以便迭代实现功能。它会记录进度,便于你暂停与恢复。 +默认情况下,`/create-app` 将计划分为 3–5 个阶段,每阶段为逻辑停止点。 +每阶段结束后,Gemini 会分析并格式化代码、运行测试并提交变更。 +完成某阶段后,它还会在 Journal 部分更新该文件。 + ## Implement features from the plan -After you’ve set up your project, you’re ready to implement +## 按计划实现功能 + +After you've set up your project, you're ready to implement the features in your implementation plan using the generated `IMPLEMENTATION.md` file. Each feature is implemented separately, as outlined in this file. Once it finishes implementing a feature, the Flutter extension will mark it as complete. +项目搭建完成后,即可使用生成的 `IMPLEMENTATION.md` 实现计划中的功能。 +如该文件所述,各功能分别实现。功能实现完成后,Flutter 扩展会将其标为完成。 + Before moving to the next phase, the extension asks for your approval. You can enter the prompt "looks good" to start generating code. +进入下一阶段前,扩展会征求你的批准。 +你可输入提示词「looks good」以开始生成代码。 + ## Modify +## 修改 + To make changes to existing code, the `/modify` command initiates a guided development session. It asks for your goals, offers to create a new branch, and generates a `MODIFICATION_PLAN.md` design doc outlining the proposed modifications and a phased implementation plan. +要对现有代码进行修改,`/modify` 命令会启动引导式开发会话。 +它会询问你的目标、提议创建新分支,并生成 `MODIFICATION_PLAN.md` 设计文档, +概述拟议修改与分阶段实现计划。 + ```console /modify ``` ## Clean up and commit +## 清理并提交 + The final step is to commit the changes using `/commit`. This command prepares your changes before committing them with Git. It automatically runs `dart fix` and `dart format`, runs the analyzer and tests, and then generates a descriptive commit message based on the changes for you to approve. +最后一步是使用 `/commit` 提交变更。 +该命令在通过 Git 提交前准备变更:自动运行 `dart fix` 与 `dart format`、 +运行分析器与测试,然后根据变更生成描述性提交消息供你批准。 + ## Fully loaded with best practices +## 内置最佳实践 + Every interactive chat session includes rules containing best practices for Flutter and Dart development. These rules ensure that Gemini writes high-quality Dart and Flutter code, @@ -176,28 +283,58 @@ interacts with MCP server tools correctly, and follows best practices such as creating unit tests, writing documentation, ensuring accessibility, and more. +每次交互式聊天会话都包含 Flutter 与 Dart 开发最佳实践规则。 +这些规则确保 Gemini 编写高质量 Dart 与 Flutter 代码、 +正确与 MCP server 工具交互,并遵循创建单元测试、编写文档、确保无障碍等最佳实践。 + ## Access to development tools with the Flutter and Dart MCP server +## 通过 Flutter 与 Dart MCP server 访问开发工具 + The Dart and Flutter MCP server is automatically configured when you install the Flutter Extension for Gemini CLI. This allows Gemini CLI and other AI agents to perform common development tasks. For example: -* Analyze and fix errors in your project’s code. +安装适用于 Gemini CLI 的 Flutter 扩展时会自动配置 Dart 与 Flutter MCP server。 +这使 Gemini CLI 与其他 AI 智能体能够执行常见开发任务。例如: + +* Analyze and fix errors in your project's code. + + 分析并修复项目代码中的错误。 + * Introspect and interact with your running application (such as trigger a hot reload, get the selected widget, fetch runtime errors). + + 内省并与运行中的应用交互(如触发热重载、获取选中的 widget、获取运行时错误)。 + * Search `pub.dev` for the best package for your use case. + + 在 `pub.dev` 搜索最适合你场景的 package。 + * Manage package dependencies in your `pubspec.yaml` file. + + 管理 `pubspec.yaml` 中的 package 依赖。 + * Run tests and analyze the results. + 运行测试并分析结果。 + ## Resources +## 资源 + As previously mentioned, this extension is in alpha. If you find a bug, please [file an issue][]. +如前所述,本扩展处于 alpha 阶段。 +若发现 bug,请 [提交 issue][file an issue]。 + You also might want to check out the [Gemini CLI extension][] repo. +你也可以查看 [Gemini CLI 扩展][Gemini CLI extension] 仓库。 + [file an issue]: {{site.github}}/gemini-cli-extensions/flutter/issues [Gemini CLI extension]: {{site.github}}/gemini-cli-extensions/flutter diff --git a/sites/docs/src/content/ai/genui/components.md b/sites/docs/src/content/ai/genui/components.md index fe403262e4..39424a3362 100644 --- a/sites/docs/src/content/ai/genui/components.md +++ b/sites/docs/src/content/ai/genui/components.md @@ -1,35 +1,53 @@ --- -title: GenUI SDK main components and concepts +# title: GenUI SDK main components and concepts +title: GenUI SDK 主要组件与概念 sidenav: ai -breadcrumb: Main components & concepts +# breadcrumb: Main components & concepts +breadcrumb: 主要组件与概念 +# description: >- +# Familiarize yourself with the main components and concepts of the +# Flutter for GenUI SDK. description: >- - Familiarize yourself with the main components and concepts of the - Flutter for GenUI SDK. + 熟悉适用于 Flutter 的 GenUI SDK 的主要组件与概念。 prev: - title: GenUI SDK overview + # title: GenUI SDK overview + title: GenUI SDK 概览 path: /ai/genui next: - title: Get started with the GenUI SDK + # title: Get started with the GenUI SDK + title: GenUI SDK 入门 path: /ai/genui/get-started +ai-translated: true --- :::experimental The `genui` package is in alpha and is likely to change. + +`genui` package 处于 alpha 阶段,可能会变更。 ::: ## Main components +## 主要组件 + The [`genui`][] package is built around the following main components: +[`genui`][] package 围绕以下主要组件构建: + `Conversation` -: The primary facade and entry point for the package. +
        The primary facade and entry point for the package. It includes the `SurfaceController` class, manages the conversation history, and orchestrates the entire generative UI process. +`Conversation` +
        package 的主要门面与入口。 + 包含 `SurfaceController` 类,管理对话历史, + 并编排整个生成式 UI 流程。 + `Catalog` -: A collection of `CatalogItem` objects that defines +
        A collection of `CatalogItem` objects that defines the set of widgets that the AI is allowed to use. The `SurfaceController` supports multiple catalogs, allowing you to organize your widgets into logical groups. @@ -37,68 +55,126 @@ The [`genui`][] package is built around the following main components: to reference), a data schema for its properties, and a builder function to render the Flutter widget. +`Catalog` +
        `CatalogItem` 对象的集合,定义 AI 允许使用的 widget 集。 + `SurfaceController` 支持多个 catalog,便于将 widget 组织成逻辑分组。 + 每个 `CatalogItem` 指定 widget 名称(供 AI 引用)、属性数据 schema, + 以及渲染 Flutter widget 的 builder 函数。 + `DataModel` -: A centralized, observable store for all dynamic UI state. +
        A centralized, observable store for all dynamic UI state. Widgets are _bound_ to data in this model. When data changes, only the widgets that depend on that specific piece of data are rebuilt. +`DataModel` +
        所有动态 UI 状态的集中、可观察存储。 + widget **绑定** 到该模型中的数据; + 数据变化时,仅依赖该数据的 widget 会重建。 + `A2uiTransportAdapter` -: A bridge that parses raw text streams coming from your LLM into +
        A bridge that parses raw text streams coming from your LLM into `A2uiMessage` commands for the `SurfaceController`. +`A2uiTransportAdapter` +
        将来自 LLM 的原始文本流解析为供 `SurfaceController` 使用的 `A2uiMessage` 命令的桥梁。 + `A2uiMessage` -: A message sent from the AI +
        A message sent from the AI (parsed by the `A2uiTransportAdapter`) to the UI, instructing it to perform actions like `createSurface`, `surfaceUpdate`, `dataModelUpdate`, or `deleteSurface`. +`A2uiMessage` +
        由 AI 发送(经 `A2uiTransportAdapter` 解析)给 UI 的消息, + 指示其执行 `createSurface`、`surfaceUpdate`、`dataModelUpdate` 或 `deleteSurface` 等操作。 + `SurfaceController` -: Handles the processing of `A2uiMessage`s, +
        Handles the processing of `A2uiMessage`s, manages the `DataModel`, and maintains the state of UI surfaces. +`SurfaceController` +
        处理 `A2uiMessage`,管理 `DataModel`,并维护 UI surface 的状态。 + ## How it works +## 工作原理 + The `Conversation` manages the interaction cycle: +`Conversation` 管理交互周期: + 1. **User input** + **用户输入** + The user provides a prompt (for example, through a text field). The app calls `conversation.sendMessage()`. + 用户提供提示词(例如通过文本字段)。 + 应用调用 `conversation.sendMessage()`。 + 2. **AI invocation** + **AI 调用** + The `Conversation` sends the user's message to the LLM SDK. + `Conversation` 将用户消息发送给 LLM SDK。 + 3. **AI response** + **AI 响应** + The LLM, guided by the widget schemas provided in its system prompt, sends back responses. + LLM 在其系统提示词提供的 widget schema 引导下返回响应。 + 4. **Stream handling** + **流式传输处理** + The text stream from the LLM SDK is fed into the `A2uiTransportAdapter`. + LLM SDK 的文本流输入 `A2uiTransportAdapter`。 + 5. **UI state update** + **UI 状态更新** + `A2uiMessages` parsed by the adapter are passed to `SurfaceController.handleMessage()`, which updates the UI state and `DataModel`. + 适配器解析的 `A2uiMessages` 传给 `SurfaceController.handleMessage()`, + 更新 UI 状态与 `DataModel`。 + 6. **UI rendering** + **UI 渲染** + The `SurfaceController` broadcasts an update, and any `Surface` widgets listening for that surface ID will rebuild. Widgets are bound to the `DataModel`, so they update automatically when their data changes. + `SurfaceController` 广播更新,监听该 surface ID 的 `Surface` widget 会重建。 + widget 绑定 `DataModel`,数据变化时自动更新。 + 7. **Callbacks** + **回调** + Text responses and errors trigger callbacks on the `Conversation` or are handled by your specific LLM integration flow. + 文本响应与错误在 `Conversation` 上触发回调,或由你的 LLM 集成流程处理。 + 8. **User interaction** + **用户交互** + The user interacts with the newly generated UI (for example, by typing in a text field). This interaction directly updates the `DataModel`. If the interaction is an action (like a button click), @@ -107,12 +183,22 @@ The `Conversation` manages the interaction cycle: a new `UserMessage` containing the current state of the data model and restarts the cycle. + 用户与新生的 UI 交互(例如在文本字段中输入)。 + 交互直接更新 `DataModel`。 + 若为操作(如按钮点击),`Surface` 捕获事件并转发给 `SurfaceController`, + 后者自动创建包含数据模型当前状态的 `UserMessage` 并重启周期。 + {:.steps} For more detailed information on the implementation of GenUI SDK for Flutter, check out the [design doc][]. +有关适用于 Flutter 的 GenUI SDK 实现的更多细节, +请参阅 [设计文档][design doc]。 + The next section walks you through adding `genui` to your app. +下一节将引导你把 `genui` 加入应用。 + [design doc]: {{site.repo.organization}}/genui/blob/main/packages/genui/DESIGN.md [`genui`]: {{site.pub-pkg}}/genui diff --git a/sites/docs/src/content/ai/genui/get-started.md b/sites/docs/src/content/ai/genui/get-started.md index a448efb550..0e56d8c46d 100644 --- a/sites/docs/src/content/ai/genui/get-started.md +++ b/sites/docs/src/content/ai/genui/get-started.md @@ -1,17 +1,25 @@ ---- -title: Get started with the GenUI SDK for Flutter +--- +# title: Get started with the GenUI SDK for Flutter +title: GenUI SDK 入门 sidenav: ai -shortTitle: Get started with the GenUI SDK -breadcrumb: Get started +# shortTitle: Get started with the GenUI SDK +shortTitle: GenUI SDK 入门 +# breadcrumb: Get started +breadcrumb: 入门 +# description: >- +# Learn how to use GenUI SDK for Flutter and add it +# to your existing Flutter app. description: >- - Learn how to use GenUI SDK for Flutter and add it - to your existing Flutter app. + 了解如何使用 GenUI SDK for Flutter,并将其添加到你现有的 Flutter 应用。 next: - title: Input and events + # title: Input and events + title: 输入与事件 path: /ai/genui/input-events prev: - title: GenUI SDK main components & concepts + # title: GenUI SDK main components & concepts + title: GenUI SDK 主要组件与概念 path: /ai/genui/components +ai-translated: true --- This guide explains how to get started with @@ -19,9 +27,14 @@ GenUI SDK for Flutter and its series of packages. The SDK's key components are described in the [main components][] page. +本指南说明如何开始使用 GenUI SDK for Flutter 及其系列包。 +SDK 的关键组件见 [主要组件][main components] 页面。 + :::experimental The `genui` package is in alpha and is likely to change. + +`genui` package 处于 alpha 阶段,可能会变更。 ::: Use the following instructions to add [`genui`][] to your Flutter app. @@ -29,47 +42,77 @@ The code examples show how to perform the instructions on a brand new app created by running [`flutter create`][], but you can follow the same steps for your existing Flutter app. +按以下说明将 [`genui`][] 添加到你的 Flutter 应用。 +代码示例展示如何在运行 [`flutter create`][] 创建的全新应用上操作, +现有 Flutter 应用也可遵循相同步骤。 + [`genui`]: {{site.pub-pkg}}/genui [main components]: /ai/genui/components [`flutter create`]: /reference/create-new-app ## Configure your agent provider +## 配置智能体 (agent) 提供方 + The `genui` package can connect to a variety of agent providers. Available providers include the following: +`genui` package 可连接多种智能体提供方,包括: + **Firebase AI Logic** -: Useful for production apps where interactions with the LLM are +
        Useful for production apps where interactions with the LLM are all in your Flutter client, without requiring a server. Firebase also makes it easier to ship your AI features securely since Firebase handles the management of your Gemini API key. +**Firebase AI Logic** +
        适用于与 LLM 的交互全部在 Flutter 客户端、无需服务器的生产应用。 + Firebase 还便于安全交付 AI 功能, + 因为它负责管理 Gemini API 密钥。 + **GenUI A2UI** -: Useful for client/server architectures where your +
        Useful for client/server architectures where your agent is running on the server. +**GenUI A2UI** +
        适用于智能体运行在服务器上的客户端/服务器架构。 + **Build your own** -: You can also build your own adapter +
        You can also build your own adapter to connect to your preferred LLM provider. Expect more from us and the community soon. +**自行构建** +
        你也可以构建自己的适配器以连接首选 LLM 提供方。 + 我们与社区很快会有更多方案。 + To connect to Gemini using the Vertex AI for Firebase SDK, follow these instructions: +使用 Vertex AI for Firebase SDK 连接 Gemini,请按以下说明操作: + 1. [Create a new Firebase project][] using the Firebase Console. + 使用 Firebase Console [创建新的 Firebase 项目][Create a new Firebase project]。 + 2. [Enable the Gemini API][] for that project. + 为该项目 [启用 Gemini API][Enable the Gemini API]。 + 3. Follow the first three steps in [Firebase's Flutter setup guide][] to add Firebase to your app. + 按 [Firebase Flutter 设置指南][Firebase's Flutter setup guide] 的前三步将 Firebase 添加到应用。 + 4. Use `dart pub add` to add `genui` and `firebase_vertex_ai` as dependencies in your `pubspec.yaml` file: + 使用 `dart pub add` 在 `pubspec.yaml` 中添加 `genui` 与 `firebase_vertex_ai` 依赖: + ```console $ dart pub add genui firebase_vertex_ai ``` @@ -77,6 +120,8 @@ To connect to Gemini using the Vertex AI for Firebase SDK, follow these instruct 5. In your app's `main` method, ensure that the widget bindings are initialized and then initialize Firebase: + 在应用的 `main` 方法中,确保初始化 widget 绑定,然后初始化 Firebase: + ```dart import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -94,6 +139,9 @@ To connect to Gemini using the Vertex AI for Firebase SDK, follow these instruct 6. Create an instance of the Vertex AI for Firebase generative model and wrap it with your `SurfaceController` and `A2uiTransportAdapter`: + 创建 Vertex AI for Firebase 生成式模型实例, + 并用你的 `SurfaceController` 与 `A2uiTransportAdapter` 包装它: + ```dart import 'package:genui/genui.dart'; import 'package:firebase_vertex_ai/firebase_vertex_ai.dart'; @@ -145,20 +193,36 @@ Flutter applications to connect to an Agent-to-Agent (A2UI) server and render dynamic user interfaces generated by an AI agent using the `genui` framework. +[`genui`][] 与 [A2UI 流式 UI 协议][A2UI Streaming UI Protocol] 整合的 package, +使 Flutter 应用可连接 Agent-to-Agent (A2UI) 服务器, +并用 `genui` 框架渲染 AI 智能体生成的动态用户界面。 + The main components in this package include: +该 package 的主要组件包括: + * `A2uiAgentConnector`: Handles the low-level web socket communication with the A2A server, including sending messages and parsing stream events. + + `A2uiAgentConnector`:处理与 A2A 服务器的底层 WebSocket 通信,包括发送消息与解析流事件。 + * `AgentCard`: A data class that holds metadata about the connected AI agent. + `AgentCard`:保存已连接 AI 智能体元数据的数据类。 + Follow these instructions: +请按照以下说明操作: + 1. Set up dependencies: Use `dart pub add` to add `genui`, `genui_a2a`, and `a2a` as dependencies in your `pubspec.yaml` file. + 设置依赖: + 使用 `dart pub add` 在 `pubspec.yaml` 文件中添加 `genui`、`genui_a2a` 和 `a2a` 依赖。 + ```console $ dart pub add genui genui_a2a a2a ``` @@ -166,23 +230,42 @@ Follow these instructions: 2. Initialize `SurfaceController`: Set up `SurfaceController` with your widget `Catalog`s. + 初始化 `SurfaceController`: + 用你的 widget `Catalog` 设置 `SurfaceController`。 + 3. Create `A2uiTransportAdapter`: Instantiate `A2uiTransportAdapter` to parse the messages. + 创建 `A2uiTransportAdapter`: + 实例化 `A2uiTransportAdapter` 以解析消息。 + 4. Create `A2uiAgentConnector`: Instantiate `A2uiAgentConnector`, providing the A2A server URI. + 创建 `A2uiAgentConnector`: + 实例化 `A2uiAgentConnector`,并提供 A2A 服务器 URI。 + 5. Create `Conversation`: Pass the adapter and controller to the `Conversation`. + 创建 `Conversation`: + 将适配器与控制器传给 `Conversation`。 + 6. Render with `Surface`: Use `Surface` widgets in your UI to display the agent-generated content. + 使用 `Surface` 渲染: + 在 UI 中使用 `Surface` widget 显示智能体生成的内容。 + 7. Send Messages: Use `connector.connectAndSend` or `Conversation.sendMessage` to send user input to the agent-generated content. + 发送消息: + 使用 `connector.connectAndSend` 或 `Conversation.sendMessage` + 将用户输入发送给智能体生成的内容。 + ```dart import 'package:flutter/material.dart'; import 'package:genui/genui.dart'; @@ -381,27 +464,44 @@ Follow these instructions: The [example][] directory on pub.dev contains a complete application demonstrating how to use this package. +pub.dev 上的 [example][] 目录包含一个完整应用,演示如何使用该 package。 + [example]: {{site.pub-pkg}}/genui_a2a/example [A2UI Streaming UI Protocol]: https://a2ui.org/ - + + To use `genui` with another agent provider, follow that provider's SDK documentation to implement a connection, and stream its results into an `A2uiTransportAdapter`. +如果需要将 `genui` 与其他 agent 提供商配合使用, +请按照该提供商的 SDK 文档实现连接, +并将结果流式传输到 `A2uiTransportAdapter` 中。 + :::warning `PromptBuilder.chat()` generates a system prompt that might be **3,000–5,000+ tokens** long, which can exceed the context window of on-device or small models. If you are targeting an on-device LLM, consider: +`PromptBuilder.chat()` 生成的系统提示词长度可能达到 +**3,000–5,000+ tokens**, +这可能会超出设备端模型或小型模型的上下文窗口。 +如果你打算使用设备端 LLM,请考虑: + - Writing a compact custom system prompt that covers only the A2UI `createSurface` to `updateComponents` flow. + + 编写仅覆盖 A2UI `createSurface` 到 `updateComponents` 流程的紧凑自定义系统提示词。 + - Using `systemPromptFragments` to pass only the portions of the schema your use case requires. + + 使用 `systemPromptFragments` 仅传入用例所需的 schema 部分。 ::: @@ -410,10 +510,15 @@ If you are targeting an on-device LLM, consider: ## Create the connection to an agent +## 创建与智能体的连接 + If you build your Flutter project for iOS or macOS, add this key to your `{ios,macos}/Runner/*.entitlements` file(s) to enable outbound network requests: +若你为 iOS 或 macOS 构建 Flutter 项目, +在 `{ios,macos}/Runner/*.entitlements` 文件中添加以下键以启用出站网络请求: + ```xml ... @@ -425,20 +530,32 @@ to enable outbound network requests: Next, use the following instructions to connect your app to your chosen agent provider. +接下来,按以下说明将应用连接到所选智能体提供方。 + 1. Create a `SurfaceController`, and provide it with the catalogs of widgets that you want to make available to the agent. Create an `A2uiTransportAdapter` to parse messages and connect it. + 创建 `SurfaceController`,提供你希望向智能体开放的 widget catalog, + 并创建 `A2uiTransportAdapter` 解析消息并连接。 + 2. Create a `PromptBuilder`, and provide it with a system instruction and the tools (functions you want the agent to be able to invoke). You should always include the tools provided by `SurfaceController`, but feel free to include others. Add this to your LLM system prompt. + 创建 `PromptBuilder`,提供系统指令与工具(你希望智能体能调用的函数)。 + 应始终包含 `SurfaceController` 提供的工具, + 也可添加其他工具,并写入 LLM 系统提示词。 + 3. Create a `Conversation` using the instances of `SurfaceController` and `A2uiTransportAdapter`. Your app will primarily interact with this object to get things done. + 使用 `SurfaceController` 与 `A2uiTransportAdapter` 实例创建 `Conversation`; + 应用主要通过该对象完成工作。 + For example: ```dart @@ -503,20 +620,34 @@ to your chosen agent provider. ## Send messages and display the agent's responses +## 发送消息并显示智能体响应 + Send a request to the agent using the `sendRequest` method in the `Conversation` class, or by directly streaming into your LLM Client and pumping the result stream to the adapter by using `_transportAdapter.addChunk`. +使用 `Conversation` 类的 `sendRequest` 方法向智能体发送请求, +或直接流式传输到 LLM 客户端并用 `_transportAdapter.addChunk` 将结果流传入适配器。 + To receive and display generated UI: +要接收并显示生成的 UI: + 1. Listen to the `events` stream in `Conversation` to track the addition and removal of UI surfaces as they are generated. These events include a _surface ID_ for each surface. + 监听 `Conversation` 中的 `events` 流, + 来跟踪 UI 界面的生成和移除。 + 这些事件包含每个界面的 **surface ID**。 + 2. Build a `Surface` widget for each active surface using the surface IDs received in the previous step. + 使用上一步收到的 surface ID, + 为每个界面构建一个 `Surface` widget。 + For example: ```dart @@ -602,16 +733,27 @@ To receive and display generated UI: ## Add your own widgets to the catalog {:#custom-widgets} +## 将自定义 widget 加入 catalog {:#custom-widgets} + For your convenience, you can use the provided core catalog of widgets. However, most production apps will want to define a custom catalog of widgets. +为方便起见,可使用提供的核心 widget catalog。 +但多数生产应用会希望定义自定义 widget catalog。 + To add your own widgets, use the following instructions. +要添加自定义 widget,请按以下说明操作。 + 1. Depend on the `json_schema_builder` package + 依赖 `json_schema_builder` package + Use `dart pub add` to add `json_schema_builder` as a dependency in your `pubspec.yaml` file: + + 使用 `dart pub add` 在 `pubspec.yaml` 文件中添加 `json_schema_builder` 依赖: ```console $ dart pub add json_schema_builder @@ -619,10 +761,15 @@ To add your own widgets, use the following instructions. 2. Create the new widget's schema + 创建新 widget 的 schema + Each catalog item needs a schema that defines the data required to populate it. Using the `json_schema_builder` package, define one for the new widget. + 每个 catalog item 都需要一个 schema,用于定义填充它所需的数据。 + 使用 `json_schema_builder` package 为新 widget 定义一个 schema。 + ```dart import 'package:json_schema_builder/json_schema_builder.dart'; import 'package:flutter/material.dart'; @@ -639,11 +786,16 @@ To add your own widgets, use the following instructions. 3. Create a `CatalogItem` + 创建 `CatalogItem` + Each `CatalogItem` represents a type of widget that the agent is allowed to generate. To do that, it combines a name, a schema, and a builder function that produces the widgets that compose the generated UI. + 每个 `CatalogItem` 表示智能体允许生成的一类 widget。 + 为此,它组合名称、schema,以及生成构成生成式 UI 的 widget 的 builder 函数。 + ```dart final riddleCard = CatalogItem( name: 'RiddleCard', @@ -680,8 +832,12 @@ To add your own widgets, use the following instructions. 4. Add the `CatalogItem` to the catalog + 将 `CatalogItem` 添加到 catalog + Include your catalog items when instantiating `SurfaceController`. + 实例化 `SurfaceController` 时包含你的 catalog item。 + ```dart _surfaceController = SurfaceController( catalogs: [BasicCatalogItems.asCatalog().copyWith([riddleCard])], @@ -690,10 +846,15 @@ To add your own widgets, use the following instructions. 5. Update the system instruction to use the new widget + 更新系统指令以使用新 widget + To make sure that the agent knows to use your new widget, tell the system instruction how and when to do so. Provide the name from the `CatalogItem` when you do. + 为确保智能体知道要使用你的新 widget,请在系统指令中说明如何以及何时使用。 + 说明时提供 `CatalogItem` 中的名称。 + ```dart final promptBuilder = PromptBuilder.chat( catalog: catalog, @@ -713,27 +874,44 @@ To add your own widgets, use the following instructions. ## Data model and data binding +## 数据模型与数据绑定 + A core concept in `genui` is the `DataModel`, a centralized, observable store for all dynamic UI state. Instead of each widget managing its own state, its state is stored in the `DataModel`. +`genui` 的核心概念是 `DataModel`——所有动态 UI 状态的集中、可观察存储。 +各 widget 不各自管理状态,而是将状态存入 `DataModel`。 + Widgets are _bound_ to data in this model. When data in the model changes, only the widgets that depend on that specific piece of data are rebuilt. This is achieved through a `DataContext` object passed to each widget's builder function. +widget **绑定** 到该模型中的数据; +模型中的数据变化时,仅依赖该数据的 widget 会重建。 +这通过传给各 widget builder 函数的 `DataContext` 对象实现。 + ### Binding to the data model +### 绑定到数据模型 + To bind a widget's property to the data model, specify a special JSON object in the data sent from the AI. This object can contain standard JSON primitives (for static values) or an object with a `path` property (to bind to a value in the data model). +要将 widget 属性绑定到数据模型,在 AI 发送的数据中指定特殊 JSON 对象。 +该对象可含标准 JSON 基元(静态值), +或含 `path` 属性的对象(绑定到数据模型中的值)。 + For example, to display a user's name in a `Text` widget, the AI would generate: +例如,要在 `Text` widget 中显示用户名,AI 会生成: + ```json { "component": "Text", @@ -744,6 +922,8 @@ the AI would generate: ### Image +### 图像 + ```json { "component": "Image", @@ -754,37 +934,60 @@ the AI would generate: ### Updating the data model +### 更新数据模型 + Input widgets, like `TextField`, update the DataModel directly. When the user types in a text field that is bound to `/user/name`, the `DataModel` updates, and any other widgets bound to that same path will automatically rebuild to show the new value. +输入类 widget(如 `TextField`)直接更新 DataModel。 +用户在绑定到 `/user/name` 的文本字段中输入时,`DataModel` 更新, +绑定同一路径的其他 widget 会自动重建以显示新值。 + This reactive data flow simplifies state management and creates a powerful, high-bandwidth interaction loop between the user, the UI, and the AI. +这种响应式数据流简化状态管理,并在用户、UI 与 AI 之间形成强大的高带宽交互循环。 + ## Next steps +## 后续 + Check out the [examples][] included in the `genui` repo. The [travel app][] shows how to define your own widget catalog that the agent can use to generate domain-specific UI. +查看 `genui` 仓库中的 [示例][examples]。 +[旅行应用][travel app] 展示如何定义智能体可用于生成领域特定 UI 的自定义 widget catalog。 + If something is unclear or missing, please [create an issue][]. +若有不清楚或缺失之处,请 [提交 issue][create an issue]。 + [examples]: {{site.repo.organization}}/genui/blob/main/examples [travel app]: {{site.repo.organization}}/genui/blob/main/examples/travel_app [create an issue]: {{site.repo.organization}}/genui/issues/new/choose ## System instructions +## 系统指令 + The `genui` package gives the LLM a set of tools it can use to generate UI. To get the LLM to use these tools, the system instructions provided through `PromptBuilder` must explicitly tell it to do so. +`genui` package 为 LLM 提供一组可用于生成 UI 的工具。 +要让 LLM 使用这些工具,通过 `PromptBuilder` 提供的系统指令必须明确告知其这样做。 + This is why the [earlier example][instruction-example] includes a system instruction for the agent with the line "Every time I give you a word, you should generate UI that...": +因此 [先前的示例][instruction-example] 为智能体包含系统指令, +其中有「Every time I give you a word, you should generate UI that...」这样的表述: + ```dart highlightLines=4-5 final promptBuilder = PromptBuilder.chat( catalog: catalog, @@ -801,11 +1004,17 @@ final promptBuilder = PromptBuilder.chat( ## Troubleshooting/FAQ {:#troubleshoot} +## 故障排除 / 常见问题 {:#troubleshoot} + ### How can I configure logging? +### 如何配置日志? + To observe communication between your app and the agent, enable logging in your `main` method. +要在 `main` 方法中观察应用与智能体之间的通信,请启用日志。 + ```dart import 'package:logging/logging.dart'; import 'package:genui/genui.dart'; @@ -823,12 +1032,19 @@ void main() async { ### I'm getting errors about my minimum macOS/iOS version. +### 我遇到关于 macOS/iOS 最低版本的错误。 + Firebase has a [minimum version requirement][] for Apple's platforms, which might be higher than Flutter's default. Check your `Podfile` (for iOS) and `CMakeLists.txt` (for macOS) to ensure that you're targeting a version that meets or exceeds Firebase's requirements. +Firebase 对 Apple 平台有 [最低版本要求][minimum version requirement], +可能高于 Flutter 默认值。 +请检查 `Podfile` (iOS) 和 `CMakeLists.txt` (macOS), +确保目标版本满足或超过 Firebase 要求。 + [Create a new Firebase project]: https://support.google.com/appsheet/answer/10104995 [create an issue]: {{site.repo.organization}}/genui/issues/new/choose [Enable the Gemini API]: https://firebase.google.com/docs/gemini-in-firebase/set-up-gemini diff --git a/sites/docs/src/content/ai/genui/index.md b/sites/docs/src/content/ai/genui/index.md index 59363d0732..4814b471f5 100644 --- a/sites/docs/src/content/ai/genui/index.md +++ b/sites/docs/src/content/ai/genui/index.md @@ -1,26 +1,39 @@ --- -title: GenUI SDK for Flutter +# title: GenUI SDK for Flutter +title: 适用于 Flutter 的 GenUI SDK sidenav: ai +# shortTitle: GenUI SDK shortTitle: GenUI SDK +# description: >- +# Learn how to use GenUI SDK for Flutter to build more +# interactive experiences for applications and chatbots. description: >- - Learn how to use GenUI SDK for Flutter to build more - interactive experiences for applications and chatbots. + 了解如何使用适用于 Flutter 的 GenUI SDK,为应用与聊天机器人构建更具交互性的体验。 next: - title: GenUI SDK main components & concepts + # title: GenUI SDK main components & concepts + title: GenUI SDK 主要组件与概念 path: /ai/genui/components +ai-translated: true --- ## What is GenUI? +## 什么是 GenUI? + At its core, the GenUI SDK for Flutter is an orchestration layer. This suite of packages coordinates the flow of information between your user, your Flutter widgets, and an AI agent, transforming text-based conversations into rich, interactive experiences. +其核心上,适用于 Flutter 的 GenUI SDK 是一个编排层。 +这套包协调用户、Flutter widget 与 AI 智能体之间的信息流,将基于文本的对话转化为丰富的交互体验。 + Imagine that, instead of presenting your user with a wall of text, they are presented with a graphical UI consisting of (for example), a row of labeled buttons and a date picker. +想象一下,你不是向用户展示一堵文字墙,而是展示由(例如)一行带标签按钮和日期选择器组成的图形 UI。 + The GenUI SDK for Flutter uses a JSON-based format to compose a UI from your existing widget catalog. As a user interacts with the UI, @@ -28,34 +41,57 @@ state changes are fed back to the agent, creating a high-bandwidth loop and turning an agent interaction into a rich, intuitive experience. +适用于 Flutter 的 GenUI SDK 使用基于 JSON 的格式, +从你现有的 widget 目录组合 UI。用户与 UI 交互时, +状态变化会反馈给智能体,形成高带宽循环,将智能体交互变为丰富、直观的体验。 + The GenUI SDK for Flutter is designed to easily integrate into your Flutter application. +适用于 Flutter 的 GenUI SDK 旨在轻松集成到你的 Flutter 应用中。 + ## When would you use it? +## 何时使用? + Use GenUI SDK for Flutter to incorporate graphical UI into your app. For example: +在应用中加入图形 UI 时使用 GenUI SDK for Flutter。 +例如: + * Instead of describing a list of products in text, use it to render a clickable carousel of product widgets. + + 不要用文字描述产品列表,用它渲染可点击的产品 widget 轮播。 + * When a user asks to plan a trip, use it to generate a complete form with sliders, date pickers, and text fields. + 当用户要求规划旅行时,用它生成带滑块、日期选择器和文本字段的完整表单。 + For more context about GenUI SDK for Flutter, check out the [Getting started with GenUI video][]: +有关 GenUI SDK for Flutter 的更多背景, +请参阅 [GenUI 入门视频][Getting started with GenUI video]: + Also, check out the Flutter + A2UI = GenUI video from Google I/O 2026! +也可观看 Google I/O 2026 的 Flutter + A2UI = GenUI 视频! + :::experimental The `genui` package is in alpha and is likely to change. + +`genui` package 处于 alpha 阶段,可能会变更。 ::: [Getting started with GenUI video]: https://www.youtube.com/watch?v=nWr6eZKM6no diff --git a/sites/docs/src/content/ai/genui/input-events.md b/sites/docs/src/content/ai/genui/input-events.md index e536fdc098..368fbdd938 100644 --- a/sites/docs/src/content/ai/genui/input-events.md +++ b/sites/docs/src/content/ai/genui/input-events.md @@ -1,66 +1,123 @@ --- -title: Input and events +# title: Input and events +title: 输入与事件 sidenav: ai -description: How input and events are handled in GenUI applications. +# description: How input and events are handled in GenUI applications. +description: GenUI 应用中如何处理输入与事件。 prev: - title: Get started with the GenUI SDK for Flutter + # title: Get started with the GenUI SDK for Flutter + title: GenUI SDK 入门 path: /ai/genui/get-started +ai-translated: true --- This guide explains how user interactions are handled within the GenUI package, from the initial widget interaction to the AI agent receiving the event. +本指南说明 GenUI package 中如何处理用户交互, +从最初 widget 交互到 AI 智能体接收事件。 + :::experimental The `genui` package is in alpha and is likely to change. + +`genui` package 处于 alpha 阶段,可能会变更。 ::: ## Overview +## 概览 + In the GenUI architecture, the UI is driven by the AI, but user interactions (like clicking a button or submitting a form) must be communicated back to the AI agent. This allows the agent to update the UI or perform actions in response to user input. +在 GenUI 架构中,UI 由 AI 驱动, +但用户交互(如点击按钮或提交表单)必须传回 AI 智能体, +以便智能体根据用户输入更新 UI 或执行操作。 + The flow of an event is as follows: +事件流程如下: + 1. Interaction: User interacts with a widget; for example, the user taps a button. + + 交互 (Interaction):用户与 widget 交互;例如点击按钮。 + 2. Capture: The widget implementation dispatches a `UiEvent`. + + 捕获 (Capture):widget 实现派发 `UiEvent`。 + 3. Processing: The framework adds context (such as a `surfaceId` or data model values) and forwards the event. + + 处理 (Processing):框架添加上下文(如 `surfaceId` 或数据模型值)并转发事件。 + 4. Transmission: The Flutter widget generates the event, adds the appropriate context, and routes it to the AI through the `ContentGenerator`, which forwards it to the AI agent. + 传输 (Transmission):Flutter widget 生成事件、添加上下文, + 经 `ContentGenerator` 路由到 AI,再转发给 AI 智能体。 + ## Defining events +## 定义事件 + ### Protocol level +### 协议层 + The A2UI protocol defines an `action` message used to report events. An `action` contains: +A2UI 协议定义用于上报事件的 `action` 消息。 +`action` 包含: + * `name`: The name of the action (defined by the AI when generating the component). + + `name`:操作名称(由 AI 在生成 widget 时定义)。 + * `surfaceId`: The ID of the UI surface where the event occurred. + + `surfaceId`:事件发生所在 UI surface 的 ID。 + * `sourceComponentId`: The ID of the component that triggered the event. + + `sourceComponentId`:触发事件的 widget ID。 + * `context`: A JSON object containing data relevant to the event. + + `context`:包含与事件相关数据的 JSON 对象。 + * `timestamp`: When the event occurred. + `timestamp`:事件发生时间。 + ### Dart implementation +### Dart 实现 + In [`package:genui`][], user events are represented by the `UiEvent` extension type and its concrete implementation `UserActionEvent`. +在 [`package:genui`][] 中, +用户事件由 `UiEvent` extension type 及其实现 `UserActionEvent` 表示。 + [`package:genui`]: {{site.pub-pkg}}/genui The following structures are defined in `lib/src/model/ui_models.dart`: +以下结构定义在 `lib/src/model/ui_models.dart`: + ```dart title="lib/src/model/ui_models.dart" /// A data object that represents a user interaction event in the UI. extension type UiEvent.fromMap(JsonMap _json) { ... } @@ -77,8 +134,26 @@ extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent { } ``` +```dart title="lib/src/model/ui_models.dart" +/// 表示 UI 中用户交互事件的数据对象。 +extension type UiEvent.fromMap(JsonMap _json) { ... } + +/// 表示用户操作的 UI 事件。 +extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent { + UserActionEvent({ + String? surfaceId, + required String name, + required String sourceComponentId, + JsonMap? context, + // ... + }) : ... +} +``` + ## Capturing events in widgets +## 在 widget 中捕获事件 + Widgets in GenUI are defined in a `Catalog`, which includes information about what events the widget can send to the AI. @@ -88,13 +163,24 @@ When you implement a custom widget (or use the standard widgets), you use the `dispatchEvent` method in `CatalogItemContext` to dispatch events. +GenUI 中的 widget 在 `Catalog` 中定义, +其中包含 widget 可向 AI 发送哪些事件的信息。 +AI 随后可发送如何回传这些事件的说明。 +实现自定义 widget(或使用标准 widget)时, +在 `CatalogItemContext` 中使用 `dispatchEvent` 派发事件。 + ### Example: Button implementation {: #button-example} +### 示例:Button 实现 {: #button-example} + The following example shows how a `Button` widget typically captures a tap and dispatches an event. It retrieves the action definition (provided by the AI) from its properties, resolves any data bindings in the context, and sends the event. +以下示例展示 `Button` widget 通常如何捕获点击并派发事件: +从属性获取 AI 提供的 action 定义,解析上下文中的数据绑定,并发送事件。 + ```dart // Inside a CatalogItem widgetBuilder: widgetBuilder: (itemContext) { @@ -131,22 +217,36 @@ widgetBuilder: (itemContext) { ## Event processing pipeline +## 事件处理流水线 + Once `dispatchEvent` is called, the event travels through the GenUI core layers. +调用 `dispatchEvent` 后,事件流经 GenUI 核心层。 + ### Surface The `Surface` widget (in `lib/src/core/surface.dart`) wraps the rendered widgets. It provides the dispatchEvent callback implementation. +`Surface` widget(位于 `lib/src/core/surface.dart`)包装已渲染 widget, +提供 dispatchEvent 回调实现。 + When `_dispatchEvent` is called: +调用 `_dispatchEvent` 时: + 1. It automatically injects the `surfaceId` into the event, ensuring the AI knows which surface the interaction came from. + + 自动将 `surfaceId` 注入事件,确保 AI 知道交互来自哪个 surface。 + 2. It delegates handling to the `SurfaceHost` (implemented by `SurfaceController`). + 将处理委托给 `SurfaceHost`(由 `SurfaceController` 实现)。 + ```dart // Surface implementation details void _dispatchEvent(UiEvent event) { @@ -165,13 +265,26 @@ void _dispatchEvent(UiEvent event) { The `SurfaceController` (in `lib/src/core/surface_controller.dart`) is the central hub for managing UI state. +`SurfaceController`(位于 `lib/src/core/surface_controller.dart`) +是管理 UI 状态的中央枢纽。 + When `handleUiEvent` is called, it does the following: +调用 `handleUiEvent` 时,它会: + 1. Verifies the event type. + + 验证事件类型。 + 2. Wraps the event in the `action` JSON envelope required by the protocol. + + 用协议要求的 `action` JSON 信封包装事件。 + 3. Emits a `UserUiInteractionMessage` on its `onSubmit` stream. + 在其 `onSubmit` 流上发出 `UserUiInteractionMessage`。 + ```dart // SurfaceController implementation details @override @@ -188,12 +301,18 @@ void handleUiEvent(UiEvent event) { ## Transmission to AI +## 传输到 AI + The final step sends the event to the AI Agent. This is typically handled by `Conversation` (in `lib/src/facade/conversation.dart`). The `Conversation` listens to the `onSubmit` stream from the message processor. +最后一步将事件发送给 AI 智能体, +通常由 `Conversation`(位于 `lib/src/facade/conversation.dart`)处理。 +`Conversation` 监听消息处理器的 `onSubmit` 流。 + ```dart // Conversation constructor _userEventSubscription = surfaceController.onSubmit.listen(sendRequest); @@ -201,11 +320,22 @@ _userEventSubscription = surfaceController.onSubmit.listen(sendRequest); When an event is received, the `sendRequest` method: +收到事件时,`sendRequest` 方法会: + 1. Wraps the `UserUiInteractionMessage` back to the developer's client code. + + 将 `UserUiInteractionMessage` 包装回开发者客户端代码。 + 2. The custom integration or predefined transport adapter forwards the message to the LLM agent network transport. + 自定义集成或预定义传输适配器将消息转发到 LLM 智能体网络传输。 + The AI Agent receives this JSON message, processes the user action, and might stream back new `surfaceUpdate` or `dataModelUpdate` messages to modify the UI, or some other action, completing the full interaction loop. +AI 智能体接收该 JSON 消息,处理用户操作, +并可能流式返回新的 `surfaceUpdate` 或 `dataModelUpdate` 消息以修改 UI, +或其他操作,完成完整交互循环。 + diff --git a/sites/docs/src/content/ai/index.md b/sites/docs/src/content/ai/index.md index 3ae9d43d80..8c32418cf7 100644 --- a/sites/docs/src/content/ai/index.md +++ b/sites/docs/src/content/ai/index.md @@ -3,6 +3,9 @@ title: Flutter & AI sidenav: ai shortTitle: AI layout: toc +# description: >- +# Learn how to integrate with and use AI to develop Flutter apps. description: >- - Learn how to integrate with and use AI to develop Flutter apps. + 了解如何集成并使用 AI 来开发 Flutter 应用。 +ai-translated: true --- diff --git a/sites/docs/src/content/ai/mcp-server.md b/sites/docs/src/content/ai/mcp-server.md index 7495483c8e..88bf0c840f 100644 --- a/sites/docs/src/content/ai/mcp-server.md +++ b/sites/docs/src/content/ai/mcp-server.md @@ -1,22 +1,34 @@ --- -title: Dart and Flutter MCP server +# title: Dart and Flutter MCP server +title: Dart 与 Flutter MCP server sidenav: ai shortTitle: MCP server +# description: > +# Learn about the Dart and Flutter MCP server tool that +# exposes Dart and Flutter tools to compatible +# AI-assistant clients and agents. description: > - Learn about the Dart and Flutter MCP server tool that - exposes Dart and Flutter tools to compatible - AI-assistant clients and agents. + 了解 Dart 与 Flutter MCP server 工具, + 它向兼容的 AI 助手客户端与智能体暴露 Dart 与 Flutter 工具。 +ai-translated: true --- This guide discusses the Dart and Flutter MCP server. +本指南介绍 Dart 与 Flutter MCP server。 + :::experimental The Dart and Flutter MCP server is experimental and likely to evolve quickly. The following instructions require Dart 3.9 or later. + +Dart 与 Flutter MCP server 处于实验阶段,可能会快速演进。 +以下说明需要 Dart 3.9 或更高版本。 ::: ## Overview +## 概述 + The [Dart and Flutter MCP server][] exposes Dart and Flutter development tool actions to compatible AI-assistant clients. MCP (model context protocol) @@ -24,6 +36,11 @@ is a protocol that enables communication between development tools and AI assistants, allowing the assistants to understand the context of the code and perform actions on behalf of the developer. +[Dart 与 Flutter MCP server][Dart and Flutter MCP server] +向兼容的 AI 助手客户端暴露 Dart 与 Flutter 开发工具操作。 +MCP(model context protocol,模型上下文协议)使开发工具与 AI 助手能够通信, +让助手理解代码上下文并代表开发者执行操作。 + The Dart and Flutter MCP server can work with any MCP client that supports standard I/O (stdio) as the transport medium. To access all the features of the Dart and Flutter MCP server, @@ -31,24 +48,54 @@ an MCP client must support [Tools][] and [Resources][]. For the best development experience with the Dart and Flutter MCP server, an MCP client should also support [Roots][]. +Dart 与 Flutter MCP server 可与任何以标准 I/O(stdio)为传输介质的 MCP 客户端配合。 +要访问其全部功能,MCP 客户端须支持 [Tools][] 与 [Resources][]。 +为获得最佳开发体验,MCP 客户端还应支持 [Roots][]。 + If you are using a client that claims it supports roots but doesn't actually set them, pass `--force-roots-fallback` flag to enable tools for managing the roots. +若客户端声称支持 roots 却未实际设置, +请传入 `--force-roots-fallback` 标志以启用管理 roots 的工具。 + The Dart and Flutter MCP server provides a growing list of tools that grant AI assistants deep insights into your project. Here is an overview of a few things it can do: +Dart 与 Flutter MCP server 提供不断增长的工具列表, +为 AI 助手提供对项目的深入洞察。它能做的一些事包括: + * Analyze and fix errors in your project's code. + + 分析并修复项目代码中的错误。 + * Resolve symbols to elements to ensure their existence and fetch documentation and signature information for them. + + 将符号解析为元素以确认其存在,并获取文档与签名信息。 + * Introspect and interact with your running application. + + 内省并与运行中的应用交互。 + * Search the [pub.dev site]({{site.pub}}) for the best package for a use case. + + 在 [pub.dev 站点]({{site.pub}}) 搜索最适合场景的 package。 + * Manage package dependencies in your `pubspec.yaml` file. + + 管理 `pubspec.yaml` 中的 package 依赖。 + * Run tests and analyze the results. + + 运行测试并分析结果。 + * Format code with the same formatter and config as [`dart format`][] and the Dart analysis server. + 使用与 [`dart format`][] 及 Dart 分析服务器相同的格式化器与配置格式化代码。 + [Tools]: https://modelcontextprotocol.io/docs/concepts/tools [Resources]: https://modelcontextprotocol.io/docs/concepts/resources [Roots]: https://modelcontextprotocol.io/docs/concepts/roots @@ -57,52 +104,96 @@ Here is an overview of a few things it can do: ## Set up your MCP client +## 设置 MCP 客户端 + Run the server with the `dart mcp-server` command, which must be configured in your preferred client. +使用 `dart mcp-server` 命令运行服务器,并须在你偏好的客户端中配置。 + This section provides instructions for setting up the Dart and Flutter MCP server with popular tools such as Antigravity, Gemini CLI, Cursor, and GitHub Copilot. +本节说明如何在 Antigravity、Gemini CLI、Cursor、GitHub Copilot 等常用工具中设置 Dart 与 Flutter MCP server。 + ### Antigravity +### Antigravity 工具 + To configure Google [Antigravity][] to use the Dart and Flutter MCP server, you can either install it from the list of available servers or [connect it as a custom MCP server][antigravity-mcp]. +要配置 Google [Antigravity][] 使用 Dart 与 Flutter MCP server, +可从可用服务器列表安装,或 [将其连接为自定义 MCP server][antigravity-mcp]。 + 1. Navigate to or open the **Agent** side panel. + 导航到或打开 **Agent** 侧边面板。 + If it's closed, open it by either: + 若已关闭,可通过以下方式打开: + - Pressing Cmd/Ctrl + L. + + 按 Cmd/Ctrl + L。 + - Going to **View** > **Open View...** > **Agent**. + 依次进入 **View** + > **Open View...** + > **Agent**。 + 1. In the upper right of the **Agent** panel, click the **Additional options** (`...`) menu button. + + 在 **Agent** 面板右上角,点按 **Additional options**(`...`)菜单按钮。 + 1. Select **MCP Servers**. + + 选择 **MCP Servers**。 + 1. In the upper right of the **Agent** panel, click **Manage MCP Servers**. + 在 **Agent** 面板右上角,点按 **Manage MCP Servers**。 + From here, you can choose to install the MCP server from [the built-in MCP store](#antigravity-mcp-store-install) or by [configuring it manually](#antigravity-mcp-manual-install). +此后可从 [内置 MCP 商店](#antigravity-mcp-store-install) 安装, +或通过 [手动配置](#antigravity-mcp-manual-install)。 + [Antigravity]: https://antigravity.google/ [antigravity-mcp]: https://antigravity.google/docs/mcp#connecting-custom-mcp-servers #### Install from the MCP store {: #antigravity-mcp-store-install} +#### 从 MCP 商店安装 {: #antigravity-mcp-store-install} + 1. In the list of available MCP servers, find or search for **Dart** and click **Install**. + 在可用 MCP server 列表中查找或搜索 **Dart**,然后点按 **Install**。 + #### Connect manually {: #antigravity-mcp-manual-install} +#### 手动连接 {: #antigravity-mcp-manual-install} + 1. In the upper right of the **Manage MCPs** editor view, click **View raw config**. + + 在 **Manage MCPs** 编辑器视图右上角,点按 **View raw config**。 + 1. Add the following `dart-mcp-server` entry to the `mcpServers` map: + 在 `mcpServers` 映射中添加以下 `dart-mcp-server` 条目: + ```json title="mcp_config.json" highlightLines=3-10 { "mcpServers": { @@ -119,26 +210,53 @@ From here, you can choose to install the MCP server from #### Install extensions +#### 安装扩展 + It is also recommended to install the Dart and Flutter extensions: +建议同时安装 Dart 与 Flutter 扩展: + 1. Open the **Extensions** view by either: + 通过以下方式打开 **Extensions** 视图: + - Pressing Shift + Cmd/Ctrl + P. + + 按 Shift + + Cmd/Ctrl + + P。 + - Going to **View** > **Extensions**. + 依次进入 **View** + > **Extensions**。 + 1. In the **Search Extensions** input box, enter **Flutter**. + + 在 **Search Extensions** 输入框中输入 **Flutter**。 + 1. From the list of extensions, select **Flutter**. + + 在扩展列表中选择 **Flutter**。 + 1. In the **Extension: Flutter** view that opens, click the **Install** button. + 在打开的 **Extension: Flutter** 视图中点按 **Install** 按钮。 + This installs both the Dart and Flutter extensions. + 这将同时安装 Dart 与 Flutter 扩展。 + To learn more about the Dart and Flutter extensions, check out [Develop Flutter apps in VS Code][]. +有关 Dart 与 Flutter 扩展的更多信息, +请参阅 [在 VS Code 中开发 Flutter 应用][Develop Flutter apps in VS Code]。 + [Develop Flutter apps in VS Code]: /tools/vs-code ### Gemini CLI @@ -146,11 +264,19 @@ check out [Develop Flutter apps in VS Code][]. To configure the [Gemini CLI][] to use the Dart and Flutter MCP server, add a Dart entry to the `mcpServers` section of the Gemini config. +要配置 [Gemini CLI][] 使用 Dart 与 Flutter MCP server, +在 Gemini 配置的 `mcpServers` 部分添加 Dart 条目。 + - To enable the server for all projects on your device, edit the `~/.gemini/settings.json` file in your home directory. + + 要为设备上所有项目启用服务器,请编辑主目录中的 `~/.gemini/settings.json`。 + - To enable the server for a specific project, edit the `.gemini/settings.json` file in the project's root directory. + 要为特定项目启用服务器,请编辑项目根目录中的 `.gemini/settings.json`。 + ```json title=".gemini/settings.json" { "mcpServers": { @@ -167,25 +293,41 @@ add a Dart entry to the `mcpServers` section of the Gemini config. For more information, check out the official Gemini CLI documentation for [setting up MCP servers][]. +更多信息请参阅 Gemini CLI 关于 [设置 MCP server][setting up MCP servers] 的官方文档。 + [Gemini CLI]: https://geminicli.com/ [setting up MCP servers]: https://geminicli.com/docs/tools/mcp-server/#how-to-set-up-your-mcp-server ### Gemini Code Assist in VS Code +### VS Code 中的 Gemini Code Assist + [Gemini Code Assist][]'s [Agent mode][] integrates the Gemini CLI to provide a powerful AI agent directly in your IDE. If you haven't set up Gemini Code Assist or its agent mode yet, follow its [Before you begin instructions][gca-setup] to get started. +[Gemini Code Assist][] 的 [Agent mode][] 集成 Gemini CLI, +在 IDE 中直接提供强大的 AI 智能体。 +若尚未设置 Gemini Code Assist 或其 agent 模式, +请遵循其 [开始前的说明][gca-setup] 入门。 + To configure Gemini Code Assist to use the Dart and Flutter MCP server, follow the instructions to [configure the Gemini CLI][]. +要配置 Gemini Code Assist 使用 Dart 与 Flutter MCP server, +请遵循[配置 Gemini CLI][configure the Gemini CLI] 的说明。 + You can verify the MCP server has been configured properly by typing `/mcp` in the chat window in Agent mode. +可在 Agent 模式的聊天窗口中输入 `/mcp` 验证 MCP server 是否已正确配置。 + For more information see the official Gemini Code Assist documentation for [using agent mode][]. +更多信息请参阅 Gemini Code Assist 关于 [使用 agent 模式][using agent mode] 的官方文档。 + [gca-setup]: https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer#before-you-begin [Gemini Code Assist]: https://codeassist.google/ [Agent mode]: https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer @@ -194,25 +336,42 @@ documentation for [using agent mode][]. ### GitHub Copilot in VS Code +### VS Code 中的 GitHub Copilot + :::note Support for the Dart and Flutter MCP server in VS Code requires v3.116 or later of the [Dart Code extension][]. + +在 VS Code 中使用 Dart 与 Flutter MCP server 需要 +[Dart Code 扩展][Dart Code extension] v3.116 或更高版本。 ::: By default, the Dart extension uses the [VS Code MCP API][] to register the Dart and Flutter MCP server, as well as a tool to provide the URI for the active Dart Tooling Daemon. +默认情况下,Dart 扩展使用 [VS Code MCP API][] +注册 Dart 与 Flutter MCP server,并提供获取活动 Dart Tooling Daemon URI 的工具。 + Explicitly enable or disable the Dart and Flutter MCP server by configuring the `dart.mcpServer` setting in your VS Code settings. +通过在 VS Code 设置中配置 `dart.mcpServer` 显式启用或禁用 Dart 与 Flutter MCP server。 + To change this globally, update your user settings: +要全局更改,请更新用户设置: + 1. In VS Code, click **View > Command Palette** and then search for **Preferences: Open User Settings (JSON)**. + 在 VS Code 中点按 **View > Command Palette**, + 搜索 **Preferences: Open User Settings (JSON)**。 + 1. Add the following setting: + 添加以下设置: + ```json "dart.mcpServer": true ``` @@ -220,11 +379,18 @@ To change this globally, update your user settings: If you'd like this setting to apply only to a specific workspace, add the entry to your workspace settings: +若仅希望对特定工作区生效,请将条目添加到工作区设置: + 1. In VS Code, click **View > Command Palette** and then search for **Preferences: Open Workspace Settings (JSON)**. + 在 VS Code 中点按 **View > Command Palette**, + 搜索 **Preferences: Open Workspace Settings (JSON)**。 + 1. Add the following setting: + 添加以下设置: + ```json "dart.mcpServer": true ``` @@ -232,6 +398,8 @@ add the entry to your workspace settings: For more information, see the official VS Code documentation for [enabling MCP support][]. +更多信息请参阅 VS Code 关于 [启用 MCP 支持][enabling MCP support] 的官方文档。 + [Dart Code extension]: https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code [VS Code MCP API]: https://code.visualstudio.com/api/extension-guides/mcp [enabling MCP support]: https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_enable-mcp-support-in-vs-code @@ -241,20 +409,34 @@ documentation for [enabling MCP support][]. The easiest way to configure the Dart and Flutter MCP server with Cursor is by clicking the **Add to Cursor** button: +在 Cursor 中配置 Dart 与 Flutter MCP server 最简便的方式是点按 **Add to Cursor** 按钮: + [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=dart&config=eyJjb21tYW5kIjoiZGFydCBtY3Atc2VydmVyIn0%3D){:.light-mode-visible} [![Add to Cursor](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=dart&config=eyJjb21tYW5kIjoiZGFydCBtY3Atc2VydmVyIn0%3D){:.dark-mode-visible} Alternatively, you can configure the server manually: +也可手动配置服务器: + 1. Go to **Cursor > Settings > Cursor Settings > Tools & Integrations**. + + 进入 **Cursor > Settings > Cursor Settings > Tools & Integrations**。 + 1. Click **Add Custom MCP** or **New MCP Server** depending on whether you already have other MCP servers configured. + + 根据是否已配置其他 MCP server,点按 **Add Custom MCP** 或 **New MCP Server**。 + 1. Edit the `.cursor/mcp.json` file in your local project (configuration will only apply to this project) or edit the global `~/.cursor/mcp.json` file in your home directory (configuration will apply for all projects) to configure the Dart and Flutter MCP server: + 编辑本地项目中的 `.cursor/mcp.json`(仅对本项目生效), + 或编辑主目录中的全局 `~/.cursor/mcp.json`(对所有项目生效), + 以配置 Dart 与 Flutter MCP server: + ```json title=".cursor/mcp.json" { "mcpServers": { @@ -271,6 +453,8 @@ Alternatively, you can configure the server manually: For more information, see the official Cursor documentation for [installing MCP servers][]. +更多信息请参阅 Cursor 关于 [安装 MCP server][installing MCP servers] 的官方文档。 + [installing MCP servers]: https://docs.cursor.com/context/model-context-protocol#installing-mcp-servers ### OpenCode @@ -278,9 +462,15 @@ documentation for [installing MCP servers][]. To configure [OpenCode][] to use the Dart and Flutter MCP server, add the `dart-mcp-server` entry to your OpenCode configuration. +要配置 [OpenCode][] 使用 Dart 与 Flutter MCP server, +在 OpenCode 配置中添加 `dart-mcp-server` 条目。 + OpenCode configuration is typically found in `~/.opencode/config.json` or in your project's `opencode key` configuration. +OpenCode 配置通常位于 `~/.opencode/config.json` +或项目的 `opencode key` 配置中。 + ```json title="~/.opencode/config.json" { "$schema": "https://opencode.ai/config.json", @@ -305,6 +495,9 @@ or in your project's `opencode key` configuration. To configure Claude Code to use the Dart and Flutter MCP server for the current project, use the `claude mcp add` CLI command: +要为当前项目配置 Claude Code 使用 Dart 与 Flutter MCP server, +请使用 `claude mcp add` CLI 命令: + ```console $ claude mcp add --transport stdio dart -- dart mcp-server ``` @@ -312,6 +505,9 @@ $ claude mcp add --transport stdio dart -- dart mcp-server To learn more about configuring MCP servers in Claude Code, check out their documentation on [Installing MCP servers][claude-install]. +要了解在 Claude Code 中配置 MCP server 的更多内容, +请参阅其关于 [安装 MCP server][claude-install] 的文档。 + [claude-install]: https://code.claude.com/docs/en/mcp#installing-mcp-servers ### Codex CLI @@ -319,6 +515,9 @@ check out their documentation on [Installing MCP servers][claude-install]. To configure the Codex CLI to use the Dart and Flutter MCP server for the current project, use the `codex mcp add` CLI command: +要为当前项目配置 Codex CLI 使用 Dart 与 Flutter MCP server, +请使用 `codex mcp add` CLI 命令: + ```console $ codex mcp add dart -- dart mcp-server --force-roots-fallback ``` @@ -326,76 +525,131 @@ $ codex mcp add dart -- dart mcp-server --force-roots-fallback To learn more about configuring MCP servers in the Codex CLI, check out their documentation on [Connecting to MCP servers][codex-connect]. +要了解在 Codex CLI 中配置 MCP server 的更多内容, +请参阅其关于 [连接到 MCP server][codex-connect] 的文档。 + [codex-connect]: https://developers.openai.com/codex/mcp ## Use your MCP client +## 使用 MCP 客户端 + Once you've set up the Dart and Flutter MCP server with a client, the Dart and Flutter MCP server enables the client to not only reason about your project's context but also to take action with tools. +配置好客户端后,Dart 与 Flutter MCP server 不仅能让客户端推理项目上下文, +还能通过工具采取行动。 + The [Large Language Model (LLM)][LLM] decides which tools to use and when, so you can focus on describing your goal in natural language. Let's see this in action with a couple of examples using GitHub Copilot's Agent mode in VS Code. +[大语言模型(LLM)][LLM] 决定使用哪些工具及何时使用, +你只需用自然语言描述目标。 +下面通过 VS Code 中 GitHub Copilot 的 Agent 模式示例说明。 + [LLM]: https://developers.google.com/machine-learning/resources/intro-llms ### Fix a runtime layout error in a Flutter app +### 修复 Flutter 应用中的运行时布局错误 + We've all been there: you build a beautiful UI, run the app, and are greeted by the infamous yellow-and-black stripes of a RenderFlex overflow error. Instead of manually debugging the widget tree, you can now ask your AI assistant for help with a prompt similar to the following: +我们都遇到过:做好漂亮 UI、运行应用后, +却看到 RenderFlex 溢出错误那标志性的黄黑条纹。 +现在无需手动调试 widget 树,可用类似以下的提示词向 AI 助手求助: + > Check for and fix static and runtime analysis issues. > Check for and fix any layout issues. Behind the scenes, the AI agent uses the Dart and Flutter MCP server's tools to: +幕后,AI 智能体使用 Dart 与 Flutter MCP server 的工具: + * See the error: It uses a tool to get the current runtime errors from the running application. + + 查看错误:使用工具从运行中的应用获取当前运行时错误。 + * Inspect the UI: It accesses the Flutter widget tree to understand the layout that is causing the overflow. + + 检查 UI:访问 Flutter widget 树以了解导致溢出的布局。 + * Apply a fix: Armed with this context, it applies a fix and checks once more for any remaining errors. + 应用修复:结合上下文应用修复并再次检查是否仍有错误。 + You can then keep or undo the code changes. +随后你可保留或撤销代码变更。 + ### Add new functionality with package search +### 通过 package 搜索添加新功能 + Imagine you need to add a chart to your app. Which package should you use? How do you add it and write the boilerplate? The Dart and Flutter MCP server can streamline this entire process with a prompt similar to the following: +假设你要为应用添加图表。 +该用哪个 package?如何添加并编写样板代码? +Dart 与 Flutter MCP server 可用类似以下的提示词简化整个流程: + > Find a suitable package to add a line chart that > maps the number of button presses over time. The AI agent now acts as a true assistant: +AI 智能体现在真正成为助手: + * Find the right tool: It uses the `pub_dev_search` tool to find popular and highly-rated charting libraries. + + 找到合适工具:使用 `pub_dev_search` 工具查找流行且高评分的图表库。 + * Manage dependencies: After you confirm its choice, such as [`package:fl_chart`][], it uses a tool to add the package as a dependency. + + 管理依赖:在你确认选择(如 [`package:fl_chart`][])后,使用工具将 package 添加为依赖。 + * Generate the code: It generates the new widget code, complete with boilerplate for a line chart that it places in the UI. It even self-corrects syntax errors introduced during the process. You can customize further from there. + 生成代码:生成含折线图样板的新 widget 代码并放入 UI, + 甚至能自行纠正过程中引入的语法错误。你可在此基础上继续定制。 + What used to be a multi-step process of research, reading documentation, editing `pubspec.yaml`, and writing the appropriate code in your app, is now a single request. +以往需要调研、阅读文档、编辑 `pubspec.yaml` 并在应用中编写代码的多步流程, +现在只需一次请求。 + [`package:fl_chart`]: {{site.pub-pkg}}/fl_chart ## Provide feedback +## 提供反馈 + If you encounter any issues or have feedback about the Dart and Flutter MCP server, file an issue on the [`dart-lang/ai` issue tracker][ai-issues]. +若遇到问题或对 Dart 与 Flutter MCP server 有反馈, +请在 [`dart-lang/ai` issue 跟踪器][ai-issues] 上提交 issue。 + [ai-issues]: https://github.com/dart-lang/ai/issues diff --git a/sites/docs/src/content/app-architecture/case-study/data-layer.md b/sites/docs/src/content/app-architecture/case-study/data-layer.md index db8b7ae2a4..fef9ab74ae 100644 --- a/sites/docs/src/content/app-architecture/case-study/data-layer.md +++ b/sites/docs/src/content/app-architecture/case-study/data-layer.md @@ -1,14 +1,21 @@ --- -title: Data layer -shortTitle: Data layer +# title: Data layer +title: 数据层 +# shortTitle: Data layer +shortTitle: 数据层 +# description: >- +# A walk-through of the data layer of an app that implements MVVM architecture. description: >- - A walk-through of the data layer of an app that implements MVVM architecture. + 逐步讲解一个实现 MVVM 架构的应用的数据层。 prev: - title: UI layer + # title: UI layer + title: UI 层 path: /app-architecture/case-study/ui-layer next: - title: Dependency Injection + # title: Dependency Injection + title: 依赖注入 path: /app-architecture/case-study/dependency-injection +ai-translated: true --- @@ -17,14 +24,22 @@ is the source of truth for all application data. As the source of truth, it's the only place that application data should be updated. +应用的数据层在 MVVM 术语中称为 *model*,是所有应用数据的单一数据源。 +作为单一数据源,应用数据只应在此更新。 + It's responsible for consuming data from various external APIs, exposing that data to the UI, handling events from the UI that require data to be updated, and sending update requests to those external APIs as needed. +它负责从各类外部 API 消费数据、向 UI 暴露数据、 +处理需要更新数据的 UI 事件,并在需要时向外部 API 发送更新请求。 + The data layer in this guide has two main components, [repositories][] and [services][]. +本指南中的数据层有两个主要组件:[repository][repositories] 与 [service][services]。 + ![A diagram that highlights the data layer components of an application.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Data-highlighted.png) * **Repositories** are the source of the truth for application data, and contain @@ -32,13 +47,23 @@ The data layer in this guide has two main components, user events or polling for data from services. Repositories are responsible for synchronizing the data when offline capabilities are supported, managing retry logic, and caching data. + + **Repository** 是应用数据的单一数据源,包含与该数据相关的逻辑, + 如响应用户事件更新数据或从 service 轮询数据。 + Repository 负责在支持离线能力时同步数据、管理重试逻辑与缓存数据。 + * **Services** are stateless Dart classes that interact with APIs, like HTTP servers and platform plugins. Any data that your application needs that isn't created inside the application code itself should be fetched from within service classes. + **Service** 是无状态 Dart 类,与 HTTP 服务器、平台插件等 API 交互。 + 应用所需且非应用代码内创建的数据都应在 service 类中获取。 + ## Define a service +## 定义 service + A service class is the least ambiguous of all the architecture components. It's stateless, and its functions don't have side effects. Its only job is to wrap an external API. @@ -46,11 +71,16 @@ There's generally one service class per data source, such as a client HTTP server or a platform plugin. +Service 类是架构组件中最明确的一类:无状态,函数无副作用,唯一职责是封装外部 API。 +通常每个数据源一个 service 类,如面向客户端的 HTTP 服务器或平台插件。 + ![A diagram that shows the inputs and outputs of service objects.](/assets/images/docs/app-architecture/case-study/mvvm-case-study-services-architecture.png) In the Compass app, for example, there's an [`APIClient`][] service that handles the CRUD calls to the client-facing server. +例如 Compass 应用中有 [`APIClient`][] service,处理面向客户端服务器的 CRUD 调用。 + ```dart title=api_client.dart class ApiClient { // Some code omitted for demo purposes. @@ -79,17 +109,25 @@ exposes asynchronous response objects. Continuing the earlier example of deleting a saved booking, the `deleteBooking` method returns a `Future>`. +Service 本身是一个类,每个方法封装不同 API 端点并暴露异步响应对象。 +延续删除已保存预订的示例,`deleteBooking` 返回 `Future>`。 + :::note Some methods return data classes that are specifically for raw data from the API, such as the `BookingApiModel` class. As you'll soon see, repositories extract data and expose it in a different format. + +部分方法返回专用于 API 原始数据的数据类,如 `BookingApiModel`。 +稍后你将看到,repository 提取数据并以不同格式暴露。 ::: ## Define a repository +## 定义 repository + A repository's sole responsibility is to manage application data. A repository is the source of truth for a single type of application data, and it should be the only place where that data type is mutated. @@ -97,6 +135,10 @@ The repository is responsible for polling new data from external sources, handling retry logic, managing cached data, and transforming raw data into domain models. +Repository 的唯一职责是管理应用数据。 +Repository 是某一类应用数据的单一数据源,且应是唯一能变更该数据类型的地方。 +Repository 负责从外部源轮询新数据、处理重试逻辑、管理缓存数据,并将原始数据转换为领域模型。 + ![A diagram that highlights the repository component of an application.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Repository-highlighted.png) You should have a separate repository for @@ -104,9 +146,14 @@ each different type of data in your application. For example, the Compass app has repositories called `UserRepository`, `BookingRepository`, `AuthRepository`, `DestinationRepository`, and more. +应用中每种不同数据类型应有一个独立 Repository。 +例如 Compass 有 `UserRepository`、`BookingRepository`、`AuthRepository`、`DestinationRepository` 等。 + The following example is the `BookingRepository` from the Compass app, and shows the basic structure of a repository. +以下示例来自 Compass 的 `BookingRepository`,展示 Repository 的基本结构。 + ```dart title=booking_repository_remote.dart class BookingRepositoryRemote implements BookingRepository { BookingRepositoryRemote({ @@ -132,6 +179,11 @@ which is used for local development. You can see the differences between the [`BookingRepository` classes on GitHub][]. + +上一示例中的类是 `BookingRepositoryRemote`,继承抽象类 `BookingRepository`。 +基类用于为不同环境创建 Repository,例如 Compass 还有用于本地开发的 `BookingRepositoryLocal`。 + +可在 [GitHub 上的 `BookingRepository` 类][] 查看差异。 ::: @@ -140,20 +192,30 @@ which it uses to get and update the raw data from the server. It's important that the service is a private member, so that the UI layer can't bypass the repository and call a service directly. +`BookingRepository` 以 `ApiClient` service 为输入,用于从服务器获取与更新原始数据。 +Service 应为私有成员,以免 UI 层绕过 Repository 直接调用 service。 + With the `ApiClient` service, the repository can poll for updates to a user's saved bookings that might happen on the server, and make `POST` requests to delete saved bookings. +借助 `ApiClient`,Repository 可轮询服务器上用户已保存预订的更新,并通过 `POST` 请求删除预订。 + The raw data that a repository transforms into application models can come from multiple sources and multiple services, and therefore repositories and services have a many-to-many relationship. A service can be used by any number of repositories, and a repository can use more than one service. +Repository 转换为应用模型的原始数据可来自多个源与多个 service, +因此 repository 与 service 为多对多关系:一个 service 可被任意数量 repository 使用,一个 repository 也可使用多个 service。 + ![A diagram that highlights the data layer components of an application.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Data-highlighted.png) ### Domain models +### 领域模型 (Domain Model) + The `BookingRepository` outputs `Booking` and `BookingSummary` objects, which are *domain models*. All repositories output corresponding domain models. @@ -163,12 +225,20 @@ API models contain raw data that often needs to be filtered, combined, or deleted to be useful to the app's view models. The repo refines the raw data and outputs it as domain models. +`BookingRepository` 输出 `Booking` 与 `BookingSummary` 等 *领域模型*。 +所有 Repository 都输出对应的领域模型。 +这些数据模型与 API 模型的区别在于仅包含应用其余部分所需数据; +API 模型含常需过滤、合并或删除才有用的原始数据,Repository 精炼后以领域模型输出。 + In the example app, domain models are exposed through return values on methods like `BookingRepository.getBooking`. The `getBooking` method is responsible for getting the raw data from the `ApiClient` service, and transforming it into a `Booking` object. It does this by combining data from multiple service endpoints. +在示例应用中,领域模型通过 `BookingRepository.getBooking` 等方法返回值暴露。 +`getBooking` 从 `ApiClient` 获取原始数据并转换为 `Booking`,通过合并多个 service 端点数据实现。 + ```dart title=booking_repository_remote.dart highlightLines=14-21 // This method was edited for brevity. Future> getBooking(int id) async { @@ -208,16 +278,28 @@ This pattern is a recommendation, but not a requirement. The architecture recommended in this guide can be implemented without it. You can learn about this class in the [Result cookbook recipe][]. + +在 Compass 中,service 类返回 `Result` 对象。 +`Result` 是包装异步调用的工具类,便于处理错误与管理依赖异步调用的 UI 状态。 + +这是建议而非硬性要求,本指南架构可不使用它。 +可在 [Result 指南][Result cookbook recipe] 了解该类。 ::: ### Complete the event cycle +### 完成事件循环 + Throughout this page, you've seen how a user can delete a saved booking, starting with an event—a user swiping on a `Dismissible` widget. The view model handles that event by delegating the actual data mutation to the `BookingRepository`. The following snippet shows the `BookingRepository.deleteBooking` method. +本页你已看到用户如何删除已保存预订:从在 `Dismissible` 上滑动的事件开始, +view model 将实际数据变更委托给 `BookingRepository`。 +以下片段展示 `BookingRepository.deleteBooking` 方法。 + ```dart title=booking_repository_remote.dart Future> delete(int id) async { try { @@ -233,16 +315,25 @@ the `_apiClient.deleteBooking` method, and returns a `Result`. The `HomeViewModel` consumes the `Result` and the data it contains, then ultimately calls `notifyListeners`, completing the cycle. +Repository 通过 `_apiClient.deleteBooking` 向 API 客户端发送 `POST` 请求并返回 `Result`。 +`HomeViewModel` 消费 `Result` 及其数据,最终调用 `notifyListeners`,完成循环。 + [repositories]: /app-architecture/guide#repositories [services]: /app-architecture/guide#services [`APIClient`]: https://github.com/flutter/samples/blob/main/compass_app/app/lib/data/services/api/api_client.dart [`sealed`]: {{site.dart-site}}/language/class-modifiers#sealed [`BookingRepository` classes on GitHub]: https://github.com/flutter/samples/tree/main/compass_app/app/lib/data/repositories/booking +[GitHub 上的 `BookingRepository` 类]: https://github.com/flutter/samples/tree/main/compass_app/app/lib/data/repositories/booking [Result cookbook recipe]: /app-architecture/design-patterns/result ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="case-study/data-layer" diff --git a/sites/docs/src/content/app-architecture/case-study/dependency-injection.md b/sites/docs/src/content/app-architecture/case-study/dependency-injection.md index cbe5cd3aba..a4e12d1d1f 100644 --- a/sites/docs/src/content/app-architecture/case-study/dependency-injection.md +++ b/sites/docs/src/content/app-architecture/case-study/dependency-injection.md @@ -1,14 +1,21 @@ --- -title: Communicating between layers -shortTitle: Dependency injection +# title: Communicating between layers +title: 各层级之间的通信 +# shortTitle: Dependency injection +shortTitle: 依赖注入 +# description: >- +# How to implement dependency injection to communicate between MVVM layers. description: >- - How to implement dependency injection to communicate between MVVM layers. + 如何通过依赖注入实现 MVVM 各层之间的通信。 prev: - title: Data layer + # title: Data layer + title: 数据层 path: /app-architecture/case-study/data-layer next: - title: Testing + # title: Testing + title: 测试 path: /app-architecture/case-study/testing +ai-translated: true --- Along with defining clear responsibilities for each component of the architecture, @@ -17,32 +24,55 @@ This refers to both the rules that dictate communication, and the technical implementation of how components communicate. An app's architecture should answer the following questions: +除为架构各组件定义清晰职责外,还需考虑组件如何通信—— +既包括约束通信的规则,也包括通信的技术实现。 +应用架构应回答以下问题: + * Which components are allowed to communicate with which other components (including components of the same type)? + + 哪些组件可以与哪些其他组件(含同类型组件)通信? + * What do these components expose as output to each other? + + 这些组件向彼此暴露什么作为输出? + * How is any given layer 'wired up' to another layer? + 任意一层如何与另一层「接线」? + ![A diagram showing the components of app architecture.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified.png) Using this diagram as a guide, the rules of engagement are as follows: -| Component | Rules of engagement | +以该图为指南,协作规则如下: + +| Component组件 | Rules of engagement协作规 | |------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | View |
        1. A view is only aware of exactly one view model, and is never aware of any other layer or component. When created, Flutter passes the view model to the view as an argument, exposing the view model's data and command callbacks to the view.
    | +| View |
    1. View 仅感知恰好一个 view model,从不感知任何其他层或组件。创建时 Flutter 将 view model 作为参数传给 view,向 view 暴露 view model 的数据与 command 回调。
    | | ViewModel |
    1. A ViewModel belongs to exactly one view, which can see its data, but the model never needs to know that a view exists.
    2. A view model is aware of one or more repositories, which are passed into the view model's constructor.
| +| ViewModel |
  1. ViewModel 属于恰好一个 view,view 可见其数据,但 model 无需知道 view 存在。
  2. View model 感知一个或多个通过构造函数传入的 Repository。
| | Repository |
  1. A repository can be aware of many services, which are passed as arguments into the repository constructor.
  2. A repository can be used by many view models, but it never needs to be aware of them.
| +| Repository |
  1. Repository 可感知多个通过构造函数参数传入的 service。
  2. Repository 可被多个 view model 使用,但无需感知它们。
| | Service |
  1. A service can be used by many repositories, but it never needs to be aware of a repository (or any other object).
| +| Service |
  1. Service 可被多个 Repository 使用,但无需感知 Repository(或任何其他对象)。
| {:.table .table-striped} ## Dependency injection +## 依赖注入 + This guide has shown how these different components communicate with each other by using inputs and outputs. In every case, communication between two layers is facilitated by passing a component into the constructor methods (of the components that consume its data), such as a `Service` into a `Repository.` +本指南已说明各组件如何通过输入输出通信。 +层间通信一律通过将组件传入消费方构造函数实现,例如将 `Service` 传入 `Repository`。 + ```dart class MyRepository { MyRepository({required MyService myService}) @@ -58,14 +88,22 @@ passed into `MyRepository`? This answer to this question involves a pattern known as [dependency injection][]. +然而还缺一环:对象创建。应用中 `MyService` 实例在何处创建以便传入 `MyRepository`? +答案涉及 [依赖注入][dependency injection] 模式。 + In the Compass app, *dependency injection* is handled using [`package:provider`][]. Based on their experience building Flutter apps, teams at Google recommend using `package:provider` to implement dependency injection. +在 Compass 中,*依赖注入* 通过 [`package:provider`][] 实现。 +基于构建 Flutter 应用的经验,Google 团队推荐使用 `package:provider` 实现依赖注入。 + Services and repositories are exposed to the top level of the widget tree of the Flutter application as `Provider` objects. +Service 与 Repository 作为 `Provider` 对象暴露于 Flutter 应用 widget 树顶层。 + ```dart title=dependencies.dart runApp( MultiProvider( @@ -103,10 +141,16 @@ as shown in the preceding snippet. Repositories are then exposed so that they can be injected into view models as needed. +Service 仅为了立即通过 `provider` 的 `BuildContext.read` 注入 Repository 而暴露,如上一片段所示。 +随后暴露 Repository 以便按需注入 view model。 + Slightly lower in the widget tree, view models that correspond to a full screen are created in the [`package:go_router`][] configuration, where provider is again used to inject the necessary repositories. +在 widget 树稍低处,对应全屏的 view model 在 [`package:go_router`][] 配置中创建, +再次用 provider 注入所需 Repository。 + ```dart title=router.dart // This code was modified for demo purposes. GoRouter router( @@ -147,6 +191,8 @@ GoRouter router( Within the view model or repository, the injected component should be private. For example, the `HomeViewModel` class looks like this: +在 view model 或 Repository 内部,注入的组件应为私有。例如 `HomeViewModel` 如下: + ```dart title=home_viewmodel.dart class HomeViewModel extends ChangeNotifier { HomeViewModel({ @@ -165,20 +211,33 @@ class HomeViewModel extends ChangeNotifier { Private methods prevent the view, which has access to the view model, from calling methods on the repository directly. +私有成员防止能访问 view model 的 view 直接调用 Repository 方法。 + This concludes the code walkthrough of the Compass app. This page only walked through the architecture-related code, but it doesn't tell the whole story. Most utility code, widget code, and UI styling was ignored. Browse the code in the [Compass app repository][] for a complete example of a robust Flutter application built following these principles. +Compass 应用的代码讲解到此结束。本页仅涵盖架构相关代码,并非全貌; +大部分工具代码、widget 代码与 UI 样式未涉及。 +请在 [Compass 应用仓库][] 浏览完整示例。 + [`package:provider`]: {{site.pub-pkg}}/provider [`package:go_router`]: {{site.pub-pkg}}/go_router [Compass app repository]: https://github.com/flutter/samples/tree/main/compass_app +[Compass 应用仓库]: https://github.com/flutter/samples/tree/main/compass_app [dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection +[依赖注入]: https://en.wikipedia.org/wiki/Dependency_injection ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="case-study/dependency-injection" diff --git a/sites/docs/src/content/app-architecture/case-study/index.md b/sites/docs/src/content/app-architecture/case-study/index.md index 254b039e20..1f03c886aa 100644 --- a/sites/docs/src/content/app-architecture/case-study/index.md +++ b/sites/docs/src/content/app-architecture/case-study/index.md @@ -1,14 +1,21 @@ --- -title: Architecture case study -shortTitle: Architecture case study +# title: Architecture case study +title: 架构案例研究 +# shortTitle: Architecture case study +shortTitle: 架构案例研究 +# description: >- +# A walk-through of a Flutter app that implements the MVVM architectural pattern. description: >- - A walk-through of a Flutter app that implements the MVVM architectural pattern. + 逐步讲解一个实现 MVVM 架构模式的 Flutter 应用。 prev: - title: Guide to app architecture + # title: Guide to app architecture + title: 应用架构指南 path: /app-architecture/guide next: - title: UI Layer + # title: UI Layer + title: UI 层 path: /app-architecture/case-study/ui-layer +ai-translated: true --- The code examples in this guide are from the [Compass sample application][], @@ -20,6 +27,12 @@ includes brand-specific styling, and contains high test coverage. In these ways and more, it simulates a real-world, feature-rich Flutter application. +本指南代码示例来自 [Compass 示例应用][Compass sample application], +该应用帮助用户规划并预订行程。 +它是功能、路由与屏幕丰富的健壮示例,与 HTTP 服务器通信, +具备开发与生产环境、品牌样式与高测试覆盖率, +在多方面模拟真实世界的功能丰富 Flutter 应用。 +
@@ -34,54 +47,103 @@ implement those guidelines by walking through the "Home" feature of the compass app. If you aren't familiar with MVVM, you should read those guidelines first. +Compass 应用架构最接近 Flutter [应用架构指南][app architecture guidelines] 中描述的 [MVVM 架构模式][MVVM architectural pattern]。 +本案例研究通过逐步讲解 Compass 应用的「Home」功能,演示如何落实这些指南。 +若你不熟悉 MVVM,请先阅读该指南。 + The Home screen of the Compass app displays user account information and a list of the user's saved trips. From this screen you can log out, open detailed trip pages, delete saved trips, and navigate to the first page of the core app flow, which allows the user to build a new itinerary. +Compass 的 Home 屏幕展示用户账户信息与已保存行程列表。 +从此屏幕可登出、打开行程详情、删除已保存行程, +并导航至核心流程首页以规划新行程。 + In this case study, you'll learn the following: +在本案例研究中,你将学习: + * How to implement Flutter's [app architecture guidelines][] using repositories and services in the [data layer][] and the MVVM architectural pattern in the [UI layer][] + + 如何在 [数据层][data layer] 用 repository 与 service、在 [UI 层][UI layer] 用 MVVM 落实 Flutter [应用架构指南][app architecture guidelines] + * How to use the [Command pattern][] to safely render UI as data changes + + 如何使用 [命令模式][Command pattern] 在数据变化时安全渲染 UI + * How to use [`ChangeNotifier`][] and [`Listenable`][] objects to manage state + + 如何使用 [`ChangeNotifier`][] 与 [`Listenable`][] 管理状态 + * How to implement [Dependency Injection][] using `package:provider` + + 如何使用 `package:provider` 实现 [依赖注入][Dependency Injection] + * How to [set up tests][] when following the recommended architecture + + 按推荐架构 [搭建测试][set up tests] + * Effective [package structure][] for large Flutter apps + 大型 Flutter 应用的有效 [package 结构][package structure] + This case-study was written to be read in order. Any given page might reference the previous pages. +本案例研究建议按顺序阅读,各页可能引用前文。 + The code examples in this case-study include all the details needed to understand the architecture, but they're not complete, runnable snippets. If you prefer to follow along with the full app, you can find it on [GitHub][]. +案例中的代码示例包含理解架构所需的细节,但并非完整可运行片段。 +若希望对照完整应用,可在 [GitHub][] 查看。 + ## Package structure +## Package 结构 + Well-organized code is easier for multiple engineers to work on with minimal code conflicts and is easier for new engineers to navigate and understand. Code organization both benefits and benefits from well-defined architecture. +组织良好的代码便于多人协作、减少冲突,也便于新人浏览理解。 +代码组织既受益于清晰架构,也反过来强化架构。 + There are two popular means of organizing code: +组织代码有两种常见方式: + 1. By feature - The classes needed for each feature are grouped together. For example, you might have an `auth` directory, which would contain files like `auth_viewmodel.dart`, `login_usecase.dart`, `logout_usecase.dart`, `login_screen.dart`, `logout_button.dart`, etc. + + 按功能 — 每个功能所需的类放在一起。 + 例如 `auth` 目录可含 `auth_viewmodel.dart`、`login_usecase.dart`、`logout_usecase.dart`、`login_screen.dart`、`logout_button.dart` 等。 + 2. By type - Each "type" of architecture is grouped together. For example, you might have directories such as `repositories`, `models`, `services`, and `viewmodels`. + 按类型 — 每种架构「类型」放在一起,例如 `repositories`、`models`、`services`、`viewmodels` 等目录。 + The architecture recommended in this guide lends itself to a combination of the two. Data layer objects (repositories and services) aren't tied to a single feature, while UI layer objects (views and view models) are. The following is how the code is organized within the Compass application. +本指南推荐的架构适合两者结合: +数据层对象(repository 与 service)不绑定单一功能,UI 层对象(view 与 view model)则绑定功能。 +Compass 应用中的代码组织如下。 + - lib/ @@ -131,26 +193,49 @@ different features and by multiple view models. The ui folder organizes the code by feature, because each feature has exactly one view and exactly one view model. +大部分应用代码位于 `data`、`domain` 与 `ui` 文件夹。 +`data` 按类型组织,因为 repository 与 service 可跨功能、被多个 view model 使用。 +`ui` 按功能组织,因为每个功能恰好有一个 view 与一个 view model。 + Other notable features of this folder structure: +该文件夹结构的其他要点: + * The UI folder also contains a subdirectory named "core". Core contains widgets and theme logic that is shared by multiple views, such as buttons with your brand styling. + + UI 文件夹还有名为 `core` 的子目录,包含多 view 共享的 widget 与主题逻辑(如品牌样式按钮)。 + * The domain folder contains the application data types, because they're used by the data and ui layers. + + `domain` 文件夹包含应用数据类型,供数据层与 UI 层使用。 + * The app contains three "main" files, which act as different entry points to the application for development, staging, and production. + + 应用有三个 `main` 文件,分别作为开发、预发布与生产的入口。 + * There are two test-related directories at the same level as `lib`: `test/` has the test code, and its own structure matches `lib/`. `testing/` is a subpackage that contains mocks and other testing utilities which can be used in other packages' test code. The `testing/` folder could be described as a version of your app that you don't ship. It's the content that is tested. + 与 `lib` 同级有两个测试相关目录:`test/` 含测试代码, + 结构与 `lib/` 对应;`testing/` 是子 package,含 mock 等测试工具, + 可供其他 package 测试使用。`testing/` 可视为不随应用发布的「被测内容」版本。 + There's additional code in the compass app that doesn't pertain to architecture. For the full package structure, [view it on GitHub][]. +Compass 应用中还有与架构无关的额外代码。完整 package 结构请 [在 GitHub 上查看][view it on GitHub]。 + ## Other architecture options +## 其他架构选项 + The example in this case-study demonstrates how one application abides by our recommended architectural rules, but there are many other example apps that could've been written. The UI of this app leans heavily on view models @@ -162,6 +247,12 @@ everything with method calls, including polling for new data. It could've instead used streams to expose data from a repository to a view model and still abide by the rules covered in this guide. +本案例展示一个应用如何遵循推荐架构规则,但也可写出许多其他示例。 +本应用 UI 重度依赖 view model 与 `ChangeNotifier`, +也可用 stream 或 [`riverpod`][]、[`flutter_bloc`][]、[`signals`][] 等库实现。 +层间通信本例全部用方法调用(含轮询新数据), +也可用 stream 从 repository 向 view model 暴露数据,仍符合本指南规则。 + Even if you do follow this guide exactly, and choose not to introduce additional libraries, you have decisions to make: Will you have a domain layer? @@ -171,8 +262,14 @@ there isn't a single right answer. Regardless of how you answer these questions, the principles in this guide will help you write scalable Flutter apps. +即使完全遵循本指南、不引入额外库,你仍需决策:是否有领域层?若有,如何管理数据访问? +答案高度依赖团队需求,没有唯一正确答案。 +无论如何作答,本指南原则都有助于编写可扩展的 Flutter 应用。 + And if you squint, aren't all architectures MVVM anyway? +往大了说,一切架构不都是 MVVM 吗? + [Compass sample application]: https://github.com/flutter/samples/tree/main/compass_app [MVVM architectural pattern]: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel [app architecture guidelines]: /app-architecture/guide @@ -192,7 +289,12 @@ And if you squint, aren't all architectures MVVM anyway? ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="case-study/index" diff --git a/sites/docs/src/content/app-architecture/case-study/testing.md b/sites/docs/src/content/app-architecture/case-study/testing.md index 6e3c6b9129..e39ce0751e 100644 --- a/sites/docs/src/content/app-architecture/case-study/testing.md +++ b/sites/docs/src/content/app-architecture/case-study/testing.md @@ -1,32 +1,51 @@ --- -title: Testing each layer -shortTitle: Testing +# title: Testing each layer +title: 测试各层级 +# shortTitle: Testing +shortTitle: 测试 +# description: >- +# How to test an app that implements MVVM architecture. description: >- - How to test an app that implements MVVM architecture. + 如何测试实现 MVVM 架构的应用。 prev: - title: Dependency injection + # title: Dependency injection + title: 依赖注入 path: /app-architecture/case-study/dependency-injection +ai-translated: true --- ## Testing the UI layer +## 测试 UI 层 + One way to determine whether your architecture is sound is considering how easy (or difficult) the application is to test. Because view models and views have well-defined inputs, their dependencies can easily be mocked or faked, and unit tests are easily written. +判断架构是否合理的一种方式,是看应用是否易于测试。 +由于 view model 与 view 输入明确,依赖易于 mock 或 fake,单元测试也易编写。 + ### ViewModel unit tests +### ViewModel 单元测试 + To test the UI logic of the view model, you should write unit tests that don't rely on Flutter libraries or testing frameworks. +测试 view model 的 UI 逻辑时,应编写不依赖 Flutter 库或测试框架的单元测试。 + Repositories are a view model's only dependencies (unless you're implementing [use-cases][]), and writing `mocks` or `fakes` of the repository is the only setup you need to do. In this example test, a fake called `FakeBookingRepository` is used. +Repository 是 view model 的唯一依赖(除非实现 [用例][use-cases]), +只需为 Repository 编写 `mock` 或 `fake`。 +本示例测试使用名为 `FakeBookingRepository` 的 fake。 + ```dart title=home_screen_test.dart void main() { group('HomeViewModel tests', () { @@ -48,6 +67,9 @@ The [`FakeBookingRepository`][] class implements [`BookingRepository`][]. In the [data layer section][] of this case-study, the `BookingRepository` class is explained thoroughly. +[`FakeBookingRepository`][] 实现 [`BookingRepository`][]。 +在本案例研究的 [数据层部分][data layer section] 中对 `BookingRepository` 有详细说明。 + ```dart title=fake_booking_repository.dart class FakeBookingRepository implements BookingRepository { List bookings = List.empty(growable: true); @@ -64,15 +86,22 @@ class FakeBookingRepository implements BookingRepository { :::note If you're using this architecture with [use-cases][], these would similarly need to be faked. + +若在此架构中使用 [用例][use-cases],同样需要 fake 用例。 ::: ### View widget tests +### View widget 测试 + Once you've written tests for your view model, you've already created the fakes you need to write widget tests as well. The following example shows how the `HomeScreen` widget tests are set up using the `HomeViewModel` and needed repositories: +为 view model 写好测试后,编写 widget 测试所需的 fake 也已就绪。 +以下示例展示如何使用 `HomeViewModel` 与所需 Repository 设置 `HomeScreen` widget 测试: + ```dart title=home_screen_test.dart void main() { group('HomeScreen tests', () { @@ -100,17 +129,25 @@ This setup creates the two fake repositories needed, and passes them into a `HomeViewModel` object. This class doesn't need to be faked. +该设置创建两个 fake Repository 并传入 `HomeViewModel`,该类无需 fake。 + :::note The code also defines a `MockGoRouter`. The router is mocked using [`package:mocktail`][], and is outside the scope of this case-study. You can find general testing guidance in [Flutter's testing documentation][]. + +代码还定义了 `MockGoRouter`,使用 [`package:mocktail`][] mock 路由, +超出本案例范围。一般测试指导见 [Flutter 测试文档][Flutter's testing documentation]。 ::: After the view model and its dependencies are defined, the Widget tree that will be tested needs to be created. In the tests for `HomeScreen`, a `loadWidget` method is defined. +定义 view model 及其依赖后,需创建待测 Widget 树。 +`HomeScreen` 测试中定义了 `loadWidget` 方法。 + ```dart title=home_screen_test.dart highlightLines=11-23 void main() { group('HomeScreen tests', () { @@ -145,6 +182,8 @@ This method turns around and calls `testApp`, a generalized method used for all widget tests in the compass app. It looks like this: +该方法转而调用 Compass 应用所有 widget 测试共用的 `testApp`,如下: + ```dart title=testing/app.dart void testApp( WidgetTester tester, @@ -176,22 +215,35 @@ void testApp( This function's only job is to create a widget tree that can be tested. +该函数唯一职责是创建可测试的 widget 树。 + The `loadWidget` method passes in the unique parts of a widget tree for testing. In this case, that includes the `HomeScreen` and its view model, as well as some additional faked repositories that are higher in the widget tree. +`loadWidget` 传入测试中 widget 树的独特部分,包括 `HomeScreen` 及其 view model, +以及 widget 树更高处的额外 fake Repository。 + The most important thing to take away is that view and view model tests only require mocking repositories if your architecture is sound. +最重要的是:若架构合理,view 与 view model 测试只需 mock Repository。 + ## Testing the data layer +## 测试数据层 + Similar to the UI layer, the components of the data layer have well-defined inputs and outputs, making both sides fake-able. To write unit tests for any given repository, mock the services that it depends on. The following example shows a unit test for the `BookingRepository`. +与 UI 层类似,数据层组件输入输出明确,两侧都可 fake。 +为任意 repository 编写单元测试时,mock 其依赖的 service。 +以下示例为 `BookingRepository` 的单元测试。 + ```dart title=booking_repository_remote_test.dart void main() { group('BookingRepositoryRemote tests', () { @@ -218,6 +270,9 @@ To learn more about writing mocks and fakes, check out examples in the [Compass App `testing` directory][] or read [Flutter's testing documentation][]. +更多 mock 与 fake 示例见 [Compass 应用 `testing` 目录][Compass App `testing` directory] +或 [Flutter 测试文档][Flutter's testing documentation]。 + [use-cases]: /app-architecture/guide#optional-domain-layer [`FakeBookingRepository`]: https://github.com/flutter/samples/blob/main/compass_app/app/testing/fakes/repositories/fake_booking_repository.dart [`BookingRepository`]: https://github.com/flutter/samples/tree/main/compass_app/app/lib/data/repositories/booking @@ -228,7 +283,12 @@ read [Flutter's testing documentation][]. ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="case-study/testing" diff --git a/sites/docs/src/content/app-architecture/case-study/ui-layer.md b/sites/docs/src/content/app-architecture/case-study/ui-layer.md index e94e91766e..3ef5577851 100644 --- a/sites/docs/src/content/app-architecture/case-study/ui-layer.md +++ b/sites/docs/src/content/app-architecture/case-study/ui-layer.md @@ -1,20 +1,29 @@ --- -title: UI layer case study -shortTitle: UI layer +# title: UI layer case study +title: UI 层案例研究 +# shortTitle: UI layer +shortTitle: UI 层 +# description: >- +# A walk-through of the UI layer of an app that implements MVVM architecture. description: >- - A walk-through of the UI layer of an app that implements MVVM architecture. + 逐步讲解一个实现 MVVM 架构的应用的 UI 层。 prev: - title: Case study overview + # title: Case study overview + title: 案例研究概览 path: /app-architecture/case-study next: - title: Data Layer + # title: Data Layer + title: 数据层 path: /app-architecture/case-study/data-layer +ai-translated: true --- The [UI layer][] of each feature in your Flutter application should be made up of two components: a **[`View`][]** and a **[`ViewModel`][].** +Flutter 应用中每个功能的 [UI 层][UI layer] 应由两个组件构成:**[`View`][]** 与 **[`ViewModel`][]**。 + ![A screenshot of the booking screen of the compass app.](/assets/images/docs/app-architecture/case-study/mvvm-case-study-ui-layer-highlighted.png) In the most general sense, view models manage UI state, @@ -26,8 +35,14 @@ Each pair of view and view model make up the UI for a single feature. For example, an app might have classes called `LogOutView` and a `LogOutViewModel`. +概括而言,view model 管理 UI 状态,view 展示 UI 状态; +二者一一对应,每对 view 与 view model 构成单一功能的 UI。 +例如应用可有 `LogOutView` 与 `LogOutViewModel`。 + ## Define a view model +## 定义 view model + A view model is a Dart class responsible for handling UI logic. View models take domain data models as input and expose that data as UI state to their corresponding views. @@ -36,6 +51,9 @@ event handlers, like button presses, and manage sending these events to the data layer of the app, where data changes happen. +View model 以领域数据模型为输入,向对应 view 暴露为 UI 状态; +封装 view 可挂到按钮按压等事件处理器的逻辑,并将事件发往发生数据变更的应用数据层。 + The following code snippet is a class declaration for a view model class called the `HomeViewModel`. Its inputs are the [repositories][] that provide its data. @@ -43,6 +61,9 @@ In this case, the view model is dependent on the `BookingRepository` and `UserRepository` as arguments. +以下片段为 `HomeViewModel` 的类声明,输入为提供数据的 [Repository][repositories]; +本例依赖 `BookingRepository` 与 `UserRepository` 作为参数。 + ```dart title=home_viewmodel.dart class HomeViewModel { HomeViewModel({ @@ -64,17 +85,27 @@ which are provided as arguments to the view model's constructor. View models and repositories have a many-to-many relationship, and most view models will depend on multiple repositories. +View model 始终依赖通过构造函数传入的数据 Repository; +与 Repository 为多对多关系,多数 view model 依赖多个 Repository。 + As in the earlier `HomeViewModel` example declaration, repositories should be private members on the view model, otherwise views would have direct access to the data layer of the application. +如前述 `HomeViewModel`,Repository 应为 view model 的私有成员,否则 view 可直接访问数据层。 + ### UI state +### UI 状态 + The output of a view model is data that a view needs to render, generally referred to as **UI State**, or just state. UI state is an immutable snapshot of data that is required to fully render a view. +View model 的输出是 view 渲染所需数据,通常称为 **UI State** 或简称 state。 +UI state 是完整渲染 view 所需数据的不可变快照。 + ![A screenshot of the booking screen of the compass app.](/assets/images/docs/app-architecture/case-study/mvvm-case-study-ui-state-highlighted.png) The view model exposes state as public members. @@ -83,6 +114,8 @@ the exposed data is a `User` object, as well as the user's saved itineraries which are exposed as an object of type `List`. +View model 以公共成员暴露状态。下例中暴露 `User` 及类型为 `List` 的已保存行程。 + ```dart title=home_viewmodel.dart class HomeViewModel { HomeViewModel({ @@ -112,6 +145,8 @@ class HomeViewModel { As mentioned, the UI state should be immutable. This is a crucial part of bug-free software. +如前所述,UI state 应不可变,这是少 bug 软件的关键。 + The compass app uses the [`package:freezed`][] to enforce immutability on data classes. For example, the following code shows the `User` class definition. @@ -119,6 +154,9 @@ the following code shows the `User` class definition. and generates the implementation for useful methods like `copyWith` and `toJson`. +Compass 应用使用 [`package:freezed`][] 强制数据类不可变;下例为 `User` 定义。 +`freezed` 提供深层不可变并生成 `copyWith`、`toJson` 等方法。 + ```dart title=user.dart @freezed class User with _$User { @@ -143,15 +181,24 @@ many more repositories exposed to the view. In some cases, you might want to create objects that specifically represent the UI state. For example, you could create a class named `HomeUiState`. + +下例渲染 view 需要两个对象。随 UI state 变复杂, +view model 可能从更多 repository 向 view 暴露更多数据。 +有时可创建专门表示 UI state 的对象,例如 `HomeUiState`。 ::: ### Updating UI state +### 更新 UI 状态 + In addition to storing state, view models need to tell Flutter to re-render views when the data layer provides a new state. In the Compass app, view models extend [`ChangeNotifier`][] to achieve this. +除存储状态外,数据层提供新状态时 view model 须通知 Flutter 重新渲染 view。 +Compass 中 view model 继承 [`ChangeNotifier`][] 实现这一点。 + ```dart title=home_viewmodel.dart class HomeViewModel [!extends ChangeNotifier!] { HomeViewModel({ @@ -176,21 +223,39 @@ class HomeViewModel [!extends ChangeNotifier!] { When new data flows from the data layer and new state needs to be emitted, [`notifyListeners`][] is called. +`HomeViewModel.user` 是 view 依赖的公共成员;数据层流入新数据需发出新状态时,调用 [`notifyListeners`][]。 +
![A screenshot of the booking screen of the compass app.](/assets/images/docs/app-architecture/case-study/mvvm-case-study-update-ui-steps.png)
+ This figure shows from a high-level how new data in the repository propagates up to the UI layer and triggers a re-build of your Flutter widgets. + + +该图从宏观展示 Repository 中的新数据如何向上传到 UI 层并触发 Flutter widget 重建。 +
1. New state is provided to the view model from a Repository. + + Repository 向 view model 提供新状态。 + 2. The view model updates its UI state to reflect the new data. + + View model 更新 UI 状态以反映新数据。 + 3. `ViewModel.notifyListeners` is called, alerting the View of new UI State. + + 调用 `ViewModel.notifyListeners`,通知 View 有新 UI State。 + 4. The view (widget) re-renders. + View (widget) 重新渲染。 + For example, when the user navigates to the Home screen and the view model is created, the `_load` method is called. Until this method completes, the UI state is empty, @@ -199,6 +264,9 @@ When the `_load` method completes, if it's successful, there's new data in the view model, and it must notify the view that new data is available. +例如用户进入 Home 屏幕并创建 view model 时调用 `_load`; +完成前 UI state 为空,view 显示加载指示器;成功完成后 view model 有新数据,须通知 view。 + ```dart title=home_viewmodel.dart highlightLines=19 class HomeViewModel extends ChangeNotifier { // ... @@ -233,6 +301,11 @@ You can also use a robust third-party state management solution, such as These libraries offer different tools for handling UI updates. Read more about using `ChangeNotifier` in our [state-management documentation][]. + +`ChangeNotifier` 与 [`ListenableBuilder`][](后文讨论)属于 Flutter SDK, +是状态变化时更新 UI 的良好方案。 +也可使用 [`package:riverpod`][]、[`package:flutter_bloc`][]、[`package:signals`][] 等第三方状态管理库。 +更多 `ChangeNotifier` 用法见 [状态管理文档][state-management documentation]。 ::: [`package:riverpod`]: {{site.pub-pkg}}/riverpod @@ -242,11 +315,15 @@ our [state-management documentation][]. ## Define a view +## 定义 view + A view is a widget within your app. Often, a view represents one screen in your app that has its own route and includes a [`Scaffold`][] at the top of the widget subtree, such as the `HomeScreen`, but this isn't always the case. +View 是应用内的 widget。常代表带独立路由、widget 树顶层含 [`Scaffold`][] 的屏幕(如 `HomeScreen`),但未必如此。 + Sometimes a view is a single UI element that encapsulates functionality that needs to be re-used throughout the app. For example, the Compass app has a view called `LogoutButton`, @@ -256,25 +333,43 @@ The `LogoutButton` view has its own view model called `LogoutViewModel`. And on larger screens, there might be multiple views on screen that would take up the full screen on mobile. +有时 view 是可在应用中复用的单一 UI 元素,例如 Compass 的 `LogoutButton` 及其 `LogoutViewModel`; +大屏上可能同时显示多个在手机上占全屏的 view。 + :::note "View" is an abstract term, and one view doesn't equal one widget. Widgets are composable, and several can be combined to create one view. Therefore, view models don't have a one-to-one relationship with widgets, but rather a one-to-one relation with a *collection* of widgets. + +「View」是抽象概念,一个 view 不等于一个 widget;多个 widget 可组成一个 view。 +因此 view model 与 widget 是一对 *一组* widget 的关系,而非一对一。 ::: The widgets within a view have three responsibilities: +View 内 widget 有三项职责: + * They display the data properties from the view model. + + 展示 view model 的数据属性。 + * They listen for updates from the view model and re-render when new data is available. + + 监听 view model 更新并在有新数据时重新渲染。 + * They attach callbacks from the view model to event handlers, if applicable. + 将 view model 的回调挂到事件处理器(如适用)。 + ![A diagram showing a view's relationship to a view model.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-View-highlighted.png) Continuing the Home feature example, the following code shows the definition of the `HomeScreen` view. +延续 Home 功能示例,以下展示 `HomeScreen` view 的定义。 + ```dart title=home_screen.dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key, required this.viewModel}); @@ -294,12 +389,19 @@ Most of the time, a view's only inputs should be a `key`, which all Flutter widgets take as an optional argument, and the view's corresponding view model. +多数情况下 view 的输入仅为可选 `key` 与对应 view model。 + ### Display UI data in a view +### 在 view 中展示 UI 数据 + A view depends on a view model for its state. In the Compass app, the view model is passed in as an argument in the view's constructor. The following example code snippet is from the `HomeScreen` widget. +View 依赖 view model 获取状态;Compass 在 view 构造函数中传入 view model。 +以下片段来自 `HomeScreen` widget。 + ```dart title=home_screen.dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key, [!required this.viewModel!]}); @@ -317,6 +419,8 @@ Within the widget, you can access the passed-in bookings from the `viewModel`. In the following code, the `booking` property is being provided to a sub-widget. +在 widget 内可通过 `viewModel` 访问传入的 bookings;下例将 `booking` 传给子 widget。 + ```dart title=home_screen.dart @override Widget build(BuildContext context) { @@ -350,6 +454,8 @@ the `booking` property is being provided to a sub-widget. ### Update the UI +### 更新 UI + The `HomeScreen` widget listens for updates from the view model with the [`ListenableBuilder`][] widget. Everything in the widget subtree under the `ListenableBuilder` widget @@ -358,6 +464,9 @@ In this case, the provided `Listenable` is the view model. Recall that the view model is of type [`ChangeNotifier`][] which is a subtype of the `Listenable` type. +`HomeScreen` 通过 [`ListenableBuilder`][] 监听 view model; +其子树在 [`Listenable`][] 变化时重建,此处为 [`ChangeNotifier`][] 类型的 view model。 + ```dart title=home_screen.dart @override Widget build(BuildContext context) { @@ -397,18 +506,26 @@ Widget build(BuildContext context) { ### Handling user events +### 处理用户事件 + Finally, a view needs to listen for *events* from users, so the view model can handle those events. This is achieved by exposing a callback method on the view model class which encapsulates all the logic. +最后,view 须监听用户 *事件*,由 view model 通过暴露封装逻辑的回调处理。 + ![A diagram showing a view's relationship to a view model.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-UI-highlighted.png) On the `HomeScreen`, users can delete previously booked events by swiping a [`Dismissible`][] widget. +在 `HomeScreen` 上,用户可通过滑动 [`Dismissible`][] 删除已预订项。 + Recall this code from the previous snippet: +回顾上一片段中的代码: + > _deleteBooking(int id) async { try { @@ -470,22 +591,32 @@ Future> _deleteBooking(int id) async { In the Compass app, these methods that handle user events are called **commands**. +在 Compass 中,这些处理用户事件的方法称为 **command**。 + ### Command objects +### Command 对象 + Commands are responsible for the interaction that starts in the UI layer and flows back to the data layer. In this app specifically, a `Command` is also a type that helps update the UI safely, regardless of the response time or contents. +Command 负责从 UI 层回流数据层的交互;`Command` 类型可安全更新 UI,不受响应时间或内容影响。 + The `Command` class wraps a method and helps handle the different states of that method, such as `running`, `complete`, and `error`. These states make it easy to display different UI, like loading indicators when `Command.running` is true. +`Command` 包装方法并处理 `running`、`complete`、`error` 等状态,便于展示加载等 UI。 + The following is code from the `Command` class. Some code has been omitted for demo purposes. +以下为 `Command` 类代码(部分省略)。 + ```dart title=command.dart abstract class Command extends ChangeNotifier { Command(); @@ -523,6 +654,8 @@ and within the method `Command.execute`, This allows the view to handle different states with very little logic, which you'll see an example of later on this page. +`Command` 继承 `ChangeNotifier`,`execute` 中多次 `notifyListeners()`,使 view 以极少逻辑处理多状态(后文有例)。 + You may have also noticed that `Command` is an abstract class. It's implemented by concrete classes such as `Command0` `Command1`. The integer in the class name refers to @@ -530,17 +663,25 @@ the number of arguments that the underlying method expects. You can see examples of these implementation classes in the Compass app's [`utils` directory][]. +`Command` 为抽象类,由 `Command0`、`Command1` 等实现,数字表示参数个数;示例见 Compass [`utils` 目录][`utils` directory]。 + :::tip Package recommendation Instead of writing your own `Command` class, consider using the [`flutter_command`][] package, which is a robust library that implements classes like these. + +也可使用 [`flutter_command`][] 等库,无需自行实现 `Command`。 ::: ### Ensuring views can render before data exists +### 确保 view 在数据存在前即可渲染 + In view model classes, commands are created in the constructor. +在 view model 构造函数中创建 command。 + ```dart title=home_viewmodel.dart highlightLines=8-9,15-16,24-30 class HomeViewModel extends ChangeNotifier { HomeViewModel({ @@ -583,6 +724,9 @@ the view wants to render. This gets at *why* the Compass app uses `Commands`. In the view's `Widget.build` method, the command is used to conditionally render different widgets. +`Command.execute` 是异步的,无法保证 view 渲染时数据已就绪——这正是 Compass 使用 `Command` 的原因; +在 `Widget.build` 中用 command 条件渲染不同 widget。 + ```dart title=home_screen.dart // ... child: ListenableBuilder( @@ -617,6 +761,8 @@ the `HomeScreen` widget was even created, it isn't a problem because the `Command` object still exists, and exposes the correct state. +`load` command 是 view model 上的持久属性,调用与完成时机不影响正确状态暴露。 + This pattern standardizes how common UI problems are solved in the app, making your codebase less error-prone and more scalable, but it's not a pattern that every app will want to implement. @@ -629,9 +775,14 @@ For example, if you were to use the [`AsyncSnapshot`][] classes provided by Flutter have this functionality built in. +该模式标准化常见 UI 问题的解决方式,但并非所有应用都需要; +是否采用取决于其他架构选择。许多状态管理库自带类似工具,例如 [stream][streams] 与 [`StreamBuilders`][] 配合 [`AsyncSnapshot`][]。 + :::note Real world example While building the Compass app, we found a bug that was solved by using the Command pattern. [Read about it on GitHub][]. + +构建 Compass 时曾用命令模式修复一个 bug。[在 GitHub 上阅读][Read about it on GitHub]。 ::: [UI layer]: /app-architecture/guide#ui-layer @@ -655,7 +806,12 @@ the Command pattern. [Read about it on GitHub][]. ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="case-study/ui-layer" diff --git a/sites/docs/src/content/app-architecture/concepts.md b/sites/docs/src/content/app-architecture/concepts.md index 76067a4a6d..339d9390c0 100644 --- a/sites/docs/src/content/app-architecture/concepts.md +++ b/sites/docs/src/content/app-architecture/concepts.md @@ -1,15 +1,22 @@ --- -title: Common architecture concepts -shortTitle: Architecture concepts +# title: Common architecture concepts +title: 常见架构设计理念 +# shortTitle: Architecture concepts +shortTitle: 架构设计理念 +# description: > +# Learn about common architecture concepts in application design, +# and how they apply to Flutter. description: > - Learn about common architecture concepts in application design, - and how they apply to Flutter. + 了解应用设计中的常见架构设计理念及其在 Flutter 中的运用。 prev: - title: Architecting Flutter apps - path: /app-architecture + # title: Architecting Flutter apps + title: 构建 Flutter 应用架构 + path: /app-architecture next: - title: Guide to app architecture - path: /app-architecture/guide + # title: Guide to app architecture + title: 应用架构指南 + path: /app-architecture/guide +ai-translated: true --- In this section, you'll find tried and true principles that guide architectural @@ -19,8 +26,15 @@ It's a gentle introduction to vocabulary and concepts related to the recommended architecture and best practices, so they can be explored in more detail throughout this guide. +在本节中,你将找到指导更广泛应用开发领域架构决策的成熟原则, +以及它们如何具体适用于 Flutter 的说明。 +这是对推荐架构与最佳实践相关术语与概念的温和入门, +以便在本指南其余部分深入探讨。 + ## Separation of concerns +## 关注点分离 + [Separation-of-concerns][] is a core principle in app development that promotes modularity and maintainability by dividing an application's functionality into distinct, self-contained units. From a high-level, @@ -30,21 +44,39 @@ Within each layer, you should further separate your application by feature or functionality. For example, your application's authentication logic should be in a different class than the search logic. +[关注点分离][Separation-of-concerns] 是应用开发的核心原则, +通过将应用功能划分为彼此独立、自包含的单元来提升模块化与可维护性。 +从宏观上看,这意味着将 UI 逻辑与业务逻辑分开, +通常称为 **分层** 架构。 +在各层内部,还应按功能或特性进一步拆分应用。 +例如,应用的认证逻辑应与搜索逻辑放在不同的类中。 + In Flutter, this applies to [widgets](/resources/glossary#widget) in the UI layer as well. You should write reusable, lean widgets that hold as little logic as possible. +在 Flutter 中,这也适用于 UI 层中的 [widget](/resources/glossary#widget)。 +你应编写可复用、精简、尽可能少包含逻辑的 widget。 + ## Layered architecture +## 分层架构 + Flutter applications should be written in *layers*. Layered architecture is a software design pattern that organizes an application into distinct layers, each with specific roles and responsibilities. Typically, applications are separated into 2 to 3 layers, depending on complexity. +Flutter 应用应按 **层** 编写。分层架构是一种软件设计模式, +将应用组织为职责明确的若干层。通常根据复杂度分为 2 到 3 层。 + The three common layers of app architecture, the UI layer, logic layer, and data layer. * **UI layer** - Displays data to the user that is exposed by the business logic layer, and handles user interaction. This is also commonly referred to as the "presentation layer". + + **UI 层** — 向用户展示业务逻辑层暴露的数据,并处理用户交互。也常称为「展示层」。 + * **Logic layer** - Implements core business logic, and facilitates interaction between the data layer and UI layer. Commonly known as the "domain layer". The logic layer is optional, and only needs to be implemented if your @@ -52,27 +84,49 @@ into 2 to 3 layers, depending on complexity. Many apps are only concerned with presenting data to a user and allowing the user to change that data (colloquially known as CRUD apps). These apps might not need this optional layer. + + **逻辑层** — 实现核心业务逻辑,并协调数据层与 UI 层之间的交互。常称为「领域层」。 + 逻辑层是可选的,仅当应用在客户端有复杂业务逻辑时才需要实现。 + 许多应用只关注向用户展示数据并允许用户修改数据(俗称 CRUD 应用), + 可能不需要这一可选层。 + * **Data layer** - Manages interactions with data sources, such as databases or platform plugins. Exposes data and methods to the business logic layer. + **数据层** — 管理与数据库、平台插件等数据源的交互,并向业务逻辑层暴露数据与方法。 + These are called "layers" because each layer can only communicate with the layers directly below or above it. The UI layer shouldn't know that the data layer exists, and vice versa. +之所以称为「层」,是因为每一层只能与紧邻的上下层通信。 +UI 层不应知道数据层的存在,反之亦然。 + ## Single source of truth +## 单一数据源 + Every data type in your app should have a [single source of truth][] (SSOT). The source of truth is responsible for representing local or remote state. If the data can be modified in the app, the SSOT class should be the only class that can do so. +应用中每种数据类型都应有 [单一数据源][single source of truth] (SSOT)。 +数据源负责表示本地或远程状态。 +若数据可在应用内修改,SSOT 类应是唯一能修改它的类。 + This can dramatically reduce the number of bugs in your application, and it can simplify code because you'll only ever have one copy of the same data. +这能显著减少应用中的 bug 数量,并简化代码,因为同一份数据只会有一个副本。 + Generally, the source of truth for any given type of data in your application is held in a class called a **Repository**, which is part of the data layer. There is typically one repository class for each type of data in your app. +通常,应用中某类数据的单一数据源由数据层中称为 **Repository(仓库)** 的类持有。 +应用中每种数据类型通常对应一个 Repository 类。 + This principle can be applied across layers and components in your application as well as within individual classes. For example, a Dart class might use [getters][] to derive values from an SSOT field @@ -80,8 +134,16 @@ a Dart class might use [getters][] to derive values from an SSOT field or a list of [records][] to group related values (instead of parallel lists whose indices might get out of sync). +该原则可应用于应用各层与各组件,也可应用于单个类内部。 +例如,Dart 类可用 [getter][getters] 从 SSOT 字段派生值 +(而不是维护多个需独立更新的字段), +或用 [record][records] 列表将相关值分组 +(而不是使用索引可能失步的并行列表)。 + ## Unidirectional data flow +## 单向数据流 + [Unidirectional data flow][] (UDF) refers to a design pattern that helps decouple state from the UI that displays that state. In the simplest terms, state flows from the data layer through the logic layer and eventually to the @@ -89,21 +151,41 @@ widgets in the UI layer. Events from user-interaction flow the opposite direction, from the presentation layer back through the logic layer and to the data layer. +[单向数据流][Unidirectional data flow] (UDF) 是一种有助于将状态与展示该状态的 UI 解耦的设计模式。 +简而言之,状态从数据层经逻辑层最终流向 UI 层中的 widget; +用户交互事件则沿相反方向,从展示层经逻辑层回到数据层。 + The three common layers of app architecture, the UI layer, logic layer, and data layer, and the flow of state from the data layer to the UI layer. In UDF, the update loop from user interaction to re-rendering the UI looks like this: +在 UDF 中,从用户交互到 UI 重新渲染的更新循环如下: + 1. [UI layer] An event occurs due to user interaction, such as a button being clicked. The widget's event handler callback invokes a method exposed by a class in the logic layer. + + [UI 层] 因用户交互(如按钮点击)产生事件。widget 的事件处理回调调用逻辑层类暴露的方法。 + 2. [Logic layer] The logic class calls methods exposed by a repository that know how to mutate the data. + + [逻辑层] 逻辑类调用 Repository 暴露的、知道如何变更数据的方法。 + 3. [Data layer] The repository updates data (if necessary) and then provides the new data to the logic class. + + [数据层] Repository 更新数据(如有必要),再将新数据提供给逻辑类。 + 4. [Logic layer] The logic class saves its new state, which it sends to the UI. + + [逻辑层] 逻辑类保存新状态并发送给 UI。 + 5. [UI layer] The UI displays the new state of the view model. + [UI 层] UI 展示 view model 的新状态。 + New data can also start at the data layer. For example, a repository might poll an HTTP server for new data. In this case, the data flow only makes the second half of the journey. @@ -112,15 +194,27 @@ in the [SSOT][], which is the data layer. This makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created. +新数据也可以从数据层发起。 +例如,Repository 可能轮询 HTTP 服务器获取新数据。 +此时数据流只完成后半段旅程。 +最重要的是:数据变更始终发生在作为数据层的 [SSOT][] 中。 +这使代码更易理解、更少出错,并避免产生畸形或意外数据。 + ## UI is a function of (immutable) state +## UI 是(不可变)状态的函数 + Flutter is declarative, meaning that it builds its UI to reflect the current state of your app. When state changes, your app should trigger a rebuild of the UI that depends on that state. In Flutter, you'll often hear this described as "UI is a function of state". +Flutter 是声明式的,即根据应用当前状态构建 UI。 +当状态变化时,应用应触发依赖该状态的 UI 重建。 +在 Flutter 中,这常被表述为「UI 是状态的函数」。 + UI is a function of state. It's crucial that your data drive your UI, and not the other way around. @@ -129,33 +223,56 @@ and views should contain as little logic as possible. This minimizes the possibility of data being lost when an app is closed, and makes your app more testable and resilient to bugs. +关键是让数据驱动 UI,而非相反。 +数据应不可变且持久,view 应尽可能少包含逻辑。 +这能尽量减少应用关闭时数据丢失的可能,并提升可测试性与抗 bug 能力。 + ## Extensibility +## 可扩展性 + Each piece of architecture should have a well defined list of inputs and outputs. For example, a view model in the logic layer should only take in data sources as inputs, such as repositories, and should only expose commands and data formatted for views. +架构的每一部分都应有明确定义的输入与输出列表。 +例如,逻辑层中的 view model 应仅以 repository 等数据源为输入, +并仅向 view 暴露为其格式化的命令与数据。 + Using clean interfaces in this way allows you to swap out concrete implementations of your classes without needing to change any of the code that consumes the interface. +以这种方式使用清晰接口,可在不修改消费方代码的情况下替换类的具体实现。 + ## Testability +## 可测试性 + The principles that make software extensible also make software easier to test. For example, you can test the self-contained logic of a view model by mocking a repository. The view model tests don't require you to mock other parts of your application, and you can test your UI logic separate from Flutter widgets themselves. +使软件可扩展的原则同样使软件更易测试。 +例如,可通过 mock repository 测试 view model 的自包含逻辑。 +view model 测试无需 mock 应用其他部分,且可将 UI 逻辑与 Flutter widget 本身分开测试。 + Your app will also be more flexible. It will be straightforward and low risk to add new logic and new UI. For example, adding a new view model cannot break any logic from the data or business logic layers. +应用也会更灵活,新增逻辑与 UI 将直接且低风险。 +例如,新增 view model 不会破坏数据层或业务逻辑层的任何逻辑。 + The next section explains the idea of inputs and outputs for any given component in your application's architecture. +下一节将说明应用架构中任意组件的输入与输出概念。 + [Separation-of-concerns]: https://en.wikipedia.org/wiki/Separation_of_concerns [single source of truth]: https://en.wikipedia.org/wiki/Single_source_of_truth [SSOT]: https://en.wikipedia.org/wiki/Single_source_of_truth @@ -165,7 +282,12 @@ in your application's architecture. ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="concepts" diff --git a/sites/docs/src/content/app-architecture/design-patterns.md b/sites/docs/src/content/app-architecture/design-patterns.md index b200cc5a34..f93385b476 100644 --- a/sites/docs/src/content/app-architecture/design-patterns.md +++ b/sites/docs/src/content/app-architecture/design-patterns.md @@ -1,25 +1,39 @@ --- -title: Architecture design patterns -shortTitle: Design patterns +# title: Architecture design patterns +title: 架构设计模式 +# shortTitle: Design patterns +shortTitle: 设计模式 +# description: >- +# A collection of articles about useful design patterns for +# building Flutter applications. description: >- - A collection of articles about useful design patterns for - building Flutter applications. + 关于构建 Flutter 应用时有用的设计模式的文章合集。 prev: - title: Recommendations + # title: Recommendations + title: 架构建议 path: /app-architecture/recommendations showToc: false +ai-translated: true --- If you've already read through the [architecture guide][] page, or if you're comfortable with Flutter and the MVVM pattern, the following articles are for you. +若你已阅读 [架构指南][architecture guide] 页面, +或已熟悉 Flutter 与 MVVM 模式, +以下文章适合你。 + These articles aren't about high-level app architecture, rather they're about solving specific design problems that improve your application's code base regardless of how you've architected your app. That said, the articles do assume the MVVM pattern laid out on the previous pages in the code examples. +这些文章不讨论高层应用架构, +而是聚焦解决特定设计问题,无论你的应用采用何种架构都能改善代码库。 +不过,文中代码示例假定采用前几页介绍的 MVVM 模式。 + [architecture guide]: /app-architecture/guide diff --git a/sites/docs/src/content/app-architecture/design-patterns/command.md b/sites/docs/src/content/app-architecture/design-patterns/command.md index e60fdf30cb..c983d38967 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/command.md +++ b/sites/docs/src/content/app-architecture/design-patterns/command.md @@ -1,12 +1,15 @@ --- -title: The command pattern -description: "Simplify view model logic by implementing a Command class." +# title: The command pattern +title: 命令模式 +# description: "Simplify view model logic by implementing a Command class." +description: "通过实现 Command 类简化 view model 逻辑。" contentTags: - mvvm - asynchronous dart - state iconPath: /assets/images/docs/app-architecture/design-patterns/command-icon.svg order: 4 +ai-translated: true --- @@ -18,32 +21,49 @@ Views and view models make up the UI layer of an application. Repositories and services represent the data layer of an application, or the model layer of MVVM. +[Model-View-ViewModel (MVVM)][] 是一种设计模式,将应用的一个功能拆为 model、view model 与 view 三部分。 +View 与 view model 构成应用的 UI 层;repository 与 service 代表数据层,即 MVVM 的 model 层。 + A command is a class that wraps a method and helps to handle the different states of that method, such as running, complete, and error. +Command 是包装方法并帮助处理方法各状态(如 running、complete、error)的类。 + [View models][] can use commands to handle interaction and run actions. You can also use them to display different UI states, like loading indicators when an action is running, or display an error dialog when an action failed. +[View model][View models] 可用 command 处理交互与执行操作,也可用于展示不同 UI 状态, +例如操作进行中显示加载指示器,失败时显示错误对话框。 + View models can become very complex as an application grows and features become bigger. Commands can help to simplify view models and reuse code. +随应用与功能增长,view model 可能变得非常复杂;command 有助于简化 view model 并复用代码。 + In this guide, you will learn how to use the command pattern to improve your view models. +本指南介绍如何使用命令模式改进 view model。 + ## Challenges when implementing view models +## 实现 view model 时的挑战 + View model classes in Flutter are typically implemented by extending the [`ChangeNotifier`][] class. This allows view models to call `notifyListeners()` to refresh views when data is updated. +Flutter 中的 view model 通常通过继承 [`ChangeNotifier`][] 实现, +以便在数据更新时调用 `notifyListeners()` 刷新 view。 + ```dart class HomeViewModel extends ChangeNotifier { @@ -55,6 +75,9 @@ View models contain a representation of the UI state, including the data being displayed. For example, this `HomeViewModel` exposes the `User` instance to the view. +View model 包含 UI 状态表示,包括展示的数据。 +例如该 `HomeViewModel` 向 view 暴露 `User` 实例。 + ```dart class HomeViewModel extends ChangeNotifier { @@ -67,6 +90,8 @@ class HomeViewModel extends ChangeNotifier { View models also contain actions typically triggered by the view, such as a `load` action in charge of loading the `user`. +View model 还包含通常由 view 触发的操作,例如负责加载 `user` 的 `load` 操作。 + ```dart class HomeViewModel extends ChangeNotifier { @@ -82,10 +107,15 @@ class HomeViewModel extends ChangeNotifier { ### UI state in view models +### View model 中的 UI 状态 + A view model also contains UI state besides data, such as whether the view is running or has experienced an error. This allows the app to tell the user if the action has completed successfully. +除数据外,view model 还包含 UI 状态,例如是否正在运行或是否出错, +以便告知用户操作是否成功完成。 + ```dart class HomeViewModel extends ChangeNotifier { @@ -105,6 +135,8 @@ class HomeViewModel extends ChangeNotifier { You can use the running state to display a progress indicator in the view: +可用 running 状态在 view 中显示进度指示器: + ```dart ListenableBuilder( @@ -120,6 +152,8 @@ ListenableBuilder( Or use the running state to avoid executing the action multiple times: +或用 running 状态避免重复执行操作: + ```dart void load() { @@ -135,6 +169,9 @@ if the view model contains multiple actions. For example, adding an `edit()` action to the `HomeViewModel` can lead the following outcome: +若 view model 包含多个操作,管理操作状态会变复杂。 +例如向 `HomeViewModel` 添加 `edit()` 可能导致: + ```dart class HomeViewModel extends ChangeNotifier { @@ -164,18 +201,30 @@ because you might want to show a different UI component when the `load()` action runs than when the `edit()` action runs; you'll have the same problem with the `error` state. +在 `load()` 与 `edit()` 间共享 running 状态未必可行, +因两种操作可能需要不同 UI 组件;`error` 状态也有同样问题。 + ### Triggering UI actions from view models +### 从 view model 触发 UI 操作 + View model classes can run into problems when executing UI actions and the view model's state changes. +执行 UI 操作且 view model 状态变化时,view model 可能遇到问题。 + For example, you might want to show a `SnackBar` when an error occurs, or navigate to a different screen when an action completes. To implement this, listen for changes in the view model, and perform the action depending on the state. +例如出错时显示 `SnackBar`,操作完成时导航到其他屏幕。 +实现方式是监听 view model 变化并按状态执行操作。 + In the view: +在 view 中: + ```dart @override @@ -203,6 +252,8 @@ void _onViewModelChanged() { You need to clear the error state each time you execute this action, otherwise this action happens each time `notifyListeners()` is called. +每次执行该操作后须清除 error 状态,否则每次 `notifyListeners()` 都会重复触发。 + ```dart void _onViewModelChanged() { @@ -215,15 +266,22 @@ void _onViewModelChanged() { ## Command pattern +## 命令模式 + You might find yourself repeating the above code over and over, implementing a different running state for each action in every view model. At that point, it makes sense to extract this code into a reusable pattern called a _command_. +你可能反复编写上述代码,为每个 view model 的每个操作实现不同 running 状态。 +此时将代码提取为可复用的 **command** 模式是合理的。 + A command is a class that encapsulates a view model action, and exposes the different states that an action can have. +Command 封装 view model 操作并暴露操作可能处于的各种状态。 + ```dart class Command extends ChangeNotifier { @@ -251,6 +309,8 @@ In the view model, instead of defining an action directly with a method, you create a command object: +在 view model 中,不直接用方法定义操作,而是创建 command 对象: + ```dart class HomeViewModel extends ChangeNotifier { @@ -273,15 +333,24 @@ and instead the command `load` gets exposed to the `View`. The previous `running` and `error` states can be removed, as they are now part of the command. +原 `load()` 变为 `_load()`,向 `View` 暴露 command `load`; +原 `running` 与 `error` 可移除,已归入 command。 + ### Executing a command +### 执行 command + Instead of calling `viewModel.load()` to run the load action, now you call `viewModel.load.execute()`. +不再调用 `viewModel.load()`,而调用 `viewModel.load.execute()`。 + The `execute()` method can also be called from within the view model. The following line of code runs the `load` command when the view model is created. +`execute()` 也可在 view model 内部调用;以下代码在创建 view model 时运行 `load` command。 + ```dart HomeViewModel() { @@ -295,18 +364,27 @@ When the action finishes, the `running` state changes to `false` and the `completed` state to `true`. +`execute()` 将 running 设为 `true` 并重置 `error` 与 `completed`; +操作结束时 running 为 `false`,completed 为 `true`。 + If the `running` state is `true`, the command cannot begin executing again. This prevents users from triggering a command multiple times by pressing a button rapidly. +running 为 `true` 时 command 无法再次执行,防止用户快速连按重复触发。 + The command’s `execute()` method captures any thrown `Exceptions` automatically and exposes them in the `error` state. +command 的 `execute()` 自动捕获抛出的 `Exception` 并暴露在 `error` 状态。 + The following code shows a sample `Command` class that has been simplified for demo purposes. You can see a full implementation at the end of this page. +以下为简化的 `Command` 示例,完整实现见文末。 + ```dart class Command extends ChangeNotifier { @@ -354,13 +432,19 @@ class Command extends ChangeNotifier { ### Listening to the command state +### 监听 command 状态 + The `Command` class extends from `ChangeNotifier`, allowing Views to listen to its states. +`Command` 继承 `ChangeNotifier`,view 可监听其状态。 + In the `ListenableBuilder`, instead of passing the view model to `ListenableBuilder.listenable`, pass the command: +在 `ListenableBuilder` 中,将 command 传给 `listenable`,而非整个 view model: + ```dart @@ -376,6 +460,8 @@ ListenableBuilder( And listen to changes in the command state in order to run UI actions: +监听 command 状态变化以执行 UI 操作: + ```dart @override @@ -403,9 +489,13 @@ void _onViewModelChanged() { ### Combining command and ViewModel +### 组合 command 与 ViewModel + You can stack multiple `ListenableBuilder` widgets to listen to `running` and `error` states before showing the view model data. +可堆叠多个 `ListenableBuilder`,在展示 view model 数据前监听 `running` 与 `error` 状态。 + ```dart body: ListenableBuilder( @@ -436,6 +526,8 @@ You can define multiple commands classes in a single view model, simplifying its implementation and minimizing the amount of repeated code. +可在单个 view model 中定义多个 command 类,简化实现并减少重复代码。 + ```dart class HomeViewModel2 extends ChangeNotifier { @@ -462,9 +554,13 @@ class HomeViewModel2 extends ChangeNotifier { ### Extending the command pattern +### 扩展命令模式 + The command pattern can be extended in multiple ways. For example, to support a different number of arguments. +命令模式可通过多种方式扩展,例如支持不同数量的参数。 + ```dart class HomeViewModel extends ChangeNotifier { @@ -493,25 +589,36 @@ class HomeViewModel extends ChangeNotifier { ## Putting it all together +## 总结 + In this guide, you learned how to use the command design pattern to improve the implementation of view models when using the MVVM design pattern. +本指南介绍了在使用 MVVM 时如何用命令设计模式改进 view model 实现。 + Below, you can find the full `Command` class as implemented in the [Compass App example][] for the Flutter architecture guidelines. It also uses the [`Result` class][] to determine if the action completed successfully or with an error. +下文为 Flutter 架构指南 [Compass 应用示例][Compass App example] 中的完整 `Command` 类, +并使用 [`Result` 类][`Result` class] 判断操作成功或失败。 + This implementation also includes two types of commands, a `Command0`, for actions without parameters, and a `Command1`, for actions that take one parameter. +该实现包含 `Command0`(无参)与 `Command1`(单参)两种 command。 + :::note Check [pub.dev][] for other ready-to-use implementations of the command pattern, such as the [`command_it`][] package. + +可在 [pub.dev][] 查看现成的命令模式实现,例如 [`command_it`][] 包。 ::: diff --git a/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md b/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md index 14f9dda104..60b1dd8827 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md +++ b/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md @@ -9,6 +9,7 @@ contentTags: - dark mode iconPath: /assets/images/docs/app-architecture/design-patterns/kv-store-icon.svg order: 1 +ai-translated: true --- @@ -40,14 +41,14 @@ and in this recipe you’ll use it to save Dark Mode preferences. If you want to learn how to store complex data on a device, you’ll likely want to use SQL. In that case, take a look at the cookbook recipe -that follows this one called [Persistent storage architecture: SQL][]. +that follows this one called [Persistent storage architecture: SQL][Persistent Storage Architecture: SQL]. 键值对存储常用于存储简单的数据, 例如应用配置, 在本教程中,你将学习如何使用它来保存深色模式偏好设置。 如果你希望学习如何在设备上存储复杂的数据, 你可能需要使用 SQL。 -此时,请阅读本教程之后的 [持久化存储架构:SQL][Persistent storage architecture: SQL]。 +此时,请阅读本教程之后的 [持久化存储架构:SQL][Persistent Storage Architecture: SQL]。 ## Example application: App with theme selection @@ -346,7 +347,7 @@ class SharedPreferencesService { ## Putting it all together -## 整合业务 +## 总结 In this example, the `ThemeRepository` and `SharedPreferencesService` are created diff --git a/sites/docs/src/content/app-architecture/design-patterns/offline-first.md b/sites/docs/src/content/app-architecture/design-patterns/offline-first.md index 1cd16fec9c..34809d30f6 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/offline-first.md +++ b/sites/docs/src/content/app-architecture/design-patterns/offline-first.md @@ -1,22 +1,30 @@ --- -title: "Offline-first support" -description: Implement offline-first support for one feature in an application. +# title: "Offline-first support" +title: "离线优先支持" +# description: Implement offline-first support for one feature in an application. +description: 为应用中的某一功能实现离线优先支持。 contentTags: - data - user experience - repository pattern iconPath: /assets/images/docs/app-architecture/design-patterns/offline-first-icon.svg order: 3 +ai-translated: true --- An offline-first application is an app capable of offering most or all of its functionality while being disconnected from the internet. + +离线优先应用是指在断网时仍能提供大部分或全部功能的应用。 + Offline-first applications usually rely on stored data to offer users temporary access to data that would otherwise only be available online. +离线优先应用通常依赖已存储的数据,让用户临时访问原本仅在联网时才可用的数据。 + Some offline-first applications combine local and remote data seamlessly, while other applications inform the user when the application is using cached data. @@ -26,13 +34,22 @@ while others require the user to explicitly synchronize it. It all depends on the application requirements and the functionality it offers, and it’s up to the developer to decide which implementation fits their needs. +有些离线优先应用会无缝融合本地与远程数据, +另一些则会在使用缓存数据时告知用户。 +同样地,有些应用在后台同步数据,另一些则要求用户显式触发同步。 +这完全取决于应用的需求及其提供的功能,由开发者自行决定哪种实现方式最契合自身需要。 + In this guide, you will learn how to implement different approaches to offline-first applications in Flutter, following the [Flutter Architecture guidelines][]. +本指南将介绍如何在 Flutter 中按 [Flutter 架构指南][Flutter Architecture guidelines] 实现离线优先应用的不同方案。 + ## Offline-first architecture +## 离线优先架构 + As explained in the common architecture concepts guide, repositories act as the single source of truth. They are responsible for presenting local or remote data, @@ -42,17 +59,28 @@ repositories combine different local and remote data sources to present data in a single access point, independently of the connectivity state of the device. +正如通用架构概念指南所述,Repository 充当单一数据源。 +它们负责呈现本地或远程数据,且应是唯一能修改数据的地方。 +在离线优先应用中,Repository 会合并不同的本地与远程数据源, +在单一访问点呈现数据,与设备的联网状态无关。 + This example uses the `UserProfileRepository`, a repository that allows you to obtain and store `UserProfile` objects with offline-first support. +本示例使用 `UserProfileRepository`,它是一个支持以离线优先方式获取并存储 `UserProfile` 对象的 Repository。 + The `UserProfileRepository` uses two different data services: one works with remote data, and the other works with a local database. +`UserProfileRepository` 使用两个不同的数据 service:一个处理远程数据,另一个处理本地数据库。 + The API client,`ApiClientService`, connects to a remote service using HTTP REST calls. +API 客户端 `ApiClientService` 通过 HTTP REST 调用连接远程服务。 + ```dart class ApiClientService { @@ -71,6 +99,8 @@ class ApiClientService { The database service, `DatabaseService`, stores data using SQL, similar to the one found in the [Persistent Storage Architecture: SQL][] recipe. +数据库 service `DatabaseService` 使用 SQL 存储数据,与 [持久化存储架构:SQL][Persistent Storage Architecture: SQL] 教程中的类似。 + ```dart class DatabaseService { @@ -90,6 +120,8 @@ class DatabaseService { This example also uses the `UserProfile` data class that has been created using the [`freezed`][] package. +本示例还使用了通过 [`freezed`][] 包创建的 `UserProfile` 数据类。 + ```dart @freezed @@ -112,10 +144,18 @@ and then `UserProfile` for the UI data model class. The `UserProfileRepository` would take care of converting from one to the other when necessary. +在数据较复杂的应用中,例如远程数据包含的字段多于 UI 所需时, +你可能希望为 API 与数据库 service 准备一个数据类,再为 UI 准备另一个。 +例如,用 `UserProfileLocal` 表示数据库实体、用 `UserProfileRemote` 表示 API 响应对象, +再用 `UserProfile` 作为 UI 数据模型类。 +`UserProfileRepository` 会在必要时负责在它们之间相互转换。 + This example also includes the `UserProfileViewModel`, a view model that uses the `UserProfileRepository` to display the `UserProfile` on a widget. +本示例还包含 `UserProfileViewModel`,它是一个使用 `UserProfileRepository` 在 widget 上展示 `UserProfile` 的 view model。 + ```dart class UserProfileViewModel extends ChangeNotifier { @@ -139,31 +179,48 @@ class UserProfileViewModel extends ChangeNotifier { ## Reading data +## 读取数据 + Reading data is a fundamental part of any application that relies on remote API services. +对于任何依赖远程 API 服务的应用而言,读取数据都是基础环节。 + In offline-first applications, you want to ensure that the access to this data is as fast as possible, and that it doesn’t depend on the device being online to provide data to the user. This is similar to the [Optimistic State design pattern][]. +在离线优先应用中,你希望尽可能快地访问这些数据, +并且无需依赖设备联网即可向用户提供数据。 +这与 [乐观状态设计模式][Optimistic State design pattern] 类似。 + In this section, you will learn two different approaches, one that uses the database as a fallback, and one that combines local and remote data using a `Stream`. +本节将介绍两种不同方案:一种将数据库用作后备,另一种用 `Stream` 合并本地与远程数据。 + ### Using local data as a fallback +### 将本地数据用作后备 + As a first approach, you can implement offline support by having a fallback mechanism for when the user is offline or a network call fails. +第一种方案是通过后备机制实现离线支持,用于用户离线或网络调用失败的情形。 + In this case, the `UserProfileRepository` attempts to obtain the `UserProfile` from the remote API server using the `ApiClientService`. If this request fails, then returns the locally stored `UserProfile` from the `DatabaseService`. +此时,`UserProfileRepository` 先尝试用 `ApiClientService` 从远程 API 服务器获取 `UserProfile`; +若该请求失败,则返回 `DatabaseService` 中本地存储的 `UserProfile`。 + ```dart Future getUserProfile() async { @@ -193,19 +250,29 @@ Future getUserProfile() async { ### Using a Stream +### 使用 Stream + A better alternative presents the data using a `Stream`. In the best case scenario, the `Stream` emits two values, the locally stored data, and the data from the server. +一种更好的替代方案是用 `Stream` 呈现数据。 +在最理想的情况下,`Stream` 会发出两个值:本地存储的数据和来自服务器的数据。 + First, the stream emits the locally stored data using the `DatabaseService`. This call is generally faster and less error prone than a network call, and by doing it first the view model can already display data to the user. +首先,stream 通过 `DatabaseService` 发出本地存储的数据。 +该调用通常比网络调用更快、更不易出错,先执行它可以让 view model 尽早向用户展示数据。 + If the database does not contain any cached data, then the `Stream` relies completely on the network call, emitting only one value. +如果数据库中没有任何缓存数据,则 `Stream` 完全依赖网络调用,只发出一个值。 + Then, the method performs the network call using the `ApiClientService` to obtain up-to-date data. If the request was successful, @@ -213,6 +280,9 @@ it updates the database with the newly obtained data, and then yields the value to the view model, so it can be displayed to the user. +随后,该方法用 `ApiClientService` 发起网络调用以获取最新数据。 +若请求成功,则用新获取的数据更新数据库,再将该值 yield 给 view model,以便展示给用户。 + ```dart Stream getUserProfile() async* { @@ -240,10 +310,15 @@ The view model must subscribe to this `Stream` and wait until it has completed. For that, call `asFuture()` with the `Subscription` object and await the result. +view model 必须订阅该 `Stream` 并等待其完成。 +为此,对 `Subscription` 对象调用 `asFuture()` 并 await 其结果。 + For each obtained value, update the view model data and call `notifyListeners()` so the UI shows the latest data. +每获取到一个值,就更新 view model 的数据并调用 `notifyListeners()`,使 UI 展示最新数据。 + ```dart Future load() async { @@ -263,11 +338,15 @@ Future load() async { ``` ### Using only local data +### 仅使用本地数据 + Another possible approach uses locally stored data for read operations. This approach requires that the data has been preloaded at some point into the database, and requires a synchronization mechanism that can keep the data up to date. +另一种可行方案是读取操作仅使用本地存储的数据。 +这种方案要求数据在某个时刻已预加载到数据库中,并需要一套能保持数据最新的同步机制。 ```dart @@ -301,27 +380,45 @@ that don’t require data to be in sync with the server at all times. For example, a weather application where the weather data is only updated once a day. +这种方案适用于无需数据始终与服务器保持同步的应用。 +例如天气应用,其天气数据每天仅更新一次。 + Synchronization could be done manually by the user, for example, a pull-to-refresh action that then calls the `sync()` method, or done periodically by a `Timer` or a background process. You can learn how to implement a synchronization task in the section about synchronizing state. +同步既可由用户手动触发,例如下拉刷新动作随后调用 `sync()` 方法, +也可由 `Timer` 或后台进程定期执行。 +你可以在「同步状态」一节中了解如何实现同步任务。 + ## Writing data +## 写入数据 + Writing data in offline-first applications depends fundamentally on the application use case. +在离线优先应用中,写入数据的方式从根本上取决于应用的使用场景。 + Some applications might require the user input data to be immediately available on the server side, while other applications might be more flexible and allow data to be out-of-sync temporarily. +有些应用要求用户输入的数据立即在服务器端可用, +另一些则更灵活,允许数据临时处于不同步状态。 + This section explains two different approaches for implementing writing data in offline-first applications. +本节介绍在离线优先应用中实现数据写入的两种不同方案。 + ### Online-only writing +### 仅在线写入 + One approach for writing data in offline-first applications is to enforce being online to write data. While this might sound counterintuitive, @@ -329,10 +426,16 @@ this ensures that the data the user has modified is fully synchronized with the server, and the application doesn’t have a different state than the server. +在离线优先应用中写入数据的一种方案是强制要求联网才能写入。 +这听起来或许有违直觉,但能确保用户修改的数据与服务器完全同步, +使应用不会与服务器处于不同的状态。 + In this case, you first attempt to send the data to the API service, and if the request succeeds, then store the data in the database. +此时,你先尝试将数据发送给 API service,若请求成功,再将数据存入数据库。 + ```dart Future updateUserProfile(UserProfile userProfile) async { @@ -353,13 +456,20 @@ The disadvantage in this case is that the offline-first functionality is only available for read operations, but not for write operations, as those require the user being online. +这种方案的缺点是:离线优先功能仅对读取操作可用,对写入操作不可用,因为写入要求用户处于在线状态。 + ### Offline-first writing +### 离线优先写入 + The second approach works the other way around. Instead of performing the network call first, the application first stores the new data in the database, and then attempts to send it to the API service once it has been stored locally. +第二种方案恰好相反。应用不先发起网络调用, +而是先将新数据存入数据库,待本地存储完成后再尝试将其发送给 API service。 + ```dart Future updateUserProfile(UserProfile userProfile) async { @@ -383,8 +493,14 @@ In the next section, you will learn different approaches to handle synchronization between local and remote data. +这种方案允许用户即使在应用离线时也能将数据存储到本地, +但如果网络调用失败,本地数据库与 API service 便不再同步。 +在下一节中,你将学习处理本地与远程数据同步的不同方案。 + ## Synchronizing state +## 同步状态 + Keeping the local and remote data in sync is an important part of offline-first applications, as the changes that have been done locally @@ -392,16 +508,25 @@ need to be copied to the remote service. The app must also ensure that, when the user goes back to the application, the locally stored data is the same as in the remote service. +保持本地与远程数据同步是离线优先应用的重要部分, +因为本地所做的更改需要复制到远程服务。 +应用还必须确保用户重新回到应用时,本地存储的数据与远程服务中的数据一致。 ### Writing a synchronization task +### 编写同步任务 + There are different approaches for implementing synchronization in a background task. +在后台任务中实现同步有多种不同方案。 + A simple solution is to create a `Timer` in the `UserProfileRepository` that runs periodically, for example every five minutes. +一种简单方案是在 `UserProfileRepository` 中创建一个定期运行的 `Timer`,例如每五分钟一次。 + ```dart Timer.periodic(const Duration(minutes: 5), (timer) => sync()); @@ -410,6 +535,8 @@ Timer.periodic(const Duration(minutes: 5), (timer) => sync()); The `sync()` method then fetches the `UserProfile` from the database, and if it requires synchronization, it is then sent to the API service. +`sync()` 方法随后从数据库获取 `UserProfile`,若其需要同步,则将其发送给 API service。 + ```dart Future sync() async { @@ -440,12 +567,19 @@ like the [`workmanager`][] plugin. This allows your application to run the synchronization process in the background even when the application is not running. +更复杂的方案则使用 [`workmanager`][] 等插件提供的后台进程。 +这样即使应用未在运行,也能在后台执行同步过程。 + :::note Running background operations continuously can drain the device battery dramatically, and some devices limit the background processing capabilities, so this approach needs to be tuned to the application requirements and one solution might not fit all cases. + +持续运行后台操作会大幅消耗设备电量, +而且某些设备会限制后台处理能力, +因此这种方案需要根据应用需求进行调整,单一方案未必适用于所有情况。 ::: It’s also recommended to only perform the synchronization task @@ -455,19 +589,33 @@ to check if the device is connected to WiFi. You can also use [`battery_plus`][] to verify that the device is not running low on battery. +还建议仅在网络可用时才执行同步任务。 +例如,你可以用 [`connectivity_plus`][] 插件检查设备是否已连接 WiFi, +也可以用 [`battery_plus`][] 确认设备电量是否充足。 + In the previous example, the synchronization task runs every 5 minutes. In some cases, that might be excessive, while in others it might not be frequent enough. The actual synchronization period time for your application depends on your application needs and it’s something you will have to decide. +前述示例中,同步任务每 5 分钟运行一次。 +在某些场景下这可能过于频繁,而在另一些场景下又不够频繁。 +应用实际的同步周期取决于你的应用需求,需要由你自行决定。 + ### Storing a synchronization flag +### 存储同步标志 + To know if the data requires synchronization, add a flag to the data class indicating if the changes need to be synchronized. +为判断数据是否需要同步,可在数据类中添加一个标志,表示更改是否需要同步。 + For example, `bool synchronized`: +例如 `bool synchronized`: + ```dart @freezed @@ -485,43 +633,71 @@ to send it to the API service only when the `synchronized` flag is `false`. If the request is successful, then change it to `true`. +你的同步逻辑应仅在 `synchronized` 标志为 `false` 时才尝试将数据发送给 API service。 +若请求成功,再将其改为 `true`。 + ### Pushing data from server +### 从服务器推送数据 + A different approach for synchronization is to use a push service to provide up-to-date data to the application. In this case, the server notifies the application when data has changed, instead of being the application asking for updates. +另一种同步方案是使用推送服务向应用提供最新数据。 +此时由服务器在数据变更时通知应用,而非由应用主动请求更新。 + For example, you can use [Firebase messaging][], to push small payloads of data to the device, as well as trigger synchronization tasks remotely using background messages. +例如,你可以用 [Firebase messaging][] 向设备推送小型数据载荷,并通过后台消息远程触发同步任务。 + Instead of having a synchronization task running in the background, the server notifies the application when the stored data needs to be updated with a push notification. +服务器通过推送通知在存储数据需要更新时通知应用,从而无需在后台持续运行同步任务。 + You can combine both approaches together, having a background synchronization task and using background push messages, to keep the application database synchronized with the server. +你也可以将两种方案结合使用:既运行后台同步任务,又使用后台推送消息,从而保持应用数据库与服务器同步。 + ## Putting it all together +## 总结 + Writing an offline-first application requires making decisions regarding the way read, write and sync operations are implemented, which depend on the requirements from the application you are developing. +编写离线优先应用需要就读取、写入与同步操作的实现方式做出决策,而这些决策取决于你所开发应用的需求。 + The key takeaways are: +要点: + - When reading data, you can use a `Stream` to combine locally stored data with remote data. + + 读取数据时,可用 `Stream` 合并本地存储的数据与远程数据。 + - When writing data, decide if you need to be online or offline, and if you need synchronizing data later or not. + + 写入数据时,需决定是要求在线还是允许离线,以及是否需要稍后同步数据。 + - When implementing a background sync task, take into account the device status and your application needs, as different applications may have different requirements. + 实现后台同步任务时,需考虑设备状态与应用需求,因为不同应用的要求可能不同。 + [Flutter Architecture guidelines]:/app-architecture [Persistent Storage Architecture: SQL]:/app-architecture/design-patterns/sql [`freezed`]:{{site.pub}}/packages/freezed diff --git a/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md b/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md index f1204081b6..021fe29e63 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md +++ b/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md @@ -1,11 +1,14 @@ --- -title: Optimistic state -description: "Improve the perception of responsiveness of an application by implementing optimistic state." +# title: Optimistic state +title: 乐观状态 +# description: "Improve the perception of responsiveness of an application by implementing optimistic state." +description: "通过实现乐观状态提升应用响应速度的感知。" contentTags: - user experience - asynchronous dart iconPath: /assets/images/docs/app-architecture/design-patterns/optimistic-state-icon.svg order: 0 +ai-translated: true --- @@ -17,6 +20,10 @@ In general, users don’t like waiting for an action to finish to see the result and anything that takes more than a few milliseconds could be considered “slow” or “unresponsive” from the user’s perspective. +构建用户体验时,性能 **感知** 有时与代码实际性能同样重要。 +通常用户不愿等操作完成才看到结果, +超过几毫秒的操作在用户看来可能显得「慢」或「无响应」。 + Developers can help mitigate this negative perception by presenting a successful UI state before the background task is fully completed. @@ -24,17 +31,28 @@ An example of this would be tapping a “Subscribe” button, and seeing it change to “Subscribed” instantly, even if the background call to the subscription API is still running. +开发者可在后台任务尚未完成前展示成功的 UI 状态以缓解这种负面感知。 +例如点击「Subscribe」按钮后立即变为「Subscribed」, +即使订阅 API 的后台调用仍在进行。 + This technique is known as Optimistic State, Optimistic UI or Optimistic User Experience. In this recipe, you will implement an application feature using Optimistic State and following the [Flutter architecture guidelines][]. +该技术称为乐观状态 (Optimistic State)、乐观 UI 或乐观用户体验。 +在本食谱中,你将按 [Flutter 架构指南][Flutter architecture guidelines] 使用乐观状态实现一个应用功能。 + ## Example feature: a subscribe button +## 示例功能:订阅按钮 + This example implements a subscribe button similar to the one you could find in a video streaming application or a newsletter. +本示例实现类似视频流媒体应用或新闻通讯中的订阅按钮。 + Application with subscribe button @@ -46,32 +64,56 @@ For demo purposes, you will not implement the actual backend code, instead you will replace this call with a fake action that will simulate a network request. +点击按钮后应用调用外部 API 执行订阅操作, +例如在数据库中记录用户已加入订阅列表。 +为演示起见,不实现真实后端,而用模拟网络请求的假操作替代。 + In the case that the call is successful, the button text will change from “Subscribe” to “Subscribed”. The button background color will change as well. +调用成功时,按钮文字从 “Subscribe” 变为 “Subscribed”,背景色也会改变。 + On the contrary, if the call fails, the button text should revert back to “Subscribe”, and the UI should show an error message to the user, for example using a Snackbar. +若调用失败,按钮文字应恢复为 “Subscribe”, +并通过 Snackbar 等方式向用户显示错误信息。 + Following the Optimistic State idea, the button should instantly change to “Subscribed” once it is tapped, and only change back to “Subscribe” if the request failed. +按乐观状态思路,点击后按钮应立即变为 “Subscribed”, +仅当请求失败时才恢复为 “Subscribe”。 + Animation of application with subscribe button ## Feature architecture +## 功能架构 + Start by defining the feature architecture. Following the architecture guidelines, create these Dart classes in a Flutter project: +先定义功能架构。按架构指南,在 Flutter 项目中创建以下 Dart 类: + - A `StatefulWidget` named `SubscribeButton` + + 名为 `SubscribeButton` 的 `StatefulWidget` + - A class named `SubscribeButtonViewModel` extending `ChangeNotifier` + + 继承 `ChangeNotifier` 的 `SubscribeButtonViewModel` 类 + - A class named `SubscriptionRepository` + `SubscriptionRepository` 类 + ```dart class SubscribeButton extends StatefulWidget { @@ -102,13 +144,22 @@ The view model will contain the subscription state. When the button is tapped, the widget will call the view model to perform the action. +`SubscribeButton` widget 与 `SubscribeButtonViewModel` 代表本方案的展示层。 +Widget 显示按钮,根据订阅状态展示 “Subscribe” 或 “Subscribed”; +View model 持有订阅状态;点击时 widget 调用 view model 执行操作。 + The `SubscriptionRepository` will implement a subscribe method that will throw an exception when the action fails. The view model will call this method when performing the subscription action. +`SubscriptionRepository` 实现 subscribe 方法,失败时抛出异常; +view model 在执行订阅时调用该方法。 + Next, connect them together by adding the `SubscriptionRepository` to the `SubscribeButtonViewModel`: +接下来将 `SubscriptionRepository` 加入 `SubscribeButtonViewModel` 以连接各层: + ```dart class SubscribeButtonViewModel extends ChangeNotifier { @@ -120,6 +171,8 @@ class SubscribeButtonViewModel extends ChangeNotifier { And add the `SubscribeButtonViewModel` to the `SubscribeButton` widget: +并将 `SubscribeButtonViewModel` 传入 `SubscribeButton` widget: + ```dart class SubscribeButton extends StatefulWidget { @@ -136,6 +189,8 @@ class SubscribeButton extends StatefulWidget { Now that you have created the basic solution architecture, you can create the `SubscribeButton` widget the following way: +完成基础方案架构后,可按以下方式创建 `SubscribeButton` widget: + ```dart SubscribeButton( @@ -146,9 +201,13 @@ SubscribeButton( ``` ### Implement the `SubscriptionRepository` +### 实现 `SubscriptionRepository` + Add a new asynchronous method named `subscribe()` to the `SubscriptionRepository` with the following code: +在 `SubscriptionRepository` 中新增异步方法 `subscribe()`,代码如下: + ```dart class SubscriptionRepository { @@ -166,16 +225,27 @@ The call to `await Future.delayed()` with a duration of one second has been added to simulate a long running request. The method execution will pause for a second, and then it will continue running. +添加 `await Future.delayed()` 一秒用于模拟耗时请求; +方法会暂停一秒后继续执行。 + In order to simulate a request failing, the subscribe method throws an exception at the end. This will be used later on to show how to recover from a failed request when implementing Optimistic State. +为模拟请求失败,subscribe 方法末尾抛出异常, +后续实现乐观状态时将展示如何从失败请求恢复。 + ### Implement the `SubscribeButtonViewModel` +### 实现 `SubscribeButtonViewModel` + To represented the subscription state, as well a possible error state, add the following public members to the `SubscribeButtonViewModel`: +为表示订阅状态及可能的错误状态, +向 `SubscribeButtonViewModel` 添加以下公共成员: + ```dart // Whether the user is subscribed @@ -187,17 +257,27 @@ bool error = false; Both are set to `false` on start. +初始均为 `false`。 + Following the ideas of Optimistic State, the `subscribed` state will change to `true` as soon as the user taps the subscribe button. And will only change back to `false` if the action fails. +按乐观状态思路,用户点击订阅按钮后 `subscribed` 立即变为 `true`, +仅当操作失败时才恢复为 `false`。 + The `error` state will change to `true` when the action fails, indicating the `SubscribeButton` widget to show an error message to the user. The variable should go back to `false` once the error has been displayed. +失败时 `error` 变为 `true`,提示 `SubscribeButton` 向用户显示错误; +错误展示后应重置为 `false`。 + Next, implement an asynchronous `subscribe()` method: +接下来实现异步 `subscribe()` 方法: + ```dart // Subscription action @@ -232,6 +312,9 @@ and then calls to `notifyListeners()`. This forces the UI to update and the button changes its appearance, showing the text “Subscribed” to the user. +如前所述,方法先将 `subscribed` 设为 `true` 并调用 `notifyListeners()`, +迫使 UI 更新,按钮显示 “Subscribed”。 + Then the method performs the actual call to the repository. This call is wrapped by a `try-catch` in order to catch any exceptions it may throw. @@ -240,11 +323,19 @@ and the `error` state is set to `true`. A final call to `notifyListeners()` is done to change the UI back to ‘Subscribe’. +然后调用 repository;用 `try-catch` 捕获异常。 +捕获异常时将 `subscribed` 设回 `false`、`error` 设为 `true`, +最后再次 `notifyListeners()` 使 UI 回到 “Subscribe”。 + If there is no exception, the process is complete because the UI is already reflecting the success state. +无异常则流程结束,因 UI 已反映成功状态。 + The complete `SubscribeButtonViewModel` should look like this: +完整的 `SubscribeButtonViewModel` 如下: + ```dart /// Subscribe button View Model. @@ -291,12 +382,18 @@ class SubscribeButtonViewModel extends ChangeNotifier { ### Implement the `SubscribeButton` +### 实现 `SubscribeButton` + In this step, you will first implement the build method of the `SubscribeButton`, and then implement the feature’s error handling. +本步先实现 `SubscribeButton` 的 build 方法,再实现错误处理。 + Add the following code to the build method: +在 build 方法中添加以下代码: + ```dart @override @@ -327,10 +424,16 @@ The button style will also change depending on this state. As well, when the button is tapped, it runs the `subscribe()` method from the view model. +build 方法使用 `ListenableBuilder` 监听 view model 变化, +创建 `FilledButton`,按状态显示 "Subscribed" 或 "Subscribe",样式随之变化; +点击时调用 view model 的 `subscribe()`。 + The `SubscribeButtonStyle` can be found here. Add this class next to the `SubscribeButton`. Feel free to modify the `ButtonStyle`. +`SubscribeButtonStyle` 见下,放在 `SubscribeButton` 旁,可自行修改 `ButtonStyle`。 + ```dart class SubscribeButtonStyle { @@ -348,12 +451,20 @@ If you run the application now, you will see how the button changes when you press it, however it will change back to the original state without showing an error. +现在运行应用,按下按钮会看到变化, +但会恢复初始状态且不显示错误。 + ### Handling errors +### 处理错误 + To handle errors, add the `initState()` and `dispose()` methods to the `SubscribeButtonState`, and then add the `_onViewModelChange()` method. +要处理错误,在 `SubscribeButtonState` 中添加 `initState()` 与 `dispose()`, +再添加 `_onViewModelChange()` 方法。 + ```dart @override @@ -390,6 +501,9 @@ to be called when the view model notifies listeners. It’s important to call `removeListener()` when the widget is disposed of, in order to avoid errors. +`addListener()` 在 view model 通知时调用 `_onViewModelChange()`; +widget 销毁时须 `removeListener()` 以免出错。 + The `_onViewModelChange()` method checks the `error` state, and if it is `true`, displays a `Snackbar` to the user showing an error message. @@ -397,32 +511,50 @@ As well, the `error` state is set back to `false`, to avoid displaying the error message multiple times if `notifyListeners()` is called again in the view model. +`_onViewModelChange()` 检查 `error`,为 `true` 时用 `Snackbar` 显示错误, +并将 `error` 重置为 `false`,避免 view model 再次 `notifyListeners()` 时重复提示。 + ## Advanced Optimistic State +## 高级乐观状态 + In this tutorial, you’ve learned how to implement an Optimistic State with a single binary state, but you can use this technique to create a more advanced solution by incorporating a third temporal state that indicates that the action is still running. +本教程介绍了用单一二元状态实现乐观状态; +也可加入第三种临时状态表示操作仍在进行,构建更高级方案。 + For example, in a chat application when the user sends a new message, the application will display the new chat message in the chat window, but with an icon indicating that the message is still pending to be delivered. When the message is delivered, that icon would be removed. +例如聊天应用发送新消息时,窗口先显示消息并带「待送达」图标,送达后移除图标。 + In the subscribe button example, you could add another flag in the view model indicating that the `subscribe()` method is still running, or use the Command pattern running state, then modify the button style slightly to show that the operation is running. +订阅按钮示例中,可在 view model 增加标志表示 `subscribe()` 仍在运行, +或使用命令模式的 running 状态,并微调按钮样式表示操作进行中。 + ## Interactive example +## 交互示例 + This example shows the `SubscribeButton` widget together with the `SubscribeButtonViewModel` and `SubscriptionRepository`, which implement a subscribe tap action with Optimistic State. +本示例展示 `SubscribeButton`、`SubscribeButtonViewModel` 与 `SubscriptionRepository`, +用乐观状态实现订阅点击。 + When you tap the button, the button text changes from “Subscribe” to “Subscribed”. After a second, the repository throws an exception, @@ -430,6 +562,9 @@ which gets captured by the view model, and the button reverts back to showing “Subscribe”, while also displaying a Snackbar with an error message. +点击后文字立即变为 “Subscribed”;约一秒后 repository 抛出异常, +view model 捕获后按钮恢复 “Subscribe” 并显示 Snackbar 错误信息。 + ```dartpad title="Flutter Optimistic State example in DartPad" run="true" // ignore_for_file: avoid_print diff --git a/sites/docs/src/content/app-architecture/design-patterns/result.md b/sites/docs/src/content/app-architecture/design-patterns/result.md index 97e701f962..644ec07fdb 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/result.md +++ b/sites/docs/src/content/app-architecture/design-patterns/result.md @@ -1,11 +1,14 @@ --- -title: Error handling with Result objects -description: "Improve error handling across classes with Result objects." +# title: Error handling with Result objects +title: 使用 Result 对象进行错误处理 +# description: "Improve error handling across classes with Result objects." +description: "使用 Result 对象改善跨类的错误处理。" contentTags: - error handling - services iconPath: /assets/images/docs/app-architecture/design-patterns/result-icon.svg order: 5 +ai-translated: true --- @@ -13,30 +16,46 @@ order: 5 Dart provides a built-in error handling mechanism with the ability to throw and catch exceptions. +Dart 提供内置错误处理机制,支持抛出与捕获异常。 + As mentioned in the [Error handling documentation][], Dart's exceptions are unhandled exceptions. -This means that methods that throw exceptions don’t need to declare them, +This means that methods that throw exceptions don't need to declare them, and calling methods aren't required to catch them either. +如 [错误处理文档][Error handling documentation] 所述,Dart 的异常是未处理异常 (unhandled exceptions)。 +这意味着抛出异常的方法无需声明异常,调用方也不必捕获。 + This can lead to situations where exceptions are not handled properly. In large projects, developers might forget to catch exceptions, and the different application layers and components -could throw exceptions that aren’t documented. +could throw exceptions that aren't documented. This can lead to errors and crashes. +这可能导致异常未得到妥善处理。 +在大型项目中,开发者可能忘记捕获异常, +各层与组件可能抛出未文档化的异常,进而导致错误与崩溃。 + In this guide, you will learn about this limitation and how to mitigate it using the _result_ pattern. +本指南将介绍这一局限,以及如何用 **结果类型 (result)** 模式缓解。 + ## Error flow in Flutter applications +## Flutter 应用中的错误流 + Applications following the [Flutter architecture guidelines][] are usually composed of view models, repositories, and services, among other parts. When a function in one of these components fails, it should communicate the error to the calling component. +遵循 [Flutter 架构指南][Flutter architecture guidelines] 的应用通常由 view model、repository、service 等组成。 +当其中某组件的函数失败时,应将错误告知调用方。 + Typically, that's done with exceptions. For example, an API client service failing to communicate with the remote server @@ -46,23 +65,48 @@ for example a Repository, would have to either capture this exception or ignore it and let the calling view model handle it. +通常通过异常完成。例如,无法与远程服务器通信的 API 客户端 service 可能抛出 HTTP 错误异常; +调用方(如 Repository)须捕获该异常,或忽略并由 view model 处理。 + This can be observed in the following example. Consider these classes: +以下示例可见这一点。考虑这些类: + - A service, `ApiClientService`, performs API calls to a remote service. + + Service `ApiClientService` 向远程服务发起 API 调用。 + - A repository, `UserProfileRepository`, provides the `UserProfile` provided by the `ApiClientService`. + + Repository `UserProfileRepository` 提供由 `ApiClientService` 获取的 `UserProfile`。 + - A view model, `UserProfileViewModel`, uses the `UserProfileRepository`. + View model `UserProfileViewModel` 使用 `UserProfileRepository`。 + The `ApiClientService` contains a method, `getUserProfile`, that throws exceptions in certain situations: -- The method throws an `HttpException` if the response code isn’t 200. +`ApiClientService` 的 `getUserProfile` 在特定情况下会抛出异常: + +- The method throws an `HttpException` if the response code isn't 200. + + 响应码非 200 时抛出 `HttpException`。 + - The JSON parsing method throws an exception if the response isn't formatted correctly. + + 响应格式不正确时 JSON 解析抛出异常。 + - The HTTP client might throw an exception due to networking issues. + HTTP 客户端可能因网络问题抛出异常。 + The following code tests for a variety of possible exceptions: +以下代码处理多种可能的异常: + ```dart class ApiClientService { @@ -85,10 +129,13 @@ class ApiClientService { } ``` -The `UserProfileRepository` doesn’t need to handle +The `UserProfileRepository` doesn't need to handle the exceptions from the `ApiClientService`. In this example, it just returns the value from the API Client. +`UserProfileRepository` 无需处理 `ApiClientService` 的异常; +本例中它直接返回 API 客户端的值。 + ```dart class UserProfileRepository { @@ -103,9 +150,13 @@ class UserProfileRepository { Finally, the `UserProfileViewModel` should capture all exceptions and handle the errors. +最后,`UserProfileViewModel` 应捕获所有异常并处理错误。 + This can be done by wrapping the call to the `UserProfileRepository` with a try-catch: +可用 try-catch 包装对 `UserProfileRepository` 的调用: + ```dart class UserProfileViewModel extends ChangeNotifier { @@ -127,6 +178,9 @@ end up with the following code. It compiles and runs, but crashes if one of the exceptions mentioned previously occurs: +现实中开发者可能忘记正确捕获异常,写出如下代码。 +它能编译运行,但若前述任一异常发生则会崩溃: + ```dart class UserProfileViewModel extends ChangeNotifier { @@ -141,28 +195,42 @@ class UserProfileViewModel extends ChangeNotifier { You can attempt to solve this by documenting the `ApiClientService`, warning about the possible exceptions it might throw. -However, since the view model doesn’t use the service directly, +However, since the view model doesn't use the service directly, other developers working in the codebase might miss this information. +可尝试为 `ApiClientService` 文档化可能抛出的异常。 +但 view model 不直接使用 service,其他开发者可能忽略该信息。 + ## Using the result pattern +## 使用结果类型模式 + An alternative to throwing exceptions is to wrap the function output in a `Result` object. +抛异常的替代方案是将函数输出包装在 `Result` 对象中。 + When the function runs successfully, the `Result` contains the returned value. However, if the function does not complete successfully, the `Result` object contains the error. +成功时 `Result` 含返回值;失败时含错误。 + A `Result` is a [`sealed`][] class that can either subclass `Ok` or the `Error` class. Return the successful value with the subclass `Ok`, and the captured error with the subclass `Error`. +`Result` 是 [`sealed`][] 类,子类为 `Ok` 或 `Error`; +成功值用 `Ok` 返回,捕获的错误用 `Error` 返回。 + The following code shows a sample `Result` class that has been simplified for demo purposes. A full implementation is at the end of this page. +以下是为演示简化的 `Result` 示例,完整实现见文末。 + ```dart /// Utility class that simplifies handling errors. @@ -205,15 +273,23 @@ In this example, the `Result` class uses a generic type `T` to represent any return value, which can be a primitive Dart type like `String` or an `int` or a custom class like `UserProfile`. +本例中 `Result` 用泛型 `T` 表示任意返回值,可为 `String`、`int` 或 `UserProfile` 等。 + ### Creating a `Result` object +### 创建 `Result` 对象 + For functions using the `Result` class to return values, instead of a value, the function returns a `Result` object containing the value. +使用 `Result` 返回值的函数不再直接返回值,而是返回包含值的 `Result`。 + For example, in the `ApiClientService`, `getUserProfile` is changed to return a `Result`: +例如 `ApiClientService` 的 `getUserProfile` 改为返回 `Result`: + ```dart class ApiClientService { @@ -228,15 +304,22 @@ class ApiClientService { Instead of returning the `UserProfile` directly, it returns a `Result` object containing a `UserProfile`. +不再直接返回 `UserProfile`,而是返回包含 `UserProfile` 的 `Result`。 + To facilitate using the `Result` class, it contains two named constructors, `Result.ok` and `Result.error`. Use them to construct the `Result` depending on desired output. As well, capture any exceptions thrown by the code and wrap them into the `Result` object. +`Result` 提供 `Result.ok` 与 `Result.error` 命名构造函数,按输出构造 `Result`, +并捕获代码抛出的异常包装进 `Result`。 + For example, here the `getUserProfile()` method has been changed to use the `Result` class: +例如 `getUserProfile()` 已改为使用 `Result`: + ```dart class ApiClientService { @@ -270,10 +353,15 @@ As well, the method is wrapped with a `try-catch` block to capture any exceptions thrown by the Http client or the JSON parser into a `Result.error`. +原 return 改为 `Result.ok` 返回;`throw HttpException()` 改为 `Result.error(HttpException())`; +并用 try-catch 将 HTTP 客户端或 JSON 解析器抛出的异常捕获为 `Result.error`。 + The repository class also needs to be modified, and instead of returning a `UserProfile` directly, now it returns a `Result`. +Repository 类也需修改,直接返回 `UserProfile` 改为返回 `Result`。 + ```dart Future> getUserProfile() async { @@ -283,13 +371,19 @@ Future> getUserProfile() async { ### Unwrapping the Result object +### 解包 Result 对象 + Now the view model doesn't receive the `UserProfile` directly, but instead it receives a `Result` containing a `UserProfile`. +现在 view model 收到的是包含 `UserProfile` 的 `Result`,而非直接的 `UserProfile`。 + This forces the developer implementing the view model to unwrap the `Result` to obtain the `UserProfile`, and avoids having uncaught exceptions. +这迫使实现 view model 的开发者解包 `Result` 获取 `UserProfile`,避免未捕获异常。 + ```dart class UserProfileViewModel extends ChangeNotifier { @@ -317,19 +411,32 @@ meaning it can only be of type `Ok` or `Error`. This allows the code to evaluate the result with a [switch result or expression][]. +`Result` 用 `sealed` 实现,只能是 `Ok` 或 `Error`, +可用 [switch 结果或表达式][switch result or expression] 求值。 + In the `Ok` case, obtain the value using the `value` property. +`Ok` 时用 `value` 属性获取值。 + In the `Error` case, obtain the error object using the `error` property. +`Error` 时用 `error` 属性获取错误对象。 + ## Improving control flow +## 改善控制流 + Wrapping code in a `try-catch` block ensures that thrown exceptions are caught and not propagated to other parts of the code. +try-catch 确保抛出的异常被捕获而不传播到其他代码。 + Consider the following code. +考虑以下代码。 + ```dart class UserProfileRepository { @@ -354,12 +461,19 @@ attempts to obtain the `UserProfile` using the `ApiClientService`. If it fails, it tries to create a temporary user in a `DatabaseService`. +此方法中 `UserProfileRepository` 先通过 `ApiClientService` 获取 `UserProfile`, +失败则尝试在 `DatabaseService` 创建临时用户。 + Because either service method can fail, the code must catch the exceptions in both cases. +因两种 service 方法都可能失败,代码须在两种情况下捕获异常。 + This can be improved using the `Result` pattern: +可用 `Result` 模式改进: + ```dart Future> getUserProfile() async { @@ -381,27 +495,47 @@ In this code, if the `Result` object is an `Ok` instance, then the function returns that object; otherwise, it returns `Result.Error`. +若 `Result` 为 `Ok` 则返回该对象,否则返回 `Result.error`。 + ## Putting it all together +## 总结 + In this guide, you have learned how to use a `Result` class to return result values. +本指南介绍了如何使用 `Result` 类返回结果值。 + The key takeaways are: +要点: + - `Result` classes force the calling method to check for errors, reducing the amount of bugs caused by uncaught exceptions. + + `Result` 类迫使调用方检查错误,减少未捕获异常导致的 bug。 + - `Result` classes help improve control flow compared to try-catch blocks. + + 相比 try-catch,`Result` 类有助于改善控制流。 + - `Result` classes are `sealed` and can only return `Ok` or `Error` instances, allowing the code to unwrap them with a switch statement. + `Result` 类为 `sealed`,只能为 `Ok` 或 `Error`,可用 switch 解包。 + Below you can find the full `Result` class as implemented in the [Compass App example][] for the [Flutter architecture guidelines][]. +下文为 [Flutter 架构指南][Flutter architecture guidelines] 的 [Compass 应用示例][Compass App example] 中的完整 `Result` 类。 + :::note Check [pub.dev][] for different ready-to-use implementations of the `Result` class, such as the [`result_dart`][], [`result_type`][], and [`multiple_result`][] packages. + +可在 [pub.dev][] 查看现成的 `Result` 实现,如 [`result_dart`][]、[`result_type`][]、[`multiple_result`][] 等包。 ::: diff --git a/sites/docs/src/content/app-architecture/design-patterns/sql.md b/sites/docs/src/content/app-architecture/design-patterns/sql.md index 971c151141..5b10bca841 100644 --- a/sites/docs/src/content/app-architecture/design-patterns/sql.md +++ b/sites/docs/src/content/app-architecture/design-patterns/sql.md @@ -379,20 +379,19 @@ The `TodoRepository` acts as the source of truth for all the ToDo items. View models must use this repository to access to the ToDo list, and it should not expose any implementation details on how they are stored. -在内部,`TodoRepository` 使用 `DatabaseService`, -它通过 `sqflite` package 实现 SQL 数据库的访问。 -你还可以使用其他存储 package,如 `sqlite3`、`drift`,甚至云存储解决方案,如 `firebase_database`, -来实现相同的 `DatabaseService`。 +`TodoRepository` 是所有待办事项的单一数据源。 +视图模型必须通过该 repository 访问待办事项列表, +且不应暴露任何关于其存储方式的实现细节。 Internally, the `TodoRepository` uses the `DatabaseService`, which implements the access to the SQL database using the `sqflite` package. You can implement the same `DatabaseService` using other storage packages like `sqlite3`, `drift` or even cloud storage solutions like `firebase_database`. -在内部, `TodoRepository` 使用 `DatabaseService` , -它通过 `sqflite` 包实现 SQL 数据库的访问。 -你可以使用其他存储包,如 `sqlite3`、 `drift` ,甚至云存储解决方案,如 `firebase_database` , -来实现相同的 `DatabaseService` 。 +在内部,`TodoRepository` 使用 `DatabaseService`, +它通过 `sqflite` package 实现 SQL 数据库的访问。 +你可以使用其他存储 package,如 `sqlite3`、`drift`,甚至云存储解决方案,如 `firebase_database`, +来实现相同的 `DatabaseService`。 The `TodoRepository` checks if the database is open before every request and opens it if necessary. @@ -592,7 +591,7 @@ for recommendations. ## Putting it all together -## 整合业务 +## 总结 In the `main()` method of your application, first initialize the `DatabaseService`, diff --git a/sites/docs/src/content/app-architecture/guide.md b/sites/docs/src/content/app-architecture/guide.md index 18f7a4bfc6..35e868c0b1 100644 --- a/sites/docs/src/content/app-architecture/guide.md +++ b/sites/docs/src/content/app-architecture/guide.md @@ -1,14 +1,21 @@ --- -title: Guide to app architecture -shortTitle: Architecture guide +# title: Guide to app architecture +title: 应用架构指南 +# shortTitle: Architecture guide +shortTitle: 架构指南 +# description: >- +# The recommended way to architect a Flutter app. description: >- - The recommended way to architect a Flutter app. + 构建 Flutter 应用的推荐架构方式。 prev: - title: Common architecture concepts - path: /app-architecture/concepts + # title: Common architecture concepts + title: 常见架构设计理念 + path: /app-architecture/concepts next: - title: Architecture case study + # title: Architecture case study + title: 架构案例研究 path: /app-architecture/case-study +ai-translated: true --- The following pages demonstrate how to build an app using best practices. @@ -17,29 +24,55 @@ making them easier to scale, test, and maintain. However, they're guidelines, not steadfast rules, and you should adapt them to your unique requirements. +以下页面演示如何按最佳实践构建应用。 +本指南建议适用于大多数应用,使其更易扩展、测试与维护。 +但它们是指导原则而非铁律,你应根据自身需求调整。 + This section provides a high-level overview of how Flutter applications can be architected. It explains the layers of an application, along with the classes that exist within each layer. The section after this provides concrete code samples and walks through a Flutter application that's implemented these recommendations. +本节从宏观概述 Flutter 应用如何架构,说明应用各层及层内存在的类。 +其后一节提供具体代码示例,并逐步讲解一个落实这些建议的 Flutter 应用。 + ## Overview of project structure +## 项目结构概览 + [Separation-of-concerns][] is the most important principle to follow when designing your Flutter app. Your Flutter application should split into two broad layers, the UI layer and the Data layer. +设计 Flutter 应用时,[关注点分离][Separation-of-concerns] 是最重要的原则。 +Flutter 应用应划分为两大层:UI 层与数据层。 + Each layer is further split into different components, each of which has distinct responsibilities, a well-defined interface, boundaries and dependencies. This guide recommends you split your application into the following components: +每一层再拆分为不同组件,各自职责、接口、边界与依赖清晰。 +本指南建议将应用拆分为以下组件: + * Views + + View(视图) + * View models + + View model(视图模型) + * Repositories + + Repository(仓库) + * Services + Service(服务) + ### MVVM If you've encountered the [Model-View-ViewModel architectural pattern][] (MVVM), @@ -52,6 +85,12 @@ Repositories and services represent the data of an application, or the model layer of MVVM. Each of these components is defined in the next section. +若你见过 [Model-View-ViewModel 架构模式][Model-View-ViewModel architectural pattern](MVVM),会对此感到熟悉。 +MVVM 将应用的一个功能拆为三部分:`Model`、`ViewModel` 与 `View`。 +View 与 view model 构成应用的 UI 层; +Repository 与 Service 代表应用数据,即 MVVM 的 model 层。 +下一节将定义这些组件。 + MVVM architectural pattern Every feature in an application will contain one view to describe the UI and @@ -60,14 +99,23 @@ one or more repositories as the sources of truth for your application data, and zero or more services that interact with external APIs, like client servers and platform plugins. +应用中每个功能通常包含:一个描述 UI 的 view、一个处理逻辑的 view model、 +一个或多个作为应用数据单一数据源的 repository, +以及零个或多个与客户端服务器、平台插件等外部 API 交互的 service。 + A single feature of an application might require all of the following objects: +单个应用功能可能需要以下全部对象: + An example of the Dart objects that might exist in one feature using the architecture described on page. Each of these objects and the arrows that connect them will be explained thoroughly by the end of this page. Throughout this guide, the following simplified version of that diagram will be used as an anchor. +本页末尾将详细解释这些对象及其连接箭头。 +贯穿本指南,将以下述简化图作为锚点。 + A simplified diagram of the architecture described on this page. :::note @@ -75,25 +123,39 @@ Apps with complex logic might also have a logic layer that sits in between the UI layer and data layer. This logic layer is commonly called the *domain layer*. The domain layer contains additional components often called *interactors* or *use-cases*. The domain layer is covered later in this guide. + +逻辑复杂的应用还可在 UI 层与数据层之间增设逻辑层,常称为 **领域层**。 +领域层包含常称为 **interactor** 或 **use-case** 的额外组件。本指南后文将介绍领域层。 ::: [Model-View-ViewModel architectural pattern]: https://en.wikipedia.org/wiki/Model–view–viewmodel ## UI layer +## UI 层 + An application's UI layer is responsible for interacting with the user. It displays an application's data to the user and receives user input, such as tap events and form inputs. +应用的 UI 层负责与用户交互: +向用户展示应用数据,并接收点击事件、表单输入等用户输入。 + The UI reacts to data changes or user input. When the UI receives new data from a Repository, it should re-render to display that new data. When the user interacts with the UI, it should change to reflect that interaction. +UI 会对数据变化或用户输入做出反应。 +当 UI 从 Repository 收到新数据时,应重新渲染以展示新数据; +当用户与 UI 交互时,UI 应变化以反映该交互。 + The UI layer is made up of two architectural components, based on the MVVM design pattern: +UI 层基于 MVVM 设计模式,由两个架构组件构成: + * **Views** describe how to present application data to the user. Specifically, they refer to *compositions of widgets* that make a feature. For instance, a view is often (but not always) a screen that @@ -101,14 +163,25 @@ based on the MVVM design pattern: all of the widgets below it in the widget tree. Views are also responsible for passing events to the view model in response to user interactions. + + **View** 描述如何向用户呈现应用数据,具体指构成某一功能的 **widget 组合**。 + 例如,view 常常是(但不总是)包含 `Scaffold` 及 widget 树中其下所有 widget 的屏幕。 + View 还负责将用户交互产生的事件传递给 view model。 + * **View models** contain the logic that converts app data into *UI State*, because data from repositories is often formatted differently from the data that needs to be displayed. For example, you might need to combine data from multiple repositories, or you might want to filter a list of data records. + **View model** 包含将应用数据转换为 **UI State** 的逻辑, + 因为 Repository 数据格式常与展示所需格式不同。 + 例如,你可能需要合并多个 Repository 的数据,或过滤数据记录列表。 + Views and view models should have a one-to-one relationship. +View 与 view model 应为一一对应关系。 + A simplified diagram of the architecture described on this page with the view and view model objects highlighted. @@ -118,11 +191,19 @@ Using views and view models, your UI layer can maintain state during configuration changes (such as screen rotations), and you can test the logic of your UI independently of Flutter widgets. +简而言之,view model 管理 UI 状态,view 展示该状态。 +借助 view 与 view model,UI 层可在配置变更(如屏幕旋转)时保持状态, +且可将 UI 逻辑与 Flutter widget 分开测试。 + :::note 'View' is an abstract term, and one view doesn't equal one widget. Widgets are composable, and several can be combined to create one view. Therefore, view models don't have a one-to-one relationship with widgets, but rather a one-to-one relationship with a *collection* of widgets. + +「View」是抽象概念,一个 view 不等于一个 widget。 +Widget 可组合,多个 widget 可组成一个 view。 +因此 view model 与 widget 并非一一对应,而是与 **一组** widget 一一对应。 ::: A feature of an application is user centric, @@ -135,6 +216,11 @@ only purpose is to provide the user with a way to log in. In the application code, the login screen would be made up of a `LoginViewModel` class and a `LoginView` class. +应用功能以用户为中心,因此由 UI 层定义。 +每一对 **view** 与 **view model** 定义应用中的一个功能,通常是应用中的一个屏幕,但未必如此。 +例如登录与登出:登录通常在专用屏幕上完成,该屏幕唯一目的是提供登录方式; +在应用代码中,登录屏幕由 `LoginViewModel` 与 `LoginView` 类构成。 + On the other hand, logging out of an app is generally not done on a dedicated screen. The ability to log out is generally presented to the user as a button in @@ -143,78 +229,134 @@ It's often presented in multiple locations. In such scenarios, you might have a `LogoutViewModel` and a `LogoutView` which only contains a single button that can be dropped into other widgets. +另一方面,登出通常不在专用屏幕完成, +而是以菜单、账户屏幕等多处按钮呈现,且常出现在多个位置。 +此时可有仅含一个可嵌入其他 widget 的按钮的 `LogoutViewModel` 与 `LogoutView`。 + ### Views +### View(视图) + In Flutter, views are the widget classes of your application. Views are the primary method of rendering UI, and shouldn't contain any business logic. They should be passed all data they need to render from the view model. +在 Flutter 中,view 是应用的 widget 类,是渲染 UI 的主要方式,不应包含业务逻辑。 +渲染所需数据应全部由 view model 传入。 + A simplified diagram of the architecture described on this page with the view object highlighted. The only logic a view should contain is: +View 仅应包含以下逻辑: + * Simple if-statements to show and hide widgets based on a flag or nullable field in the view model + + 根据 view model 中的标志或可空字段,用简单 if 语句显示或隐藏 widget + * Animation logic + + 动画逻辑 + * Layout logic based on device information, like screen size or orientation. + + 基于屏幕尺寸、方向等设备信息的布局逻辑 + * Simple routing logic + 简单路由逻辑 + All logic related to data should be handled in the view model. +所有与数据相关的逻辑都应在 view model 中处理。 + ### View models +### View model(视图模型) + A view model exposes the application data necessary to render a view. In the architecture design described on this page, most of the logic in your Flutter application lives in view models. +View model 暴露渲染 view 所需的应用数据。 +在本页描述的架构中,Flutter 应用的大部分逻辑位于 view model。 + A simplified diagram of the architecture described on this page with the view model object highlighted. A view model's main responsibilities include: +View model 的主要职责包括: + * Retrieving application data from repositories and transforming it into a format suitable for presentation in the view. For example, it might filter, sort, or aggregate data. + + 从 Repository 获取应用数据并转换为适合在 view 中展示的格式,例如过滤、排序或聚合数据。 + * Maintaining the current state needed in the view, so that the view can rebuild without losing data. For example, it might contain boolean flags to conditionally render widgets in the view, or a field that tracks which section of a carousel is active on screen. + + 维护 view 所需的当前状态,使 view 重建时不丢失数据; + 例如包含用于条件渲染 widget 的布尔标志,或跟踪轮播当前区块的字段。 + * Exposes callbacks (called **commands**) to the view that can be attached to an event handler, like a button press or form submission. + 向 view 暴露可挂到事件处理器(如按钮点击、表单提交)的回调(称为 **command**)。 + Commands are named for the [command pattern][], and are Dart functions that allow views to execute complex logic without knowledge of its implementation. Commands are written as members of the view model class to be called by the gesture handlers in the view class. +Command 得名于 [命令模式][command pattern],是允许 view 在不了解实现细节的情况下执行复杂逻辑的 Dart 函数。 +Command 作为 view model 类的成员编写,由 view 类中的手势处理器调用。 + You can find examples of views, view models, and commands on the [UI layer][] portion of the [App architecture case study][]. +可在 [应用架构案例研究][App architecture case study] 的 [UI 层][UI layer] 部分查看 view、view model 与 command 的示例。 + For a gentle introduction to MVVM in Flutter, check out the [state management fundamentals][]. +若要温和入门 Flutter 中的 MVVM,请参阅 [状态管理基础][state management fundamentals]。 + [UI layer]: /app-architecture/case-study/ui-layer [App architecture case study]: /app-architecture/case-study [state management fundamentals]: /data-and-backend/state-mgmt/intro ## Data layer +## 数据层 + The data layer of an app handles your business data and logic. Two pieces of architecture make up the data layer: services and repositories. These pieces should have well-defined inputs and outputs to simplify their reusability and testability. +应用的数据层处理业务数据与逻辑。 +数据层由 service 与仓库两部分构成,应有清晰的输入输出以便复用与测试。 + A simplified diagram of the architecture described on this page with the Data layer highlighted. Using MVVM language, services and repositories make up your *model layer*. +用 MVVM 术语说,service 与 repository 构成 **model 层**。 + ### Repositories +### Repository(仓库) + [Repository][] classes are the source of truth for your model data. They're responsible for polling data from services, and transforming that raw data into **domain models**. @@ -223,15 +365,39 @@ formatted in a way that your view model classes can consume. There should be a repository class for each different type of data handled in your app. +[Repository(仓库)][Repository] 类是 model 数据的单一数据源, +负责从 service 轮询数据并将原始数据转换为 **领域模型 (domain model)**。 +领域模型表示应用所需数据,格式可供 view model 消费。 +应用中每种不同数据类型应有一个 Repository 类。 + Repositories handle the business logic associated with services, such as: +Repository 处理与 Service 相关的业务逻辑,例如: + * Caching + + 缓存 + * Error handling + + 错误处理 + * Retry logic + + 重试逻辑 + * Refreshing data + + 刷新数据 + * Polling services for new data + + 轮询 service 获取新数据 + * Refreshing data based on user actions + 根据用户操作刷新数据 + A simplified diagram of the architecture described on this page with the Repository object highlighted. @@ -240,23 +406,38 @@ For example, a social media app might have a `UserProfileRepository` class that exposes a `Stream`, which emits a new value whenever the user signs in or out. +Repository 以领域模型形式输出应用数据。 +例如,社交应用可有 `UserProfileRepository`,暴露 `Stream`, +在用户登录或登出时发出新值。 + The models output by repositories are consumed by view models. Repositories and view models have a many-to-many relationship. A view model can use many repositories to get the data it needs, and a repository can be used by many view models. +Repository 输出的模型由 view model 消费。Repository 与 view model 为多对多关系: +一个 view model 可使用多个 Repository 获取数据,一个 Repository 也可被多个 view model 使用。 + Repositories should never be aware of each other. If your application has business logic that needs data from two repositories, you should combine the data in the view model or in the domain layer, especially if your repository-to-view-model relationship is complex. +Repository 彼此不应感知。若业务逻辑需要两个 Repository 的数据, +应在 view model 或领域层合并数据,尤其在 Repository 与 view model 关系复杂时。 + #### Managing app-wide session state +#### 管理应用级会话状态 + Because repositories are the single source of truth for application data, they are also the ideal place to manage **app-wide lifecycle state**—state that needs to be shared across multiple view models but shouldn't persist beyond the current application session. +Repository 是应用数据的单一数据源,也是管理 **应用级生命周期状态** 的理想位置—— +需在多个 view model 间共享但不应超出当前应用会话持久化的状态。 + Examples of app-wide lifecycle state include an active user session, in-memory data caches, or transient application settings. Because view models and repositories have a many-to-many relationship, @@ -266,8 +447,16 @@ This allows distinct features to reactively observe and modify the same shared state through streams and methods exposed by the repository, without violating the clean one-to-one boundary between a view and its view model. +应用级生命周期状态的例子包括活跃用户会话、内存数据缓存或临时应用设置。 +由于 view model 与 Repository 为多对多关系,多个 view model 可依赖同一 Repository 实例 +(通常通过 service locator 或依赖注入容器管理), +使不同功能通过 Repository 暴露的 stream 与方法响应式观察并修改同一共享状态, +同时不破坏 view 与其 view model 之间清晰的一对一边界。 + ### Services +### Service(服务) + Services are in the lowest layer of your application. They wrap API endpoints and expose asynchronous response objects, such as `Future` and `Stream` objects. @@ -275,57 +464,97 @@ They're only used to isolate data-loading, and they hold no state. Your app should have one service class per data source. Examples of endpoints that services might wrap include: +Service 位于应用最底层,封装 API 端点并暴露 `Future`、`Stream` 等异步响应对象; +仅用于隔离数据加载,不持有状态。每个数据源应有一个 service 类。 +Service 可能封装的端点示例包括: + * The underlying platform, like iOS and Android APIs + + 底层平台,如 iOS 与 Android API + * REST endpoints + + REST 端点 + * Local files + 本地文件 + As a rule of thumb, services are most helpful when the necessary data lives outside of your application's Dart code - which is true of each of the preceding examples. +经验法则:当所需数据位于应用 Dart 代码之外时,service 最有用——上述示例皆属此类。 + Services and repositories have a many-to-many relationship. A single Repository can use several services, and a service can be used by multiple repositories. +Service 与 Repository 为多对多关系:单个 repository 可使用多个 service,一个 service 也可被多个 repository 使用。 + A simplified diagram of the architecture described on this page with the Service object highlighted. ## Optional: Domain layer +## 可选:领域层 + As your app grows and adds features, you might need to abstract away logic that adds too much complexity to your view models. These classes are often called interactors or **use-cases**. +随应用增长与功能增加,你可能需要将过多复杂逻辑从 view model 中抽象出来, +这类类常称为 interactor 或 **用例 (use-case)**。 + Use-cases are responsible for making interactions between the UI and Data layers simpler and more reusable. They take data from repositories and make it suitable for the UI layer. +用例负责简化并复用 UI 层与数据层之间的交互,从 Repository 取数并整理为 UI 层可用形式。 + MVVM design pattern with an added domain layer object Use-cases are primarily used to encapsulate business logic that would otherwise live in the view model and meets one or more of the following conditions: +用例主要用于封装否则会放在 view model 中、且满足以下一项或多项条件的业务逻辑: + 1. Requires merging data from multiple repositories + + 需要合并多个 Repository 的数据 + 2. Is exceedingly complex + + 极其复杂 + 3. The logic will be reused by different view models + 逻辑将被不同 view model 复用 + This layer is optional because not all applications or features within an application have these requirements. If you suspect your application would benefit from this additional layer, consider the pros and cons: +该层是可选的,因为并非所有应用或应用内功能都有这些需求。 +若你认为应用会受益于这一额外层,请权衡利弊: -| Pros | Cons | +| Pros优点 | Cons缺点 | |--------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | ✅ Avoid code duplication in view models | ❌ Increases complexity of your architecture, adding more classes and higher cognitive load | +| ✅ 避免 view model 中的代码重复 | ❌ 增加架构复杂度,类更多、认知负担更高 | | ✅ Improve testability by separating complex business logic from UI logic | ❌ Testing requires additional mocks | +| ✅ 将复杂业务逻辑与 UI 逻辑分离,提升可测试性 | ❌ 测试需要额外 mock | | ✅ Improve code readability in view models | ❌ Adds additional boilerplate to your code | +| ✅ 提升 view model 代码可读性 | ❌ 增加样板代码 | {:.table .table-striped} ### Data access with use-cases +### 通过用例访问数据 + Another consideration when adding a Domain layer is whether view models will continue to have access to repository data directly, or if you'll enforce view models to go through use-cases to get their data. Put another way, @@ -334,11 +563,18 @@ Perhaps when you notice repeated logic in your view models? Or, will you create a use-case each time a view model needs data, even if the logic in the use-case is simple? +增设领域层时还需考虑:view model 是否仍可直接访问 repository 数据, +还是强制通过用例获取数据。 +换言之,是按需添加用例(例如在 view model 中发现重复逻辑时), +还是每次 view model 需要数据都创建用例,即使用例逻辑很简单? + If you choose to do the latter, it intensifies the earlier outlined pros and cons. Your application code will be extremely modular and testable, but it also adds a significant amount of unnecessary overhead. +若选择后者,会放大前述利弊:代码将极度模块化且可测试,但也会带来大量不必要开销。 + A good approach is to add use-cases only when needed. If you find that your view models are accessing data through use-cases most of the time, @@ -347,15 +583,30 @@ The example app used later in this guide has use-cases for some features, but also has view models that interact with repositories directly. A complex feature might ultimately end up looking like this: +较好做法是仅在需要时添加用例。 +若发现 view model 大多通过用例访问数据,可随时重构为完全通过用例。 +本指南后续示例应用部分功能有用例,也有 view model 直接与 repository 交互。 +复杂功能最终可能如下: + A simplified diagram of the architecture described on this page with a use case object. This method of adding use-cases is defined by the following rules: +这种添加用例的方式遵循以下规则: + * Use-cases depend on repositories + + 用例依赖 Repository + * Use-cases and repositories have a many-to-many relationship + + 用例与 Repository 为多对多关系 + * View models depend on one or more use-cases *and* one or more repositories + View model 依赖一个或多个用例 **以及** 一个或多个 Repository + This method of using use-cases ends up looking less like a layered lasagna, and more like a plated dinner with two mains (UI and data layers) and a side (domain layer). @@ -363,13 +614,22 @@ Use-cases are just utility classes that have well-defined inputs and outputs. This approach is flexible and extendable, but it requires greater diligence to maintain order. +这种用例用法看起来不像分层千层面,而像一盘有两道主菜(UI 层与数据层)和配菜(领域层)的晚餐。 +用例只是输入输出清晰的工具类。该方式灵活可扩展,但需更用心维护秩序。 + [Separation-of-concerns]: https://en.wikipedia.org/wiki/Separation_of_concerns [command pattern]: https://en.wikipedia.org/wiki/Command_pattern +[命令模式]: https://en.wikipedia.org/wiki/Command_pattern [Repository]: https://martinfowler.com/eaaCatalog/repository.html ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="guide" diff --git a/sites/docs/src/content/app-architecture/index.md b/sites/docs/src/content/app-architecture/index.md index 8a6d829a8f..ffe2f3dd5c 100644 --- a/sites/docs/src/content/app-architecture/index.md +++ b/sites/docs/src/content/app-architecture/index.md @@ -1,12 +1,18 @@ --- -title: Architecting Flutter apps -shortTitle: Architecture +# title: Architecting Flutter apps +title: 构建 Flutter 应用架构 +# shortTitle: Architecture +shortTitle: 架构 +# description: > +# Learn how to structure Flutter apps. description: > - Learn how to structure Flutter apps. + 学习如何组织 Flutter 应用结构。 showToc: false next: - title: Architecture concepts + # title: Architecture concepts + title: 架构设计理念 path: /app-architecture/concepts +ai-translated: true ---
@@ -17,12 +23,20 @@ maintainable, resilient, and scalable Flutter app. In this guide, you'll learn app architecture principles and best practices for building Flutter apps. +架构是构建可维护、有韧性且可扩展的 Flutter 应用的重要一环。 +在本指南中,你将学习应用架构原则以及构建 Flutter 应用的最佳实践。 + 'Architecture' is a word that's hard to define. It's a broad term and can refer to any number of topics depending on the context. In this guide, 'architecture' refers to how to structure, organize, and design your Flutter app in order to scale as your project requirements and team grow. +「架构」这个词很难精确定义。 +它是一个宽泛术语,在不同语境下可以指许多不同主题。 +在本指南中,「架构」指如何组织、划分与设计 Flutter 应用结构, +以便随项目需求与团队规模增长而扩展。 +
Hero image @@ -32,42 +46,86 @@ your Flutter app in order to scale as your project requirements and team grow. ## What you'll learn +## 你将学到什么 + * Benefits of intentional architecture + + 有意为之的架构带来的好处 + * Common architectural principles + + 常见架构原则 + * The Flutter team's recommended app architecture + + Flutter 团队推荐的应用架构 + * MVVM and state management + + MVVM 与状态管理 + * Dependency injection + + 依赖注入 + * Common design patterns for writing robust Flutter applications + 编写健壮 Flutter 应用的常见设计模式 + {% comment %} TODO @ewindmill complete this list as pages land, add links. {% endcomment %} ## Benefits of intentional architecture +## 有意为之的架构的好处 + Good app architecture provides a number of benefits to engineering teams and their end users. +良好的应用架构为工程团队与最终用户带来诸多好处。 + * Maintainability - App architecture makes it easier to modify, update, and fix issues over time. + + 可维护性 — 应用架构使长期修改、更新与修复问题更容易。 + * Scalability - A well thought out application allows more people to contribute to the same codebase concurrently, with minimal code conflicts. + + 可扩展性 — 经过深思熟虑的应用设计能让更多人并行贡献同一代码库,并尽量减少代码冲突。 + * Testability - Applications with intentional architecture generally have simpler classes with well-defined inputs and outputs, which makes them easier to mock and test. + + 可测试性 — 有意为之的架构通常带来输入输出边界清晰的更简单类,更易于 mock 与测试。 + * Lower cognitive load - Developers who are new to the project will be more productive in a shorter amount of time, and code reviews are generally less time-consuming when code is easier to understand. + + 更低认知负担 — 新加入项目的开发者能更快上手,代码更易理解时,代码评审通常也更省时。 + * A better user experience - Features can ship faster and with fewer bugs. + 更好的用户体验 — 功能可以更快上线,且 bug 更少。 + ## How to use this guide +## 如何使用本指南 + This is a guide for building scalable Flutter applications and was written for teams that have multiple developers contributing to the same code base, who're building a feature-rich application. If you're writing a Flutter app that has a *growing team and codebase*, this guidance is for you. +本指南面向构建可扩展 Flutter 应用的团队而写, +适用于多名开发者共同维护同一代码库、并构建功能丰富应用的情形。 +若你正在开发的 Flutter 应用拥有 **不断壮大的团队与代码库**, +这些指导正适合你。 + Along with general architectural advice, this guide gives concrete examples of best practices and includes specific recommendations. Some libraries can be swapped out, and very large teams with unique complexity @@ -75,6 +133,11 @@ might find that some parts don't apply. In either case, the ideas remain sound. This is the recommended way to build a Flutter app. +除一般架构建议外,本指南还提供最佳实践的具体示例与明确推荐。 +部分库可以替换;规模极大且复杂度特殊的团队可能发现某些部分并不适用。 +无论哪种情况,核心理念仍然成立。 +这是构建 Flutter 应用的推荐方式。 + In the first part of this guide, you'll learn about common architectural principles from a high level. In the second part, the guide walks through specific and @@ -82,6 +145,10 @@ concrete recommendations of architecting Flutter apps. Finally, at the end of the guide, you'll find a list of design patterns and sample code that shows the recommendations in action. +指南第一部分从宏观层面介绍常见架构原则。 +第二部分逐步讲解构建 Flutter 应用的具体、可落地建议。 +最后在指南末尾,你会看到设计模式列表与示例代码,展示这些建议如何落地。 + [Common architectural principles]: /app-architecture/concepts [recommended app architecture]: /app-architecture/guide [MVVM]: /app-architecture/guide#mvvm @@ -89,7 +156,12 @@ sample code that shows the recommendations in action. ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="index" diff --git a/sites/docs/src/content/app-architecture/recommendations.md b/sites/docs/src/content/app-architecture/recommendations.md index 3f1d8def75..8064fd6ac3 100644 --- a/sites/docs/src/content/app-architecture/recommendations.md +++ b/sites/docs/src/content/app-architecture/recommendations.md @@ -1,14 +1,21 @@ --- -title: Architecture recommendations and resources -shortTitle: Architecture recommendations +# title: Architecture recommendations and resources +title: 架构建议与资源 +# shortTitle: Architecture recommendations +shortTitle: 架构建议 +# description: > +# Recommendations for building scalable Flutter applications. description: > - Recommendations for building scalable Flutter applications. + 构建可扩展 Flutter 应用的建议。 prev: - title: Architecture case study + # Architecture case study + title: 架构案例研究 path: /app-architecture/case-study next: - title: Design patterns + # title: Design patterns + title: 设计模式 path: /app-architecture/design-patterns +ai-translated: true --- This page presents architecture best practices, why they matter, and @@ -17,68 +24,136 @@ You should treat these recommendations as recommendations, and not steadfast rules, and you should adapt them to your app's unique requirements. +本页介绍架构最佳实践、其重要性, +以及是否建议你在 Flutter 应用中采用。 +请将这些内容视为建议而非铁律, +并根据应用的独特需求加以调整。 + The best practices on this page have a priority, which reflects how strongly the Flutter team recommends it. +本页最佳实践带有优先级,反映 Flutter 团队的推荐力度。 + * **Strongly recommend:** You should always implement this recommendation if you're starting to build a new application. You should strongly consider refactoring an existing app to implement this practice unless doing so would fundamentally clash with your current approach. + + **强烈推荐:** 若你正在新建应用,应始终落实该建议。 + 除非与现有做法根本冲突,否则应认真考虑在既有应用中重构以落实该实践。 + * **Recommend**: This practice will likely improve your app. + + **推荐:** 该实践很可能改善你的应用。 + * **Conditional**: This practice can improve your app in certain circumstances. + **视情况而定:** 该实践在特定情形下可能改善你的应用。 + ## Separation of concerns +## 关注点分离 + You should separate your app into a UI layer and a data layer. Within those layers, you should further separate logic into classes by responsibility. +你应将应用划分为 UI 层与数据层。 +在各层内部,还应按职责将逻辑进一步拆分到不同类中。 + ## Handling data +## 数据处理 + Handling data with care makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created. +谨慎处理数据能让代码更易理解、更少出错, +并避免产生畸形或意外的数据。 + ## App structure +## 应用结构 + Well organized code benefits both the health of the app itself, and the team working on the code. +组织良好的代码既有利于应用本身的健康,也有利于维护代码的团队。 + ## Testing +## 测试 + Good testing practices makes your app flexible. It also makes it straightforward and low risk to add new logic and new UI. +良好的测试实践使应用更灵活, +也让新增逻辑与 UI 变得直接且低风险。 + ## Recommended resources {:#resources} +## 推荐资源 {:#resources} + * Code and templates + + 代码与模板 + * [Compass app source code][] - Source code of a full-featured, robust Flutter application that implements many of these recommendations. + + [Compass 应用源代码][Compass app source code] — + 功能完整、健壮的 Flutter 应用源代码,落实了本指南中的许多建议。 + * [very_good_cli][] - A Flutter application template made by the Flutter experts Very Good Ventures. This template generates a similar app structure. + + [very_good_cli][] — + 由 Flutter 专家 Very Good Ventures 制作的 Flutter 应用模板, + 可生成类似的应用结构。 + * Documentation + + 文档 + * [Very Good Engineering architecture documentation][] - Very Good Engineering is a documentation site by VGV that has technical articles, demos, and open-sourced projects. It includes documentation on architecting Flutter applications. + + [Very Good Engineering 架构文档][Very Good Engineering architecture documentation] — + Very Good Engineering 是 VGV 的文档站点,包含技术文章、演示与开源项目, + 其中包括 Flutter 应用架构相关文档。 + * Tooling + + 工具 + * [Flutter developer tools][] - DevTools is a suite of performance and debugging tools for Dart and Flutter. + + [Flutter 开发者工具][Flutter developer tools] — + DevTools 是一套面向 Dart 与 Flutter 的性能与调试工具。 + * [flutter_lints][] - A package that contains the lints for Flutter apps recommended by the Flutter team. Use this package to encourage good coding practices across a team. + [flutter_lints][] — + 包含 Flutter 团队为 Flutter 应用推荐的 lint 规则的软件包, + 可用于在团队中推广良好编码实践。 + [Compass app source code]: https://github.com/flutter/samples/tree/main/compass_app [very_good_cli]: https://cli.vgv.dev/ @@ -88,7 +163,12 @@ It also makes it straightforward and low risk to add new logic and new UI. ## Feedback +## 反馈 + As this section of the website is evolving, we [welcome your feedback][]! +网站本节内容仍在完善中, +[欢迎提供反馈][welcome your feedback]! + [welcome your feedback]: https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page="recommendations" diff --git a/sites/docs/src/content/contribute/docs/cli.md b/sites/docs/src/content/contribute/docs/cli.md index 0767753dfe..77890fe4bc 100644 --- a/sites/docs/src/content/contribute/docs/cli.md +++ b/sites/docs/src/content/contribute/docs/cli.md @@ -1,23 +1,34 @@ --- -title: Docs command-line tool -shortTitle: Tool +# title: Docs command-line tool +title: 文档命令行工具 +# shortTitle: Tool +shortTitle: 工具 +# description: >- +# Learn about the dash_site CLI tool that is used to +# develop, test, and serve the Dart and Flutter documentation sites. description: >- - Learn about the dash_site CLI tool that is used to - develop, test, and serve the Dart and Flutter documentation sites. + 了解用于开发、测试与提供 Dart 和 Flutter 文档站点的 dash_site CLI 工具。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: The site's CLI tool can be accessed by running `dart run dash_site` in the repository's root directory. +在仓库根目录运行 `dart run dash_site` 即可访问站点 CLI 工具。 + ## Commands +## 命令 + ### `analyze-dart` ### `build` diff --git a/sites/docs/src/content/contribute/docs/code-blocks.md b/sites/docs/src/content/contribute/docs/code-blocks.md index 834f2e5356..827307e338 100644 --- a/sites/docs/src/content/contribute/docs/code-blocks.md +++ b/sites/docs/src/content/contribute/docs/code-blocks.md @@ -1,27 +1,45 @@ --- -title: Code blocks +# title: Code blocks +title: 代码块 +# description: >- +# Learn about Markdown code blocks on the Dart and Flutter documentation sites +# and custom functionality and configurability that they support. description: >- - Learn about Markdown code blocks on the Dart and Flutter documentation sites - and custom functionality and configurability that they support. + 了解 Dart 与 Flutter 文档站点上的 Markdown 代码块及其支持的自定义功能与可配置项。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: ## Title +## 标题 + ## Tags +## 标签 + ## Line numbers +## 行号 + ## Highlight lines +## 高亮行 + ## Highlight spans +## 高亮区间 + ## Diffing +## 差异对比 + ## DartPad diff --git a/sites/docs/src/content/contribute/docs/components.md b/sites/docs/src/content/contribute/docs/components.md index 0c1a201fed..1257ea8af4 100644 --- a/sites/docs/src/content/contribute/docs/components.md +++ b/sites/docs/src/content/contribute/docs/components.md @@ -1,17 +1,27 @@ --- -title: Custom components +# title: Custom components +title: 自定义组件 +# description: >- +# Learn about custom components that the +# Dart and Flutter documentation sites support for displaying content. description: >- - Learn about custom components that the - Dart and Flutter documentation sites support for displaying content. + 了解 Dart 与 Flutter 文档站点为展示内容而支持的自定义组件。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: ## Tabs +## 标签页 + ## YouTube embed + +## YouTube 嵌入 diff --git a/sites/docs/src/content/contribute/docs/excerpts.md b/sites/docs/src/content/contribute/docs/excerpts.md index eff2df3c1d..29037818e8 100644 --- a/sites/docs/src/content/contribute/docs/excerpts.md +++ b/sites/docs/src/content/contribute/docs/excerpts.md @@ -1,20 +1,30 @@ --- -title: Code excerpts +# title: Code excerpts +title: 代码摘录 +# description: >- +# Learn about adding and using code excerpts +# in the Dart and Flutter documentation sites. description: >- - Learn about adding and using code excerpts - in the Dart and Flutter documentation sites. + 了解如何在 Dart 与 Flutter 文档站点中添加与使用代码摘录。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: The source of code excerpts is in the root `/examples` directory. +代码摘录的源文件位于根目录 `/examples`。 + To learn how to use code excerpts, check out the [excerpter tool README][]. +要了解如何使用代码摘录,请参阅 [excerpter 工具 README][excerpter tool README]。 + [excerpter tool README]: https://github.com/flutter/website/blob/main/packages/excerpter/README.md diff --git a/sites/docs/src/content/contribute/docs/frontmatter.md b/sites/docs/src/content/contribute/docs/frontmatter.md index 1cc96f4914..c535ad7e31 100644 --- a/sites/docs/src/content/contribute/docs/frontmatter.md +++ b/sites/docs/src/content/contribute/docs/frontmatter.md @@ -1,23 +1,33 @@ --- +# title: Frontmatter title: Frontmatter +# description: >- +# Learn about the YAML frontmatter each document on +# the Dart and Flutter documentation sites starts with. description: >- - Learn about the YAML frontmatter each document on - the Dart and Flutter documentation sites starts with. + 了解 Dart 与 Flutter 文档站点上每篇文档开头的 YAML frontmatter。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: Each Markdown document on the site starts with [YAML][] frontmatter. You can edit the frontmatter to customize the generated page and its metadata. +站点上每篇 Markdown 文档都以 [YAML][] frontmatter 开头。你可以编辑 frontmatter 以自定义生成的页面及其元数据。 + As a minimum, a `title` and `description` are required for each page. +每页至少需要 `title` 与 `description`。 + ```yaml --- title: Build a Flutter app @@ -30,11 +40,17 @@ description: >- ## Access frontmatter data in templates +## 在模板中访问 frontmatter 数据 + Layouts, templates, and source files can access values from the frontmatter as top-level data with templating. +布局、模板与源文件可通过模板将 frontmatter 中的值作为顶层数据访问。 + For example, the following frontmatter sets a `showData` variable to `value`: +例如,以下 frontmatter 将 `showData` 变量设为 `value`: + ```yaml --- # ... @@ -44,6 +60,8 @@ showDate: false The configured value of `showDate` can be accessed in templates: +`showDate` 的配置值可在模板中访问: + ```md Should show date: {{showDate}} {% if showDate %} @@ -54,12 +72,18 @@ Should show date: {{showDate}} If you add a new value to the frontmatter, prefer using `lowerCamelCase` for the name. +若向 frontmatter 添加新值,名称优先使用 `lowerCamelCase`。 + ## Frontmatter fields +## frontmatter 字段 + Besides `title` and `description`, the sites support a variety of other optional fields to customize page generation. +除 `title` 与 `description` 外,站点还支持多种可选字段以自定义页面生成。 + ### `title` ### `description` diff --git a/sites/docs/src/content/contribute/docs/index.md b/sites/docs/src/content/contribute/docs/index.md index 0a3a41ca46..30380c08fe 100644 --- a/sites/docs/src/content/contribute/docs/index.md +++ b/sites/docs/src/content/contribute/docs/index.md @@ -1,94 +1,180 @@ --- -title: Contribute to the docs -shortTitle: Docs +# title: Contribute to the docs +title: 为文档贡献 +# shortTitle: Docs +shortTitle: 文档 +# description: >- +# Learn about contributing to the Dart and Flutter documentation sites. description: >- - Learn about contributing to the Dart and Flutter documentation sites. + 了解如何为 Dart 与 Flutter 文档站点贡献。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: ## Contribution guides +## 贡献指南 + - [Writing](/contribute/docs/writing) + + [写作](/contribute/docs/writing) + - [Markdown](/contribute/docs/markdown) + - [Frontmatter](/contribute/docs/frontmatter) + - [Code blocks](/contribute/docs/code-blocks) + + [代码块](/contribute/docs/code-blocks) + - [Code excerpts](/contribute/docs/excerpts) + + [代码摘录](/contribute/docs/excerpts) + - [Components](/contribute/docs/components) + + [组件](/contribute/docs/components) + - [Sidenav](/contribute/docs/sidenav) + + [侧边栏导航](/contribute/docs/sidenav) + - [Releases](/contribute/docs/releases) + + [发布](/contribute/docs/releases) + - [Command-line tool](/contribute/docs/cli) + [命令行工具](/contribute/docs/cli) + ## Repository layout +## 仓库层级 + - `.github/` Configuration for GitHub [actions][gh-actions], issue and PR [templates][gh-templates], and [dependabot][]. + + GitHub [actions][gh-actions]、issue 与 PR [templates][gh-templates] 以及 [dependabot][] 的配置。 + - `cloud_build/` Configuration for Google [Cloud Build][] that is used for staging and deploying the site. + + 用于站点预发布与部署的 Google [Cloud Build][] 配置。 + - `diagrams/` Source files for diagrams used on the site. + + 站点所用图表的源文件。 + - `examples/` The source files for [code excerpts][] used in doc code blocks. + + 文档代码块中使用的 [代码摘录][code excerpts] 源文件。 + - `src/` - `_11ty/` Custom extensions for [11ty][], [Liquid][], and Markdown. + + [11ty][]、[Liquid][] 与 Markdown 的自定义扩展。 + - `plugins/` - `syntax/` [Shiki][] themes for syntax highlighting. + + 语法高亮的 [Shiki][] 主题。 + - `filters.ts` - `shortcodes.ts` - `_data/` YAML and JSON files used to add data used across site templates. + + 用于向全站模板提供数据的 YAML 与 JSON 文件。 + - `_includes/` Partial files used by liquid [render and include][] statements. + + liquid [render and include][] 语句使用的局部文件。 + - `_layouts/` Layout templates used by the pages on the site. + + 站点页面使用的布局模板。 + - `_sass/` Styles for the generated documentation, written with [sass][]. + + 使用 [sass][] 编写的生成文档样式。 + - `content/` The root directory for the content of the site. + + 站点内容的根目录。 + - `assets/` The directory for assets, including images, used by the site. + + 站点资源(含图片)目录。 + - `...` The other directories hosting the site content. + + 托管站点内容的其他目录。 + - `tool/` - `dash_site/` The implementation directories for the `dash_site` tooling. + + `dash_site` 工具的实现目录。 + - `dash_site` The entrypoint script for the site's CLI tool. + + 站点 CLI 工具的入口脚本。 + - `eleventy.config.ts` The entrypoint for the site's [11ty][] static-site generation setup. + + 站点 [11ty][] 静态站点生成配置的入口。 + - `firebase.json` Configuration for [Firebase Hosting][] that is used for the staged and deployed sites. + + 用于预发布与已部署站点的 [Firebase Hosting][] 配置。 + - `package.json` Configuration of used [npm][] dependencies. + 所用 [npm][] 依赖的配置。 + [gh-actions]: https://docs.github.com/actions [gh-templates]: https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests [dependabot]: https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide diff --git a/sites/docs/src/content/contribute/docs/markdown.md b/sites/docs/src/content/contribute/docs/markdown.md index fa62002c26..87291e5480 100644 --- a/sites/docs/src/content/contribute/docs/markdown.md +++ b/sites/docs/src/content/contribute/docs/markdown.md @@ -1,40 +1,58 @@ --- -title: Authoring Markdown +# title: Authoring Markdown +title: 编写 Markdown shortTitle: Markdown +# description: >- +# Learn about the Markdown syntaxes the Dart and Flutter documentation sites +# support and their guidelines for using them. description: >- - Learn about the Markdown syntaxes the Dart and Flutter documentation sites - support and their guidelines for using them. + 了解 Dart 与 Flutter 文档站点支持的 Markdown 语法及使用指南。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: Our sites support writing content in [Markdown][], with some additions from [GitHub Flavored Markdown][] as well as other custom syntaxes. +我们的站点支持使用 [Markdown][] 编写内容,并包含 [GitHub Flavored Markdown][] 的部分扩展以及其他自定义语法。 + This page outlines the Markdown syntax we support as well as our style guidelines for authoring Markdown. +本页概述我们支持的 Markdown 语法以及编写 Markdown 的样式指南。 + [Markdown]: https://commonmark.org/ [GitHub Flavored Markdown]: https://github.github.com/gfm/ ## General guidelines +## 通用指南 + Prefer using Markdown syntax over custom HTML and components. Raw Markdown is easier to maintain, easier for tools to understand, and easier to migrate in the future if necessary. +优先使用 Markdown 语法而非自定义 HTML 与组件。原始 Markdown 更易维护、更易被工具理解,且在必要时更易迁移。 + ## Code blocks +## 代码块 + Don't use Markdown's indented code blocks, only use fenced code blocks using backticks and always specify a language. For example: +不要使用 Markdown 的缩进代码块,仅使用反引号围栏代码块,并始终指定语言。例如: + ````markdown ```dart void main() { @@ -46,4 +64,6 @@ void main() { To learn more about customizing code blocks, check out the dedicated documentation on [Code blocks][]. +要了解更多代码块自定义方式,请参阅 [代码块][Code blocks] 专题文档。 + [Code blocks]: /contribute/docs/code-blocks diff --git a/sites/docs/src/content/contribute/docs/releases.md b/sites/docs/src/content/contribute/docs/releases.md index 3a56d2b91f..229349ba5f 100644 --- a/sites/docs/src/content/contribute/docs/releases.md +++ b/sites/docs/src/content/contribute/docs/releases.md @@ -1,14 +1,21 @@ --- -title: Handling releases -shortTitle: Releases +# title: Handling releases +title: 处理发布 +# shortTitle: Releases +shortTitle: 发布 +# description: >- +# Learn how to prepare for and handle new releases of +# Dart and Flutter on the documentation sites. description: >- - Learn how to prepare for and handle new releases of - Dart and Flutter on the documentation sites. + 了解如何在文档站点上为 Dart 与 Flutter 新版本做准备并处理发布。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: diff --git a/sites/docs/src/content/contribute/docs/sidenav.md b/sites/docs/src/content/contribute/docs/sidenav.md index 13d3ae41ac..40ab6b9da2 100644 --- a/sites/docs/src/content/contribute/docs/sidenav.md +++ b/sites/docs/src/content/contribute/docs/sidenav.md @@ -1,20 +1,28 @@ --- -title: Sidenav +# title: Sidenav +title: 侧边栏导航 +# description: >- +# Learn about adding to and configuring the sidenav of the +# Dart and Flutter documentation site. description: >- - Learn about adding to and configuring the sidenav of the - Dart and Flutter documentation site. + 了解如何为 Dart 与 Flutter 文档站点的侧边栏导航添加与配置内容。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: The sidenav presents the overall information architecture for the site and provides developers access to documentation by topic. +侧边栏导航呈现站点的整体信息架构,并按主题为开发者提供文档访问入口。 + The contents of the sidenav are configured in [YAML][] files in the `/src/data/sidenav/` directory. Each file in that directory defines a named sidenav, @@ -22,18 +30,34 @@ with the filename (without extension) serving as the key. Pages select which sidenav to display through the `sidenav` front matter field. If no sidenav is specified, the `default` sidenav is used. +侧边栏导航内容在 `/src/data/sidenav/` 目录的 [YAML][] 文件中配置。该目录中每个文件定义一个命名的侧边栏导航,文件名(不含扩展名)作为键。页面通过 `sidenav` frontmatter 字段选择显示的侧边栏导航。若未指定,则使用 `default` 侧边栏导航。 + [YAML]: https://yaml.org/ ## Add a page +## 添加页面 + ## Remove a page +## 移除页面 + ## Hide pages unless open +## 除非展开否则隐藏页面 + ## Icons for top-level sections +## 顶级分区的图标 + Top-level section entries support an optional `icon` field to render a Material Symbols icon to the left of the section title. Use the icon's identifier (for example, `flag`, `download`, or `build`). This icon appears only for the first-level items. +顶级分区条目支持可选的 `icon` 字段,在分区标题左侧渲染 Material Symbols 图标。使用图标标识符(例如 `flag`、`download` 或 `build`)。该图标仅出现在第一级条目中。 + Icons use the site's Material Symbols font. Choose identifiers from Google's [Material Symbols catalog](https://fonts.google.com/icons). +图标使用站点的 Material Symbols 字体。从 Google 的 [Material Symbols 目录](https://fonts.google.com/icons) 选择标识符。 + ## Infrastructure + +## 基础设施 diff --git a/sites/docs/src/content/contribute/docs/writing.md b/sites/docs/src/content/contribute/docs/writing.md index 5690a275ca..4487ec5640 100644 --- a/sites/docs/src/content/contribute/docs/writing.md +++ b/sites/docs/src/content/contribute/docs/writing.md @@ -1,95 +1,149 @@ --- -title: Writing for the docs sites -short-tile: Writing +# title: Writing for the docs sites +title: 为文档站点写作 +# short-tile: Writing +short-tile: 写作 +# description: >- +# Learn about the writing style guide and processes followed when writing +# for the Dart and Flutter documentation sites. description: >- - Learn about the writing style guide and processes followed when writing - for the Dart and Flutter documentation sites. + 了解为 Dart 与 Flutter 文档站点写作时所遵循的写作风格指南与流程。 sitemap: false noindex: true showBreadcrumbs: true +ai-translated: true --- :::warning This document is a work in progress. + +本文档仍在编写中。 ::: ## Writing guidelines +## 写作指南 + When writing for the documentation sites, follow the [Google developer documentation style guide][], except in the cases where the [Dash docs guidelines][] conflict with it. +为文档站点写作时,请遵循 [Google 开发者文档风格指南][Google developer documentation style guide],除非与 [Dash 文档指南][Dash docs guidelines] 冲突。 + [Google developer documentation style guide]: https://developers.google.com/style [Dash docs guidelines]: #dash-docs-styles ### Dash docs styles +### Dash 文档样式 + :::warning This section is a work in progress. It will be added to over time. + +本节仍在编写中。 +将随时间逐步补充。 ::: ## Semantic breaks +## 语义换行 + To make PR review, diff resolution, and history tracking easier, use [semantic breaks][] when writing Markdown. Reference the [full specification][sembr-spec] for helps, but roughly follow these guidelines: +为便于 PR 审查、diff 解决与历史追踪,编写 Markdown 时请使用 [语义换行][semantic breaks]。参阅 [完整规范][sembr-spec] 获取帮助,并大致遵循以下指南: + - Keep each line 80 characters or fewer. + + 每行不超过 80 个字符。 + - Break lines at sentences and, unless the sentence is very short, on phrases within sentences. + + 在句子处换行;除非句子很短,可在句内短语处换行。 + - When it's necessary to split a sentence across lines, try to pick a break that makes it clear that the line continues on the next line. That way future editors and reviewers are more likely to notice that the edit might affect another line. + 当必须将一句拆到多行时,尽量选择能表明下一行继续本句的断点,以便后续编辑者与审查者更容易注意到编辑可能影响其他行。 + Incorporating semantic breaks in your writing might feel tedious at first, but quickly proves helpful and becomes natural. Don't worry about getting the breaks perfect or completely consistent, any effort towards their semantic nature is extremely helpful. +在写作中采用语义换行起初可能显得繁琐,但很快会变得有用且自然。不必追求断行完美或完全一致,任何朝语义化方向的努力都非常有帮助。 + For some more discussion about the origin of this technique, also check out Brandon Rhode's [Semantic Linefeeds][] post. +关于该技术起源的更多讨论,也可参阅 Brandon Rhode 的 [Semantic Linefeeds][] 文章。 + [semantic breaks]: https://sembr.org/ [sembr-spec]: https://sembr.org/#:~:text=seen%20by%20readers.-,Semantic%20Line%20Breaks%20Specification,-(SemBr) [Semantic Linefeeds]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ ## Links +## 链接 + ### Write link text +### 编写链接文本 + Use descriptive link text that follows the Google guidelines on [Cross-references and linking][]. +使用符合 Google [交叉引用与链接][Cross-references and linking] 指南的描述性链接文本。 + [Cross-references and linking]: https://developers.google.com/style/cross-references ### Configure link destinations +### 配置链接目标 + For easier editing, shorter lines, and reduced duplication, prefer using Markdown link references instead of inline links. +为便于编辑、缩短行宽并减少重复,优先使用 Markdown 链接引用而非行内链接。 + Place the link definitions at the end of the current section where they're used, before the next header. +将链接定义放在使用它们的当前章节末尾、下一个标题之前。 + If a link definition is used multiple times across a page, you can place it at the bottom of the document. +若链接定义在整页多次使用,可放在文档底部。 + ### Open the link in a new tab +### 在新标签页中打开链接 + If you want a link to open in a new tab by default, add the `target="_blank"` and `rel="noopener"` attributes. +若希望链接默认在新标签页打开,添加 `target="_blank"` 与 `rel="noopener"` 属性。 + For Markdown links: +对于 Markdown 链接: + ```md [Link text][link-ref]{: target="_blank" rel="noopener"} ``` For HTML links: +对于 HTML 链接: + ```html Link text ``` diff --git a/sites/docs/src/content/contribute/index.md b/sites/docs/src/content/contribute/index.md index 188860a1c1..7c8bd288d2 100644 --- a/sites/docs/src/content/contribute/index.md +++ b/sites/docs/src/content/contribute/index.md @@ -1,9 +1,14 @@ --- -title: Contribute to Flutter -shortTitle: Contribute +# title: Contribute to Flutter +title: 为 Flutter 贡献 +# shortTitle: Contribute +shortTitle: 贡献 +# description: >- +# Learn about contributing to the Flutter project and its surrounding ecosystem. description: >- - Learn about contributing to the Flutter project and its surrounding ecosystem. + 了解如何为 Flutter 项目及其周边生态贡献。 showBreadcrumbs: false +ai-translated: true --- ![Dash and her friends excited for your contribution](/assets/images/dash/dash-contribute.png){:height="160px" style="float: right;"} @@ -12,20 +17,31 @@ If you would like to contribute to the Flutter project and its surrounding ecosystem, we're happy to have your help! +若你希望为 Flutter 项目及其周边生态贡献,我们欢迎你的帮助! + Flutter is an open-source project that thrives on community contributions. No matter whether you're fixing a bug, proposing a new feature, improving documentation, or helping others in the community, your efforts are valuable and appreciated. +Flutter 是依赖社区贡献的开源项目。无论你是在修复 bug、提出新功能、改进文档,还是帮助社区中的其他人,你的努力都很有价值,我们深表感谢。 + This page provides a non-exhaustive overview of how you can get involved. If you need help contributing or would like more suggestions on getting started, consider reaching out on the [Flutter contributors Discord][]. +本页概览参与方式(非穷尽列表)。若你需要贡献方面的帮助或更多入门建议,可在 [Flutter 贡献者 Discord][Flutter contributors Discord] 上联系。 + :::important Before beginning your journey of contributing to Flutter, please make sure to read and follow Flutter's [Code of conduct][]. +在开始为 Flutter 贡献之前, +请务必阅读并遵守 Flutter 的 [行为准则][Code of conduct]。 + Also, learn more about Flutter's [culture of inclusivity][] and [core values][]. + +同时,了解更多关于 Flutter [包容文化][culture of inclusivity] 与 [核心价值观][core values] 的信息。 ::: [Flutter contributors Discord]: {{site.main-url}}/chat @@ -86,57 +102,106 @@ Also, learn more about Flutter's [culture of inclusivity][] and [core values][]. ## Develop with Flutter +## 使用 Flutter 开发 + Even just using Flutter and providing feedback is a valuable contribution! +即便只是使用 Flutter 并提供反馈,也是宝贵的贡献! + ### Provide feedback +### 提供反馈 + Sharing your feedback and experiences helps the Flutter team understand and prioritize developer needs and pain points. +分享反馈与经验有助于 Flutter 团队理解并优先处理开发者需求与痛点。 + You can provide valuable feedback through many avenues, including: +你可以通过多种途径提供有价值的反馈,包括: + - Upvoting existing issues + 为现有 issue 点赞 + If you're experiencing an issue that has already been reported, consider upvoting it to help the Flutter team understand its importance. + 若你遇到的问题已被报告,可考虑点赞以帮助 Flutter 团队了解其重要性。 + Avoid otherwise empty thumbs up, +1, or similar comments. However, if you have additional information, such as reproduction steps or additional version information, do consider providing those details in a new comment. + + 避免仅发表空的点赞、+1 或类似评论。 + 不过,若有额外信息(如复现步骤或版本信息),请在新评论中提供。 + - Reporting new bugs + 报告新 bug + If you experience a bug with Flutter that hasn't yet been reported, do [open a new issue][] with reproduction information. + + 若遇到尚未报告的 Flutter bug,请附带复现信息 [新建 issue][open a new issue]。 + - Requesting features + 请求功能 + If there's a feature you think Flutter should add or implement but hasn't yet been suggested, do [open a new issue][] with all relevant information as well as your use case. + + 若有你认为 Flutter 应添加但尚未被提出的功能,请附带所有相关信息与你的用例 [新建 issue][open a new issue]。 + - Partaking in surveys + 参与调查 + Occasionally, the Flutter team will run developer surveys and studies. To help understand pain points and improve the Flutter developer experience, consider responding with as much feedback and details as possible. + Flutter 团队偶尔会开展开发者调查与研究。 + 为帮助了解痛点并改进开发者体验,请尽量提供详尽的反馈与细节。 + To sign up for future UX research studies, visit [flutter.dev/research-signup][uxr-signup]. + + 若要报名未来的 UX 研究,请访问 [flutter.dev/research-signup][uxr-signup]。 + - Discussing proposals + 讨论提案 + Major changes to Flutter are often discussed through [design documents][]. Consider reading and providing feedback on proposals that are relevant to you or your apps. + Flutter 的重大变更常通过 [设计文档][design documents] 讨论。 + 请阅读并就与你或你的应用相关的提案提供反馈。 + To find current design docs and proposals, check out [issues with the `design doc` label][design-doc-issues] on the GitHub issue database. + + 要查找当前设计文档与提案,请在 GitHub issue 数据库中查看 [带 `design doc` 标签的 issue][design-doc-issues]。 + - Reviewing pull requests + 审查 pull request + If you're familiar with a particular area of Flutter or a solution to a particular issue is important to you, consider reviewing open pull requests, trying them with your app, and providing any relevant feedback. + 若你熟悉 Flutter 的某一领域,或某 issue 的解决方案对你很重要, + 可考虑审查开放的 pull request,在你的应用中试用,并提供相关反馈。 + [open a new issue]: {{site.repo.flutter}}/issues/new [uxr-signup]: {{site.main-url}}/research-signup [design documents]: {{site.repo.flutter}}/blob/main/docs/contributing/Design-Documents.md @@ -144,19 +209,29 @@ You can provide valuable feedback through many avenues, including: ### Try out the beta channel +### 试用 beta 渠道 + To help ensure Flutter's stability and improve upcoming features, help test upcoming releases before they reach the stable channel. +为帮助确保 Flutter 稳定性并改进即将推出的功能,请在版本进入 stable 渠道之前测试即将发布的版本。 + Consider testing releases on the `beta` channel, both for general development and for testing compatibility with your apps. +建议在 `beta` 渠道上测试版本,既用于日常开发,也用于检验与应用兼容性。 + Any feedback you have or regressions you encounter, make sure to [report them][report-bugs] to the Flutter team. +请将任何反馈或遇到的回归 [报告给 Flutter 团队][report-bugs]。 + To get started, [switch][switch-channels] to the [`beta` channel][beta-channel] today and account for any [necessary migrations][]. +入门请立即 [切换][switch-channels] 到 [`beta` 渠道][beta-channel],并处理任何 [必要迁移][necessary migrations]。 + [switch-channels]: /install/upgrade#change-channels [beta-channel]: /install/upgrade#the-beta-channel [report-bugs]: {{site.github}}/flutter/flutter/issues/new/choose @@ -164,45 +239,65 @@ and account for any [necessary migrations][]. ## Contribute code +## 贡献代码 + Directly improve Flutter's codebase and related tools. +直接改进 Flutter 代码库及相关工具。 + ### Flutter framework +### Flutter 框架 + Found a bug in a built-in widget, have an idea for a new one, love adding tests, or just interested in the internals of Flutter? Consider contributing to the Flutter framework itself and improving the core of Flutter for everyone. +在内置 widget 中发现 bug、有新 widget 想法、喜欢添加测试,或对 Flutter 内部机制感兴趣?可考虑为 Flutter 框架本身贡献,为所有人改进 Flutter 核心。 + To learn about contributing to the Flutter framework, check out the Flutter [contribution guide][framework-contribute]. +要了解如何为 Flutter 框架贡献,请参阅 Flutter [贡献指南][framework-contribute]。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md ### Flutter engine +### Flutter 引擎 + Interested in implementing the primitives and platform integrations that underlay Flutter or have a knack for graphics programming? Consider contributing to the Flutter engine and making Flutter even more portable, performant, and powerful. +有兴趣实现 Flutter 底层的原语与平台集成,或擅长图形编程?可考虑为 Flutter 引擎贡献,使 Flutter 更具可移植性、性能与能力。 + To learn about contributing to the Flutter engine, check out the Flutter [contribution guide][framework-contribute] and how to [Set up the engine development environment][engine-setup]. +要了解如何为 Flutter 引擎贡献,请参阅 Flutter [贡献指南][framework-contribute] 以及如何 [配置引擎开发环境][engine-setup]。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [engine-setup]: {{site.repo.flutter}}/blob/main/docs/engine/contributing/Setting-up-the-Engine-development-environment.md -### Flutter packages +### Flutter package Contribute to first-party packages that are maintained by the Flutter team. The first-party packages provide essential functionality for apps as well as encapsulate various platform-specific functionality. +为 Flutter 团队维护的第一方 package 贡献。第一方 package 为应用提供核心功能,并封装多种平台特定功能。 + To learn about contributing to the first-party packages, check out the Flutter [contribution guide][framework-contribute] as well as the packages-specific [contribution guide][packages-contribute]. +要了解如何为第一方 package 贡献,请参阅 Flutter [贡献指南][framework-contribute] 以及 package 专用的 [贡献指南][packages-contribute]。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [packages-contribute]: {{site.repo.packages}}/blob/main/CONTRIBUTING.md @@ -213,138 +308,297 @@ great place to begin contributing due to its relatively low-cost setup. Enhance and fixes can greatly impact the developer experience for Flutter developers and perhaps help you develop your own apps. +由于设置成本相对较低,为 [Dart 与 Flutter DevTools][Dart and Flutter DevTools] 贡献是很好的入门方式。增强与修复可显著影响 Flutter 开发者的开发体验,也可能帮助你开发自己的应用。 + To get started, check out the [DevTools `CONTRIBUTING.md` guide][devtools-contribute]. +入门请参阅 [DevTools `CONTRIBUTING.md` 指南][devtools-contribute]。 + [Dart and Flutter DevTools]: /tools/devtools [devtools-contribute]: {{site.repo.organization}}/devtools/blob/master/CONTRIBUTING.md ### Site infrastructure +### 站点基础设施 + Fix bugs, improve accessibility, or add features to the Dart and Flutter websites. +修复 bug、改进无障碍功能,或为 Dart 与 Flutter 网站添加功能。 + If you're familiar with web development or site generation, contributing to the Dart and Flutter websites can be a great avenue to improve the learning experience of Flutter developers. +若你熟悉 Web 开发或站点生成,为 Dart 与 Flutter 网站贡献可显著改善 Flutter 开发者的学习体验。 + Depending on your interests, you might want to contribute to: +根据兴趣,你可能希望贡献于: + - The pub.dev site + + pub.dev 站点 + - **Live site:** [`pub.dev`]({{site.pub}}) + + **线上站点:** [`pub.dev`]({{site.pub}}) + - **Repository:** [`dart-lang/pub-dev`]({{site.github}}/dart-lang/pub-dev) + + **仓库:** [`dart-lang/pub-dev`]({{site.github}}/dart-lang/pub-dev) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/pub-dev/blob/master/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/pub-dev/blob/master/CONTRIBUTING.md) + - The Flutter documentation site + + Flutter 文档站点 + - **Live site:** [`docs.flutter.dev`]({{site.url}}) + + **线上站点:** [`docs.flutter.dev`]({{site.url}}) + - **Repository:** [`flutter/website`]({{site.repo.this}}) + + **仓库:** [`flutter/website`]({{site.repo.this}}) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/flutter/website/blob/main/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/flutter/website/blob/main/CONTRIBUTING.md) + - The Dart documentation site + + Dart 文档站点 + - **Live site:** [`dart.dev`]({{site.dart-site}}) + + **线上站点:** [`dart.dev`]({{site.dart-site}}) + - **Repository:** [`dart-lang/site-www`]({{site.github}}/dart-lang/site-www) + + **仓库:** [`dart-lang/site-www`]({{site.github}}/dart-lang/site-www) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/site-www/blob/main/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/site-www/blob/main/CONTRIBUTING.md) + - DartPad + - **Live site:** [`dartpad.dev`]({{site.dartpad}}) + + **线上站点:** [`dartpad.dev`]({{site.dartpad}}) + - **Repository:** [`dart-lang/dart-pad`]({{site.github}}/dart-lang/dart-pad) + + **仓库:** [`dart-lang/dart-pad`]({{site.github}}/dart-lang/dart-pad) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dart-pad/blob/main/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dart-pad/blob/main/CONTRIBUTING.md) + - The `dartdoc` tool + + `dartdoc` 工具 + - **Live site:** [`api.flutter.dev`]({{site.api}}) + + **线上站点:** [`api.flutter.dev`]({{site.api}}) + - **Repository:** [`dart-lang/dartdoc`]({{site.github}}/dart-lang/dartdoc) + + **仓库:** [`dart-lang/dartdoc`]({{site.github}}/dart-lang/dartdoc) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dartdoc/blob/main/CONTRIBUTING.md) + **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dartdoc/blob/main/CONTRIBUTING.md) + ### Dart SDK Contribute to the Dart language and surrounding tooling, improving the client-optimized language that forms the foundation of Flutter's excellent developer experience. +为 Dart 语言及周边工具贡献,改进构成 Flutter 出色开发者体验基础的客户端优化语言。 + Dart's contribution workflow is slightly different, so if you're interested, make sure to check out its [contribution][dart-contribute] and [building][dart-build] guides. +Dart 的贡献流程略有不同,若感兴趣请务必查阅其 [贡献][dart-contribute] 与 [构建][dart-build] 指南。 + [dart-contribute]: {{site.github}}/dart-lang/sdk/blob/main/CONTRIBUTING.md [dart-build]: {{site.github}}/dart-lang/sdk/blob/main/docs/Building.md ### Code samples +### 代码示例 + Improve or add samples demonstrating Flutter features, helping developers that prefer to learn by example. +改进或添加演示 Flutter 功能的示例,帮助偏好通过示例学习的开发者。 + You can always share your own samples or templates, or you can contribute to Flutter-maintained samples: +你可以分享自己的示例或模板,也可以为 Flutter 维护的示例贡献: + - Full project samples + + 完整项目示例 + - **Location:** [`flutter/samples`]({{site.repo.samples}}) + + **位置:** [`flutter/samples`]({{site.repo.samples}}) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.repo.samples}}/blob/main/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.samples}}/blob/main/CONTRIBUTING.md) + - API doc samples + + API 文档示例 + - **Location:** [`flutter/flutter/packages/flutter`]({{site.repo.flutter}}/tree/main/packages/flutter) + + **位置:** [`flutter/flutter/packages/flutter`]({{site.repo.flutter}}/tree/main/packages/flutter) + - **Contribution guide:** [`README.md`]({{site.repo.flutter}}/tree/main/dev/snippets) + + **贡献指南:** [`README.md`]({{site.repo.flutter}}/tree/main/dev/snippets) + - Website code snippets + + 网站代码片段 + - **Location:** [`flutter/website/examples`]({{site.repo.this}}/tree/main/examples) + + **位置:** [`flutter/website/examples`]({{site.repo.this}}/tree/main/examples) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.repo.this}}/blob/main/CONTRIBUTING.md) + + **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.this}}/blob/main/CONTRIBUTING.md) + - Flutter repo samples + + Flutter 仓库示例 + - **Location:** [`flutter/flutter/examples`]({{site.repo.flutter}}/tree/main/examples) + + **位置:** [`flutter/flutter/examples`]({{site.repo.flutter}}/tree/main/examples) + - **Contribution guide:** [`CONTRIBUTING.md`]({{site.repo.flutter}}/blob/main/CONTRIBUTING.md) + **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.flutter}}/blob/main/CONTRIBUTING.md) + ## Write documentation +## 编写文档 + Contributing to Flutter documentation, no matter the form, is one of the most impactful ways you can help Flutter. +无论以何种形式为 Flutter 文档贡献,都是你能帮助 Flutter 的最有影响力的方式之一。 + ### Flutter API docs +### Flutter API 文档 + The API docs are heavily relied on by many Flutter developers, both online and in their code editors. +许多 Flutter 开发者在线上及其代码编辑器中大量依赖 API 文档。 + Whether you're interested in writing new docs, updating existing ones, adding relevant code snippets, or even creating new visuals like diagrams, your contribution to the API docs will be appreciated by every Flutter developer. +无论你希望撰写新文档、更新现有文档、添加相关代码片段,还是创建图表等新视觉材料,你对 API 文档的贡献都会受到每位 Flutter 开发者的感谢。 + To get started, check out the [Flutter SDK contribution guide][flutter-contribute], particularly its section on [API documentation][flutter-api-contribute] +入门请参阅 [Flutter SDK 贡献指南][flutter-contribute],尤其是其中关于 [API 文档][flutter-api-contribute] 的部分。 + [flutter-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [flutter-api-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md#api-documentation ### Documentation website +### 文档网站 + Consider contributing to this very site, guiding developers as they learn and explore Flutter. +可考虑为本站点贡献,在开发者学习与探索 Flutter 时提供指引。 + To learn about contributing to the Flutter documentation website, check out the website's [contribution documentation][website-contribute]. +要了解如何为 Flutter 文档网站贡献,请参阅网站的 [贡献文档][website-contribute]。 + You can also contribute to the [Dart website][], enhancing the documentation for the client-optimized language that forms the foundation of Flutter. To learn how to contribute, check out the [`dart-lang/site-www` contribution docs][dart-dev-contribute]. +你也可以为 [Dart 网站][Dart website] 贡献,增强构成 Flutter 基础的客户端优化语言的文档。要了解如何贡献,请参阅 [`dart-lang/site-www` 贡献文档][dart-dev-contribute]。 + [website-contribute]: {{site.repo.this}}/blob/main/CONTRIBUTING.md [Dart website]: {{site.dart-site}} [dart-dev-contribute]: {{site.github}}/dart-lang/site-www/tree/main?tab=readme-ov-file#getting-started ## Triage issues +## 分类 issue + Help the Flutter team by triaging incoming bug reports and feature requests. +通过分类 incoming 的 bug 报告与功能请求来帮助 Flutter 团队。 + There are plenty of ways to help in [Flutter's issue database][flutter-issues], including but not limited to: +在 [Flutter 的 issue 数据库][flutter-issues] 中有很多帮助方式,包括但不限于: + - Determining issue validity + + 判断 issue 是否有效 + - Ensuring actionability + + 确保可执行性 + - Documenting affected versions + + 记录受影响版本 + - Adding reproduction steps + + 添加复现步骤 + - Identifying duplicate or resolved issues + + 识别重复或已解决的 issue + - Solving or redirecting support queries + 解决或转接支持类问题 + To get started helping with issues, read about [helping out in the issue database][issue-contribute] and learn about Flutter's approach to [issue triage][] and [issue hygiene][issue hygiene]. +入门请阅读 [在 issue 数据库中提供帮助][issue-contribute],并了解 Flutter 对 [issue 分类][issue triage] 与 [issue 规范][issue hygiene] 的做法。 + [flutter-issues]: {{site.repo.flutter}}/issues [issue-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md#helping-out-in-the-issue-database [issue triage]: {{site.repo.flutter}}/blob/main/docs/triage/README.md @@ -352,36 +606,54 @@ learn about Flutter's approach to ## Strengthen the package ecosystem +## 强化 package 生态 + Help grow and support the collection of available Dart and Flutter packages on [pub.dev](https://pub.dev/). +帮助在 [pub.dev](https://pub.dev/) 上发展并支持可用的 Dart 与 Flutter package 集合。 + ### Contribute to packages you use +### 为你使用的 package 贡献 + To give back to packages you depend on and potentially even help your own apps, find packages you rely on and contribute back to them. +为所依赖的 package 回馈,并可能惠及你自己的应用:找到你依赖的 package 并为其贡献。 + To contribute to a package, navigate to its page on the [pub.dev site][] and find the repository linked in the page's sidenav. +要为某个 package 贡献,请访问其在 [pub.dev 站点][pub.dev site] 上的页面,并在页面侧边栏导航中找到链接的仓库。 + Before contributing, do make sure to follow each package's contribution guide, discuss your contribution with the maintainers, and keep in mind Flutter's [Code of conduct][]. +贡献前请务必遵循各 package 的贡献指南、与维护者讨论你的贡献,并牢记 Flutter 的 [行为准则][Code of conduct]。 + [pub.dev site]: {{site.pub}} [Code of conduct]: {{site.repo.flutter}}/blob/main/CODE_OF_CONDUCT.md ### Open source reusable functionality from your app +### 将应用中的可复用功能开源 + If you've built a cool, generic widget or utility in your app, consider extracting it into a package and publishing it to pub.dev. +若你在应用中构建了很棒的通用 widget 或工具,可考虑将其提取为 package 并发布到 pub.dev。 + To get started, learn about [Creating Dart packages][] and [Developing Flutter packages][]. Then, when you're ready to publish your package to the [pub.dev site][], follow the guide and best practices on [Publishing packages][]. +入门请学习 [创建 Dart package][Creating Dart packages] 与 [开发 Flutter package][Developing Flutter packages]。准备发布到 [pub.dev 站点][pub.dev site] 时,请遵循 [发布 package][Publishing packages] 指南与最佳实践。 + [Creating Dart packages]: {{site.dart-site}}/tools/pub/create-packages [Developing Flutter packages]: /packages-and-plugins/developing-packages [pub.dev site]: {{site.pub}} @@ -389,22 +661,32 @@ follow the guide and best practices on [Publishing packages][]. ### Add Dart or Flutter support to popular SDKs +### 为流行 SDK 添加 Dart 或 Flutter 支持 + Create or contribute to packages that wrap native SDKs or web APIs. +创建或贡献封装原生 SDK 或 Web API 的 package。 + Before creating a new package, first try to find any existing wrappers that you could use or contribute to on the [pub.dev site][]. +创建新 package 前,请先在 [pub.dev 站点][pub.dev site] 上查找可复用或贡献的现有封装。 + Depending on the SDK and platform, you might need to [Write platform-specific code][], use [JS interop][], wrap a REST API using [`package:http`][], or reimplement the required functionality in Dart. +根据 SDK 与平台,你可能需要 [编写平台特定代码][Write platform-specific code]、使用 [JS 互操作][JS interop]、用 [`package:http`][] 封装 REST API,或在 Dart 中重新实现所需功能。 + If you're planning to create a new package, learn about [Creating Dart packages][] and [Developing Flutter packages][]. Then, when you're ready to publish your package to the [pub.dev site][], follow the guide and best practices on [Publishing packages][]. +若计划创建新 package,请学习 [创建 Dart package][Creating Dart packages] 与 [开发 Flutter package][Developing Flutter packages]。准备发布到 [pub.dev 站点][pub.dev site] 时,请遵循 [发布 package][Publishing packages] 指南与最佳实践。 + [pub.dev site]: {{site.pub}} [Write platform-specific code]: /platform-integration/platform-channels [JS interop]: {{site.dart-site}}/interop/js-interop @@ -412,19 +694,31 @@ follow the guide and best practices on [Publishing packages][]. ## Support the community +## 支持社区 + Help other developers learn Flutter and succeed as they build their own apps. +帮助其他开发者学习 Flutter,并在构建自己的应用时取得成功。 + ### Help other developers +### 帮助其他开发者 + Share your Flutter knowledge and expertise to help your fellow Flutter developers succeed. +分享你的 Flutter 知识与经验,帮助 fellow Flutter 开发者成功。 + This can take many forms from starting a Flutter help channel in your company to answering questions on public forums. +形式多种多样,从在公司内开设 Flutter 帮助频道,到在公共论坛回答问题。 + Some common locations Flutter developers look for help include: +Flutter 开发者常寻求帮助的场所包括: + - [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) - [Flutter Dev Discord](https://discord.com/invite/rflutterdev) - [Dart Community Discord](https://discord.com/invite/Qt6DgfAWWx) @@ -434,39 +728,72 @@ Some common locations Flutter developers look for help include: ### Host events +### 举办活动 + Connect with other Flutter enthusiasts and organize local, national, and even virtual events. Events can be anything, from study groups and simple meetups, to workshops and hackathons. +与其他 Flutter 爱好者联系,组织本地、全国乃至线上活动。活动形式多样,从学习小组、简单聚会,到工作坊与黑客松。 + For inspiration and support, check out existing [Flutter events][], learn more about the [Flutter community][], and explore the [Flutter Meetup Network][]. +获取灵感与支持,可查看现有 [Flutter 活动][Flutter events]、了解更多 [Flutter 社区][Flutter community] 信息,并探索 [Flutter Meetup Network][Flutter Meetup Network]。 + [Flutter events]: {{site.main-url}}/events [Flutter community]: {{site.main-url}}/community [Flutter Meetup Network]: https://www.meetup.com/pro/flutter/ ### Post about Flutter +### 发布 Flutter 相关内容 + Share your insights and projects with the wider Flutter community. +与更广泛的 Flutter 社区分享你的见解与项目。 + There are endless options for sharing about Flutter and connecting with the developer community. Some common outlets include: +分享 Flutter 并与开发者社区建立联系的方式无穷无尽。常见渠道包括: + - Blog posts + + 博客文章 + - Video tutorials + + 视频教程 + - Short-form posts + + 短文 + - Forum threads + + 论坛帖子 + - GitHub discussions + + GitHub 讨论 + - Link aggregation boards + 链接聚合板块 + Post or share about whatever you're passionate about, but if you're not sure what to post, consider posting about topics that developers often ask about. +发布你热衷的任何内容;若不确定发什么,可考虑开发者常问的主题。 + If the platform you're posting on supports tagging posts, consider adding the `#Flutter` and `#FlutterDev` hashtags to help other developers find your content. + +若发布平台支持标签,可添加 `#Flutter` 与 `#FlutterDev` 标签,便于其他开发者发现你的内容。 diff --git a/sites/docs/src/content/cookbook/animation/opacity-animation.md b/sites/docs/src/content/cookbook/animation/opacity-animation.md index fb6a492d3c..6b4d92ecf9 100644 --- a/sites/docs/src/content/cookbook/animation/opacity-animation.md +++ b/sites/docs/src/content/cookbook/animation/opacity-animation.md @@ -5,6 +5,7 @@ title: Widget 的淡入淡出效果 description: 如何淡入淡出一个 widget。 tags: cookbook, 实用教程, 动画效果 keywords: 淡入淡出效果,隐藏元素,用户体验 +ai-translated: true --- @@ -175,7 +176,7 @@ to `true` or `false`. How to fade the box in and out? With an The `AnimatedOpacity` widget requires three arguments: -`AnimatedOpacity` 小部件需要传入三个参数: +`AnimatedOpacity` widget 需要传入三个参数: * `opacity`: A value from 0.0 (invisible) to 1.0 (fully visible). @@ -187,7 +188,7 @@ The `AnimatedOpacity` widget requires three arguments: * `child`: The widget to animate. In this case, the green box. - `child`:需要进行动画的小部件。在这个例子中就是那个绿色的方框。 + `child`:需要进行动画的 widget。在这个例子中就是那个绿色的方框。 ```dart diff --git a/sites/docs/src/content/cookbook/animation/physics-simulation.md b/sites/docs/src/content/cookbook/animation/physics-simulation.md index 4ab41c81ed..5cb46c4b39 100644 --- a/sites/docs/src/content/cookbook/animation/physics-simulation.md +++ b/sites/docs/src/content/cookbook/animation/physics-simulation.md @@ -5,6 +5,7 @@ title: Widget 的物理模拟动画效果 description: 如何实现物理动画。 tags: cookbook, 实用教程, 动画效果 keywords: 物理动画效果,重力效果,交互 +ai-translated: true --- @@ -288,8 +289,8 @@ after it's finished being dragged. This is so that the widget realistically continues at that speed before being snapped back. (The `_runAnimation` method already sets the direction by setting the animation's start and end alignment.) -最后一步时做一些简单的数学计算,计算小部件被拖动完成之后的速度。 -这样小部件在被快速恢复之前实际上以该速度继续运动。 +最后一步时做一些简单的数学计算,计算 widget 被拖动完成之后的速度。 +这样 widget 在被快速恢复之前实际上以该速度继续运动。 (`_runAnimation` 方法已经通过设置动画的开始和结束对齐方式来设置方向。) First, import the `physics` package: diff --git a/sites/docs/src/content/cookbook/audio/index.md b/sites/docs/src/content/cookbook/audio/index.md index 738418c362..773a70ccf4 100644 --- a/sites/docs/src/content/cookbook/audio/index.md +++ b/sites/docs/src/content/cookbook/audio/index.md @@ -1,6 +1,10 @@ --- -title: Flutter audio cookbook -shortTitle: Audio -description: A catalog of Flutter audio-related recipes. +# title: Flutter audio cookbook +title: Flutter 音频食谱 +# shortTitle: Audio +shortTitle: 音频 +# description: A catalog of Flutter audio-related recipes. +description: Flutter 音频相关食谱目录。 layout: toc +ai-translated: true --- diff --git a/sites/docs/src/content/cookbook/audio/record.md b/sites/docs/src/content/cookbook/audio/record.md index d17ed42e17..67d35baba0 100644 --- a/sites/docs/src/content/cookbook/audio/record.md +++ b/sites/docs/src/content/cookbook/audio/record.md @@ -1,29 +1,44 @@ --- -title: Record or stream audio input +# title: Record or stream audio input +title: 录制或流式传输音频输入 +# description: >- +# Learn how to record or stream audio input in +# your Flutter app using the record package. description: >- - Learn how to record or stream audio input in - your Flutter app using the record package. + 了解如何在你的 Flutter 应用中使用 record package + 录制或流式传输音频输入。 +ai-translated: true --- This recipe demonstrates how to use the [`record` package][] to add audio recording and streaming capabilities to your Flutter app. To get started using the package, follow these steps: +本食谱演示如何使用 [`record` package][] 为你的 Flutter 应用添加音频录制与流式传输能力。要开始使用该 package,请按以下步骤操作: + [`record` package]: {{site.pub-pkg}}/record ## 1. Add the package dependency +## 1. 添加 package 依赖 + To add `package:record` as a dependency, use `flutter pub add`: +要将 `package:record` 添加为依赖,请使用 `flutter pub add`: + ```console $ flutter pub add record ``` ## 2. Initialize an `AudioRecorder` +## 2. 初始化 `AudioRecorder` + Initialize an `AudioRecorder` object. This is the primary object that controls the recording process. +初始化一个 `AudioRecorder` 对象。这是控制录制过程的主要对象。 + ```dart import 'package:record/record.dart'; @@ -32,10 +47,14 @@ final recorder = AudioRecorder(); ## 3. Request user permission +## 3. 请求用户权限 + Before recording, you need to request user permission. You might also need to add platform-specific permission configurations. Refer to the [`record` package][] documentation for details. +录制前,你需要请求用户权限。你可能还需要添加各平台特定的权限配置。详情请参阅 [`record` package][] 文档。 + ```dart final recorder = AudioRecorder(); if (await recorder.hasPermission()) { @@ -47,11 +66,15 @@ if (await recorder.hasPermission()) { ## 4. Create a recording configuration +## 4. 创建录制配置 + Create and configure a `RecordConfig` object to specify the record settings, such as the encoder, sample rate, and channel number. You can also enable features like auto gain, echo cancellation, and noise suppression. +创建并配置 `RecordConfig` 对象以指定录制设置,例如编码器、采样率和声道数。你还可以启用自动增益、回声消除和噪声抑制等功能。 + ```dart final recordConfig = RecordConfig( encoder: AudioEncoder.pcm16bits, @@ -65,11 +88,15 @@ final recordConfig = RecordConfig( ## 5. Start recording to a file +## 5. 开始录制到文件 + To start recording to a file, call the `start` method on the `AudioRecorder`, passing in the `recordConfig` you defined and the path where the file should be stored. +要开始录制到文件,请在 `AudioRecorder` 上调用 `start` 方法,传入你定义的 `recordConfig` 以及文件应保存的路径。 + ```dart // TODO: Specify the path where the audio file should be saved. final audioFilePath = 'myRecording.wav'; @@ -78,9 +105,13 @@ await recorder.start(recordConfig, path: audioFilePath); ## 6. Control an ongoing recording +## 6. 控制进行中的录制 + You can control an ongoing recording using the `pause`, `resume`, and `stop` methods on the `AudioRecorder`. +你可以使用 `AudioRecorder` 上的 `pause`、`resume` 和 `stop` 方法来控制进行中的录制。 + ```dart await recorder.pause(); await recorder.resume(); @@ -89,9 +120,13 @@ await recorder.stop(); ## 7. [Optional] Record to an audio stream +## 7. [可选] 录制到音频流 + To stream audio, use the `startStream` method. This returns a [stream][] of audio data. +要流式传输音频,请使用 `startStream` 方法。该方法会返回音频数据的 [stream][]。 + ```dart final stream = await recorder.startStream(recordConfig); stream.listen((audioChunk) { @@ -103,9 +138,13 @@ stream.listen((audioChunk) { ## 8. Stop recording +## 8. 停止录制 + To stop the recording and get the path to the saved file, call the asynchronous `stop` method on the `AudioRecorder`. +要停止录制并获取已保存文件的路径,请在 `AudioRecorder` 上调用异步的 `stop` 方法。 + ```dart final path = await recorder.stop(); print('Recording stopped. File saved to: $path'); @@ -113,24 +152,34 @@ print('Recording stopped. File saved to: $path'); ## 9. Dispose of the recorder +## 9. 释放 recorder + When you are finished using the `AudioRecorder`, remember to call its `dispose` method to release its resources. +当你不再使用 `AudioRecorder` 时,记得调用其 `dispose` 方法以释放资源。 + ```dart await recorder.dispose(); ``` ## Supported formats and encodings +## 支持的格式与编码 + The `record` package supports various encoders and file formats, but support varies by platform. For the full list of supported encoders per platform, check out the package's [encoding support table][]. +`record` package 支持多种编码器和文件格式,但各平台支持情况不同。要查看各平台支持的完整编码器列表,请参阅该 package 的 [编码支持表][encoding support table]。 + For more detailed information and examples, visit the [`record` package][] page on pub.dev or check out the `record` Package of the Week video. +更多详细信息和示例,请访问 pub.dev 上的 [`record` package][] 页面,或观看 `record` Package of the Week 视频。 +
diff --git a/sites/docs/src/content/cookbook/audio/soloud.md b/sites/docs/src/content/cookbook/audio/soloud.md index d7e44a987e..5f701281bf 100644 --- a/sites/docs/src/content/cookbook/audio/soloud.md +++ b/sites/docs/src/content/cookbook/audio/soloud.md @@ -1,8 +1,13 @@ --- -title: Play or stream sound and music with flutter_soloud +# title: Play or stream sound and music with flutter_soloud +title: 使用 flutter_soloud 播放或流式传输声音与音乐 +# description: >- +# Learn how to play or stream audio in +# your Flutter app using the flutter_soloud package. description: >- - Learn how to play or stream audio in - your Flutter app using the flutter_soloud package. + 了解如何在你的 Flutter 应用中使用 flutter_soloud package + 播放或流式传输音频。 +ai-translated: true --- This recipe demonstrates how to integrate the [`flutter_soloud` package][] @@ -10,23 +15,33 @@ into your Flutter application to play sound effects and background music with low latency. To get started using the package, follow these steps: +本食谱演示如何将 [`flutter_soloud` package][] 集成到你的 Flutter 应用中,以低延迟播放音效和背景音乐。要开始使用该 package,请按以下步骤操作: + [`flutter_soloud` package]: {{site.pub-pkg}}/flutter_soloud ## 1. Add the package dependency +## 1. 添加 package 依赖 + To add `package:flutter_soloud` as a dependency, use `flutter pub add`: +要将 `package:flutter_soloud` 添加为依赖,请使用 `flutter pub add`: + ```console $ flutter pub add flutter_soloud ``` ## 2. Initialize `SoLoud` +## 2. 初始化 `SoLoud` + Before playing any audio, you need to initialize the `SoLoud` instance. You can also configure playback settings like sample rate, buffer size, and the number of channels. +在播放任何音频之前,你需要初始化 `SoLoud` 实例。你还可以配置采样率、缓冲区大小和声道数等播放设置。 + ```dart import 'package:flutter_soloud/flutter_soloud.dart'; @@ -43,24 +58,34 @@ await SoLoud.instance.init( ## 3. Load audio +## 3. 加载音频 + The `flutter_soloud` package supports various audio formats including MP3, WAV, OGG, and FLAC. You can load audio from different sources. +`flutter_soloud` package 支持 MP3、WAV、OGG、FLAC 等多种音频格式。你可以从不同来源加载音频。 + From a local file: +从本地文件: + ```dart final sound = await SoLoud.instance.loadFile('path/to/sound.mp3'); ``` From an app asset: +从应用资源: + ```dart final sound = await SoLoud.instance.loadAsset('assets/sound.mp3'); ``` From a network URL: +从网络 URL: + ```dart final sound = await SoLoud.instance.loadUrl( 'https://example.com/sound.mp3', @@ -70,6 +95,8 @@ final sound = await SoLoud.instance.loadUrl( From bytes in memory: +从内存中的字节: + ```dart // TODO: Replace with your actual audio data. final soundBytes = Uint8List.fromList([]); @@ -83,10 +110,14 @@ final sound = await SoLoud.instance.loadMem( ## 4. Play audio +## 4. 播放音频 + Once the audio is loaded, you can play it using `SoLoud.instance.play`. You can also configure gapless looping. +音频加载完成后,你可以使用 `SoLoud.instance.play` 播放。你还可以配置无缝循环。 + ```dart // Play the sound. var handle = await SoLoud.instance.play(sound); @@ -103,11 +134,17 @@ The `play` method returns a handle that references that particular instance of the sound being played. You can use this handle to control the playback. +`play` 方法返回一个 handle,用于引用正在播放的该声音实例。你可以使用此 handle 控制播放。 + ## 5. Control playback +## 5. 控制播放 + With the handle returned from `play`, you can perform various actions such as pausing or resuming playback: +使用 `play` 返回的 handle,你可以执行暂停或恢复播放等多种操作: + ```dart // Toggle pause state. SoLoud.instance.pauseSwitch(handle); @@ -115,12 +152,16 @@ SoLoud.instance.pauseSwitch(handle); Seek to a specific timestamp: +跳转到指定时间戳: + ```dart SoLoud.instance.seek(handle, Duration(seconds: 5)); ``` Set playback speed: +设置播放速度: + ```dart // Play twice as fast. SoLoud.instance.setRelativePlaySpeed(handle, 2.0); @@ -128,6 +169,8 @@ SoLoud.instance.setRelativePlaySpeed(handle, 2.0); Adjust playback volume: +调整播放音量: + ```dart // Set playback to 50% volume. SoLoud.instance.setVolume(handle, 0.5); @@ -135,6 +178,8 @@ SoLoud.instance.setVolume(handle, 0.5); Fade the audio volume: +淡入淡出音量: + ```dart SoLoud.instance.fadeVolume( handle, @@ -145,26 +190,38 @@ SoLoud.instance.fadeVolume( Stop playback: +停止播放: + ```dart await SoLoud.instance.stop(handle); ``` ## 6. Dispose of sound sources +## 6. 释放声音源 + When you are finished with a sound source, remember to dispose of it to free up resources. +当你不再使用某个声音源时,记得释放它以释放资源。 + ```dart await SoLoud.instance.disposeSource(sound); ``` ## 7. [Optional] Stream audio +## 7. [可选] 流式传输音频 + The `flutter_soloud` package also supports streaming audio data in real time. +`flutter_soloud` package 还支持实时流式传输音频数据。 + Initialize and configure a buffer stream: +初始化并配置缓冲区流: + ```dart final stream = SoLoud.instance.setBufferStream( bufferingType: BufferingType.released, @@ -176,12 +233,16 @@ final stream = SoLoud.instance.setBufferStream( Play the stream: +播放流: + ```dart final handle = await SoLoud.instance.play(stream); ``` As your app receives audio data, add it to the stream: +当你的应用收到音频数据时,将其添加到流中: + ```dart // TODO: Replace with your actual audio data. final audioChunk = Uint8List.fromList([]); @@ -194,16 +255,22 @@ SoLoud.instance.addAudioDataStream( When you're done with the audio stream, mark it as complete: +当你完成音频流时,将其标记为已完成: + ```dart SoLoud.instance.setDataIsEnded(stream); ``` ## More information +## 更多信息 + For more detailed information and examples, visit the [`flutter_soloud` package][] on pub.dev or check out the `flutter_soloud` Package of the Week video. +更多详细信息和示例,请访问 pub.dev 上的 [`flutter_soloud` package][],或观看 `flutter_soloud` Package of the Week 视频。 +
diff --git a/sites/docs/src/content/cookbook/design/cupertino-sheets.md b/sites/docs/src/content/cookbook/design/cupertino-sheets.md index 0b00b9d8db..0c0402c390 100644 --- a/sites/docs/src/content/cookbook/design/cupertino-sheets.md +++ b/sites/docs/src/content/cookbook/design/cupertino-sheets.md @@ -1,7 +1,11 @@ --- -title: Display a Cupertino sheet +# title: Display a Cupertino sheet +title: 显示 Cupertino sheet +# description: >- +# How to implement a Cupertino sheet to display messages and content. description: >- - How to implement a Cupertino sheet to display messages and content. + 如何实现 Cupertino sheet 以显示消息和内容。 +ai-translated: true --- @@ -11,19 +15,32 @@ used to present content or options. It slides up from the bottom of the screen and can be pulled down to dismiss. +Cupertino sheet 是一种 iOS 风格的模态底部 sheet,用于展示内容或选项。它从屏幕底部滑入,可下拉关闭。 + In Flutter, this is the job of [`showCupertinoSheet`][]. This recipe implements a Cupertino sheet using the following steps: +在 Flutter 中,这由 [`showCupertinoSheet`][] 负责。本食谱通过以下步骤实现 Cupertino sheet: + 1. Create a `CupertinoApp` or `MaterialApp`. + + 创建 `CupertinoApp` 或 `MaterialApp`。 + 2. Display the sheet content. + 显示 sheet 内容。 + ## 1. Create a `CupertinoApp` +## 1. 创建 `CupertinoApp` + When creating apps that follow the iOS design guidelines, you can use `CupertinoApp`. The following example provides a button in the center of the screen that triggers the modal. +创建遵循 iOS 设计规范的应用时,你可以使用 `CupertinoApp`。以下示例在屏幕中央提供一个按钮,用于触发模态界面。 + ```dart class CupertinoSheetDemo extends StatelessWidget { @@ -41,10 +58,14 @@ class CupertinoSheetDemo extends StatelessWidget { ## 2. Display the sheet content +## 2. 显示 sheet 内容 + With the basic app structure in place, display the sheet. To show it, call `showCupertinoSheet` and provide a `scrollableBuilder` that returns the content for the sheet, such as a `SingleChildScrollView`. +基本应用结构就绪后,显示 sheet。要展示它,请调用 `showCupertinoSheet` 并提供 `scrollableBuilder`,返回 sheet 的内容,例如 `SingleChildScrollView`。 + ```dart showCupertinoSheet( @@ -76,6 +97,8 @@ showCupertinoSheet( ## Interactive example +## 交互式示例 + ```dartpad title="Flutter CupertinoSheet hands-on example in DartPad" run="true" import 'package:flutter/cupertino.dart'; diff --git a/sites/docs/src/content/cookbook/effects/index.md b/sites/docs/src/content/cookbook/effects/index.md index bc95dcc6e4..f0c7f0efa2 100644 --- a/sites/docs/src/content/cookbook/effects/index.md +++ b/sites/docs/src/content/cookbook/effects/index.md @@ -1,6 +1,10 @@ --- -title: Flutter effects cookbook -shortTitle: Effects -description: A catalog of recipes for adding effects to your Flutter app. +# title: Flutter effects cookbook +title: Flutter 动效食谱 +# shortTitle: Effects +shortTitle: 动效 +# description: A catalog of recipes for adding effects to your Flutter app. +description: 为 Flutter 应用添加动效的食谱目录。 layout: toc +ai-translated: true --- diff --git a/sites/docs/src/content/cookbook/forms/text-input.md b/sites/docs/src/content/cookbook/forms/text-input.md index 9f49bb2000..385fd11a83 100644 --- a/sites/docs/src/content/cookbook/forms/text-input.md +++ b/sites/docs/src/content/cookbook/forms/text-input.md @@ -5,6 +5,7 @@ title: 文本框的创建和设定 description: 如何实现一个文本框。 tags: cookbook, 实用教程, 表格交互 keywords: 文本框,实现,Flutter搜索框 +ai-translated: true --- @@ -28,7 +29,7 @@ Flutter 提供了两个开箱即用的文本框组件: [`TextField`][] is the most commonly used text input widget. -[`TextField`][] 是最常用的文本输入组件。 +[`TextField`][] 是最常用的文本输入 widget。 By default, a `TextField` is decorated with an underline. You can add a label, icon, inline hint text, and error text by supplying an @@ -70,8 +71,8 @@ such as validation and integration with other [`FormField`][] widgets. [`TextFormField`][] 内部封装了一个 `TextField` -并被集成在表单组件 [`Form`][] 中。 -如果需要对文本输入进行验证或者需要与其他表单组件 [`FormField`][] 交互联动, +并被集成在表单 widget [`Form`][] 中。 +如果需要对文本输入进行验证或者需要与其他表单 widget [`FormField`][] 交互联动, 可以考虑使用 `TextFormField`。 diff --git a/sites/docs/src/content/cookbook/forms/validation.md b/sites/docs/src/content/cookbook/forms/validation.md index ea7e8d9b21..26e1ce734f 100644 --- a/sites/docs/src/content/cookbook/forms/validation.md +++ b/sites/docs/src/content/cookbook/forms/validation.md @@ -5,6 +5,7 @@ title: 构建一个有验证判断的表单 description: 如何创建一个能够验证输入内容的表单。 tags: cookbook, 实用教程, 表格交互 keywords: 文本框, 验证 +ai-translated: true --- @@ -127,7 +128,7 @@ you can use the [`Form.of()`][] method to access the form within nested widgets. 一般情况下,推荐使用 `GlobalKey` 来访问一个表单。 -嵌套组件且组件树比较复杂的情况下, +嵌套 widget 且 widget 树比较复杂的情况下, 可以使用 [`Form.of()`][] 方法访问表单。 ::: diff --git a/sites/docs/src/content/cookbook/games/achievements-leaderboard.md b/sites/docs/src/content/cookbook/games/achievements-leaderboard.md index fd49c73064..4f96155a2b 100644 --- a/sites/docs/src/content/cookbook/games/achievements-leaderboard.md +++ b/sites/docs/src/content/cookbook/games/achievements-leaderboard.md @@ -1,7 +1,11 @@ --- -title: Add achievements and leaderboards to your mobile game +# title: Add achievements and leaderboards to your mobile game +title: 为移动游戏添加成就与排行榜 +# description: > +# How to use the games_services plugin to add functionality to your game. description: > - How to use the games_services plugin to add functionality to your game. + 如何使用 games_services 插件为你的游戏添加相关功能。 +ai-translated: true --- @@ -15,6 +19,8 @@ Some players want to *compete* in it. This could be hitting high scores or accomplishing speedruns. These two ideas map to the concepts of *achievements* and *leaderboards*. +玩家玩游戏有多种动机。概括而言,主要有四种动机:[immersion, achievement, cooperation, and competition][]。无论你构建什么游戏,总有一些玩家想在游戏中*取得成就*,例如赢得奖杯或解锁秘密。也有一些玩家想在游戏中*竞争*,例如冲击高分或完成速通。这两种诉求对应*成就*和*排行榜*的概念。 + ![A simple graphic representing the four types of motivation explained above](/assets/images/docs/cookbook/types-of-gamer-motivations.png){:.site-illustration} Ecosystems such as the App Store and Google Play provide @@ -22,36 +28,60 @@ centralized services for achievements and leaderboards. Players can view achievements from all their games in one place and developers don't need to re-implement them for every game. +App Store 和 Google Play 等生态提供成就与排行榜的集中式服务。玩家可以在一处查看所有游戏的成就,开发者也无需为每个游戏重复实现这些功能。 + This recipe demonstrates how to use the [`games_services` package][] to add achievements and leaderboard functionality to your mobile game. +本食谱演示如何使用 [`games_services` package][] 为你的移动游戏添加成就与排行榜功能。 + [`games_services` package]: {{site.pub-pkg}}/games_services [immersion, achievement, cooperation, and competition]: https://meditations.metavert.io/p/game-player-motivations ## 1. Enable platform services +## 1. 启用平台服务 + To enable games services, set up *Game Center* on iOS and *Google Play Games Services* on Android. +要启用游戏服务,请在 iOS 上配置 *Game Center*,在 Android 上配置 *Google Play Games Services*。 + +### iOS + ### iOS To enable Game Center (GameKit) on iOS: +要在 iOS 上启用 Game Center(GameKit): + 1. Open your Flutter project in Xcode. Open `ios/Runner.xcworkspace` + 在 Xcode 中打开你的 Flutter 项目。打开 `ios/Runner.xcworkspace` + 2. Select the root **Runner** project. + 选择根级 **Runner** 项目。 + 3. Go to the **Signing & Capabilities** tab. + 进入 **Signing & Capabilities** 标签页。 + 4. Click the `+` button to add **Game Center** as a capability. + 点击 `+` 按钮,将 **Game Center** 添加为 capability。 + 5. Close Xcode. + 关闭 Xcode。 + 6. If you haven't already, register your game in [App Store Connect][] and from the **My App** section press the `+` icon. + 如果尚未注册,请在 [App Store Connect][] 中注册你的游戏,并在 **My App** 部分点击 `+` 图标。 + ![Screenshot of the + button in App Store Connect](/assets/images/docs/cookbook/app-store-add-app-button.png) 7. Still in App Store Connect, look for the *Game Center* section. You @@ -60,46 +90,68 @@ To enable Game Center (GameKit) on iOS: achievements, depending on your game. Take note of the IDs of the leaderboards and achievements you create. + 仍在 App Store Connect 中,找到 *Game Center* 部分(撰写本文时位于 **Services**)。在 **Game Center** 页面,可根据游戏需要设置排行榜和若干成就。记下你创建的排行榜与成就的 ID。 + [App Store Connect]: https://appstoreconnect.apple.com/ ### Android +### Android + To enable *Play Games Services* on Android: +要在 Android 上启用 *Play Games Services*: + 1. If you haven't already, go to [Google Play Console][] and register your game there. + 如果尚未注册,请前往 [Google Play Console][] 注册你的游戏。 + ![Screenshot of the 'Create app' button in Google Play Console](/assets/images/docs/cookbook/google-play-create-app.png) 2. Still in Google Play Console, select *Play Games Services* → *Setup and management* → *Configuration* from the navigation menu and follow their instructions. + 仍在 Google Play Console 中,从导航菜单选择 *Play Games Services* → *Setup and management* → *Configuration*,并按说明操作。 + * This takes a significant amount of time and patience. Among other things, you'll need to set up an OAuth consent screen in Google Cloud Console. If at any point you feel lost, consult the official [Play Games Services guide][]. + 这需要较多时间和耐心。除其他事项外,你还需要在 Google Cloud Console 中配置 OAuth 同意屏幕。如有困惑,请参阅官方 [Play Games Services 指南][Play Games Services guide]。 + ![Screenshot showing the Games Services section in Google Play Console](/assets/images/docs/cookbook/play-console-play-games-services.png) 3. When done, you can start adding leaderboards and achievements in **Play Games Services** → **Setup and management**. Create the exact same set as you did on the iOS side. Make note of IDs. + 完成后,可在 **Play Games Services** → **Setup and management** 中添加排行榜和成就。创建与 iOS 端完全相同的集合,并记下 ID。 + 4. Go to **Play Games Services → Setup and management → Publishing**. + 前往 **Play Games Services → Setup and management → Publishing**。 + 5. Click **Publish**. Don't worry, this doesn't actually publish your game. It only publishes the achievements and leaderboard. Once a leaderboard, for example, is published this way, it cannot be unpublished. + 点击 **Publish**。不必担心,这并不会真正发布你的游戏,只会发布成就和排行榜。例如,排行榜一旦以此方式发布,就无法取消发布。 + 6. Go to **Play Games Services** **→ Setup and management → Configuration → Credentials**. + 前往 **Play Games Services → Setup and management → Configuration → Credentials**。 + 7. Find the **Get resources** button. It returns an XML file with the Play Games Services IDs. + 找到 **Get resources** 按钮。它会返回包含 Play Games Services ID 的 XML 文件。 + ```xml @@ -118,16 +170,24 @@ To enable *Play Games Services* on Android: 8. Add a file at `android/app/src/main/res/values/games-ids.xml` containing the XML you received in the previous step. + 在 `android/app/src/main/res/values/games-ids.xml` 添加文件,内容为上一步收到的 XML。 + [Google Play Console]: https://play.google.com/console/ [Play Games Services guide]: {{site.developers}}/games/services/console/enabling ## 2. Sign in to the game service +## 2. 登录游戏服务 + Now that you have set up *Game Center* and *Play Games Services*, and have your achievement & leaderboard IDs ready, it's finally Dart time. +既然你已配置好 *Game Center* 和 *Play Games Services*,并准备好了成就与排行榜 ID,接下来就该写 Dart 了。 + 1. Add a dependency on the [`games_services` package]({{site.pub-pkg}}/games_services). + 添加对 [`games_services` package]({{site.pub-pkg}}/games_services) 的依赖。 + ```console $ flutter pub add games_services ``` @@ -135,6 +195,8 @@ have your achievement & leaderboard IDs ready, it's finally Dart time. 2. Before you can do anything else, you have to sign the player into the game service. + 在做其他操作之前,你必须让玩家登录游戏服务。 + ```dart try { @@ -148,24 +210,33 @@ The sign in happens in the background. It takes several seconds, so don't call `signIn()` before `runApp()` or the players will be forced to stare at a blank screen every time they start your game. +登录在后台进行,需要数秒,因此不要在 `runApp()` 之前调用 `signIn()`,否则玩家每次启动游戏都会面对空白屏幕。 + The API calls to the `games_services` API can fail for a multitude of reasons. Therefore, every call should be wrapped in a try-catch block as in the previous example. The rest of this recipe omits exception handling for clarity. +调用 `games_services` API 可能因多种原因失败。因此,每次调用都应像上例那样用 try-catch 包裹。为清晰起见,本食谱其余部分省略异常处理。 + :::tip It's a good practice to create a controller. This would be a `ChangeNotifier`, a bloc, or some other piece of logic that wraps around the raw functionality of the `games_services` plugin. +创建一个 controller 是好做法。可以是 `ChangeNotifier`、bloc,或其他封装 `games_services` 插件原始功能的逻辑。 ::: ## 3. Unlock achievements +## 3. 解锁成就 + 1. Register achievements in Google Play Console and App Store Connect, and take note of their IDs. Now you can award any of those achievements from your Dart code: + 在 Google Play Console 和 App Store Connect 中注册成就并记下 ID。现在你可以从 Dart 代码授予这些成就: + ```dart await GamesServices.unlock( @@ -178,35 +249,49 @@ the raw functionality of the `games_services` plugin. The player's account on Google Play Games or Apple Game Center now lists the achievement. + 玩家在 Google Play Games 或 Apple Game Center 的账户中现在会显示该成就。 2. To display the achievements UI from your game, call the `games_services` API: + 要从游戏中显示成就 UI,请调用 `games_services` API: + ```dart await GamesServices.showAchievements(); ``` This displays the platform achievements UI as an overlay on your game. + 这会在你的游戏上以叠加层形式显示平台成就 UI。 3. To display the achievements in your own UI, use [`GamesServices.loadAchievements()`][]. + 若要在自己的 UI 中显示成就,请使用 [`GamesServices.loadAchievements()`][]。 + [`GamesServices.loadAchievements()`]: {{site.pub-api}}/games_services/latest/games_services/GamesServices/loadAchievements.html ## 4. Submit scores +## 4. 提交分数 + When the player finishes a play-through, your game can submit the result of that play session into one or more leaderboards. +当玩家完成一局时,你的游戏可将该局结果提交到一个或多个排行榜。 + For example, a platformer game like Super Mario can submit both the final score and the time taken to complete the level, to two separate leaderboards. +例如,像超级马里奥这样的平台游戏可将最终得分和通关用时分别提交到两个不同的排行榜。 + 1. In the first step, you registered a leaderboard in Google Play Console and App Store Connect, and took note of its ID. Using this ID, you can submit new scores for the player: + 在第一步中,你已在 Google Play Console 和 App Store Connect 注册排行榜并记下 ID。使用该 ID,你可以为玩家提交新分数: + ```dart await GamesServices.submitScore( @@ -220,10 +305,13 @@ leaderboards. You don't need to check whether the new score is the player's highest. The platform game services handle that for you. + 你无需检查新分数是否为玩家最高分,平台游戏服务会替你处理。 2. To display the leaderboard as an overlay over your game, make the following call: + 要在游戏上以叠加层显示排行榜,请进行以下调用: + ```dart await GamesServices.showLeaderboards( @@ -235,24 +323,44 @@ leaderboards. 3. If you want to display the leaderboard scores in your own UI, you can fetch them with [`GamesServices.loadLeaderboardScores()`][]. + 若要在自己的 UI 中显示排行榜分数,可使用 [`GamesServices.loadLeaderboardScores()`][] 获取。 + [`GamesServices.loadLeaderboardScores()`]: {{site.pub-api}}/games_services/latest/games_services/GamesServices/loadLeaderboardScores.html ## 5. Next steps +## 5. 后续步骤 + There's more to the `games_services` plugin. With this plugin, you can: +`games_services` 插件还有更多功能。借助该插件,你可以: + - Get the player's icon, name or unique ID + + 获取玩家头像、名称或唯一 ID + - Save and load game states + + 保存和加载游戏状态 + - Sign out of the game service + 退出游戏服务 + Some achievements can be incremental. For example: "You have collected all 10 pieces of the McGuffin." +部分成就可以是递增的,例如:「你已收集齐 McGuffin 的全部 10 个碎片。」 + Each game has different needs from game services. +每个游戏对游戏服务的需求各不相同。 + To start, you might want to create this controller in order to keep all achievements & leaderboards logic in one place: +开始时,你可以创建如下 controller,将所有成就与排行榜逻辑集中在一处: + ```dart import 'dart:async'; @@ -371,14 +479,26 @@ class GamesServicesController { ## More information +## 更多信息 + The Flutter Casual Games Toolkit includes the following templates: +Flutter Casual Games Toolkit 包含以下模板: + * [basic][]: basic starter game + + [basic][]:基础入门游戏 + * [card][]: starter card game + + [card][]:入门纸牌游戏 + * [endless runner][]: starter game (using Flame) where the player endlessly runs, avoiding pitfalls and gaining rewards + [endless runner][]:入门游戏(使用 Flame),玩家无尽奔跑、躲避陷阱并获取奖励 + [basic]: {{site.github}}/flutter/games/tree/main/templates/basic#readme [card]: {{site.github}}/flutter/games/tree/main/templates/card#readme [endless runner]: {{site.github}}/flutter/games/tree/main/templates/endless_runner#readme diff --git a/sites/docs/src/content/cookbook/games/firestore-multiplayer.md b/sites/docs/src/content/cookbook/games/firestore-multiplayer.md index 34d1e788e1..356eff7a8a 100644 --- a/sites/docs/src/content/cookbook/games/firestore-multiplayer.md +++ b/sites/docs/src/content/cookbook/games/firestore-multiplayer.md @@ -1,8 +1,12 @@ --- -title: Add multiplayer support using Firestore +# title: Add multiplayer support using Firestore +title: 使用 Firestore 添加多人游戏支持 +# description: > +# How to use Firebase Cloud Firestore to implement multiplayer +# in your game. description: > - How to use use Firebase Cloud Firestore to implement multiplayer - in your game. + 如何使用 Firebase Cloud Firestore 在游戏中实现多人游戏。 +ai-translated: true --- @@ -10,16 +14,26 @@ description: > Multiplayer games need a way to synchronize game states between players. Broadly speaking, two types of multiplayer games exist: +多人游戏需要在玩家之间同步游戏状态。概括而言,存在两类多人游戏: + 1. **High tick rate**. These games need to synchronize game states many times per second with low latency. These would include action games, sports games, fighting games. + **高 tick rate**。 + 这类游戏需要每秒多次以低延迟同步游戏状态。 + 包括动作游戏、体育游戏、格斗游戏等。 + 2. **Low tick rate**. These games only need to synchronize game states occasionally with latency having less impact. These would include card games, strategy games, puzzle games. + **低 tick rate**。 + 这类游戏只需偶尔同步游戏状态,延迟影响较小。 + 包括纸牌游戏、策略游戏、益智游戏等。 + This resembles the differentiation between real-time versus turn-based games, though the analogy falls short. For example, real-time strategy games run—as the name suggests—in @@ -28,6 +42,8 @@ These games can simulate much of what happens in between player interactions on local machines. Therefore, they don't need to synchronize game states that often. +这与即时制与回合制游戏的区分类似,但类比并不完全准确。例如,即时战略游戏顾名思义是实时运行的,但这并不等同于高 tick rate。这类游戏可以在本地机器上模拟玩家交互之间发生的许多内容,因此不需要频繁同步游戏状态。 + ![An illustration of two mobile phones and a two-way arrow between them](/assets/images/docs/cookbook/multiplayer-two-mobiles.jpg){:.site-illustration} If you can choose low tick rates as a developer, you should. @@ -37,26 +53,36 @@ For those cases, solutions such as Firestore *don't make a good fit*. Pick a dedicated multiplayer server solution such as [Nakama][]. Nakama has a [Dart package][]. +如果你能作为开发者选择低 tick rate,就应该这样做。低 tick 可降低延迟要求和服务器成本。有时游戏需要高 tick rate 的同步,此时 Firestore 等方案*并不合适*。应选择专用多人服务器方案,例如 [Nakama][]。Nakama 提供 [Dart package][]。 + If you expect that your game requires a low tick rate of synchronization, continue reading. +如果你预计游戏需要低 tick rate 的同步,请继续阅读。 + This recipe demonstrates how to use the [`cloud_firestore` package][] to implement multiplayer capabilities in your game. This recipe doesn't require a server. It uses two or more clients sharing game state using Cloud Firestore. +本食谱演示如何使用 [`cloud_firestore` package][] 在游戏中实现多人功能。本食谱不需要服务器,它使用两个或多个客户端通过 Cloud Firestore 共享游戏状态。 + [`cloud_firestore` package]: {{site.pub-pkg}}/cloud_firestore [Dart package]: {{site.pub-pkg}}/nakama [Nakama]: https://heroiclabs.com/nakama/ ## 1. Prepare your game for multiplayer +## 1. 为多人游戏准备你的游戏 + Write your game code to allow changing the game state in response to both local events and remote events. A local event could be a player action or some game logic. A remote event could be a world update coming from the server. +编写游戏代码,使其能响应本地事件和远程事件而改变游戏状态。本地事件可以是玩家操作或某些游戏逻辑,远程事件可以是来自服务器的世界更新。 + ![Screenshot of the card game](/assets/images/docs/cookbook/multiplayer-card-game.jpg){:.site-mobile-screenshot .site-illustration} To simplify this cookbook recipe, start with @@ -64,6 +90,8 @@ the [`card`][] template that you'll find in the [`flutter/games` repository][]. Run the following command to clone that repository: +为简化本食谱,请从 [`flutter/games` repository][] 中的 [`card`][] 模板开始。运行以下命令克隆该仓库: + ```console git clone https://github.com/flutter/games.git ``` @@ -75,9 +103,12 @@ git clone https://github.com/flutter/games.git Open the project in `templates/card`. +在 `templates/card` 中打开项目。 + :::note You can ignore this step and follow the recipe with your own game project. Adapt the code at appropriate places. +你可以跳过此步骤,用自己的游戏项目跟随本食谱,并在适当位置调整代码。 ::: [`card`]: {{site.github}}/flutter/games/tree/main/templates/card#readme @@ -85,6 +116,8 @@ project. Adapt the code at appropriate places. ## 2. Install Firestore +## 2. 安装 Firestore + [Cloud Firestore][] is a horizontally scaling, NoSQL document database in the cloud. It includes built-in live synchronization. @@ -92,28 +125,51 @@ This is perfect for our needs. It keeps the game state updated in the cloud database, so every player sees the same state. +[Cloud Firestore][] 是云端可水平扩展的 NoSQL 文档数据库,内置实时同步,非常适合我们的需求。它会在云数据库中保持游戏状态更新,使每位玩家看到相同的状态。 + If you want a quick, 15-minute primer on Cloud Firestore, check out the following video: +若想快速了解 Cloud Firestore(约 15 分钟),可观看以下视频: + To add Firestore to your Flutter project, follow the first two steps of the [Get started with Cloud Firestore][] guide: +要将 Firestore 添加到你的 Flutter 项目,请遵循 [Cloud Firestore 入门指南][Get started with Cloud Firestore] 的前两步: + * [Create a Cloud Firestore database][] + + [创建 Cloud Firestore 数据库][Create a Cloud Firestore database] + * [Set up your development environment][] + [设置开发环境][Set up your development environment] + The desired outcomes include: +预期结果包括: + * A Firestore database ready in the cloud, in **Test mode** + + 云端已就绪的 Firestore 数据库,处于 **Test mode** + * A generated `firebase_options.dart` file + + 已生成的 `firebase_options.dart` 文件 + * The appropriate plugins added to your `pubspec.yaml` + 已在 `pubspec.yaml` 中添加相应插件 + You *don't* need to write any Dart code in this step. As soon as you understand the step of writing Dart code in that guide, return to this recipe. +此步骤*无需*编写任何 Dart 代码。一旦理解该指南中编写 Dart 代码的步骤,请返回本食谱。 + {% comment %} Revisit to see if we can inline the steps here: @@ -128,10 +184,14 @@ Dart code in that guide, return to this recipe. ## 3. Initialize Firestore +## 3. 初始化 Firestore + 1. Open `lib/main.dart` and import the plugins, as well as the `firebase_options.dart` file that was generated by `flutterfire configure` in the previous step. + 打开 `lib/main.dart`,导入插件以及上一步由 `flutterfire configure` 生成的 `firebase_options.dart` 文件。 + ```dart import 'package:cloud_firestore/cloud_firestore.dart'; @@ -143,6 +203,8 @@ Dart code in that guide, return to this recipe. 2. Add the following code just above the call to `runApp()` in `lib/main.dart`: + 在 `lib/main.dart` 中 `runApp()` 调用正上方添加以下代码: + ```dart WidgetsFlutterBinding.ensureInitialized(); @@ -151,16 +213,21 @@ Dart code in that guide, return to this recipe. ``` This ensures that Firebase is initialized on game startup. + 这可确保游戏启动时初始化 Firebase。 3. Add the Firestore instance to the app. That way, any widget can access this instance. Widgets can also react to the instance missing, if needed. + 将 Firestore 实例添加到应用中,这样任何 widget 都可以访问该实例,必要时 widget 也可对实例缺失做出响应。 + To do this with the `card` template, you can use the `provider` package (which is already installed as a dependency). + 对于 `card` 模板,你可以使用 `provider` package(已作为依赖安装)。 Replace the boilerplate `runApp(MyApp())` with the following: + 将样板 `runApp(MyApp())` 替换为: ```dart @@ -169,22 +236,28 @@ Dart code in that guide, return to this recipe. Put the provider above `MyApp`, not inside it. This enables you to test the app without Firebase. + 将 provider 放在 `MyApp` 之上,而非其内部,这样你可以在不使用 Firebase 的情况下测试应用。 :::note In case you are *not* working with the `card` template, you must either [install the `provider` package][] or use your own method of accessing the `FirebaseFirestore` instance from various parts of your codebase. + 如果你*未*使用 `card` 模板,则必须 [安装 `provider` package][install the `provider` package],或使用你自己的方式从代码库各处访问 `FirebaseFirestore` 实例。 ::: [install the `provider` package]: {{site.pub-pkg}}/provider/install ## 4. Create a Firestore controller class +## 4. 创建 Firestore controller 类 + Though you can talk to Firestore directly, you should write a dedicated controller class to make the code more readable and maintainable. +虽然可以直接与 Firestore 通信,但你应编写专用的 controller 类,使代码更易读、更易维护。 + How you implement the controller depends on your game and on the exact design of your multiplayer experience. For the case of the `card` template, @@ -192,12 +265,16 @@ you could synchronize the contents of the two circular playing areas. It's not enough for a full multiplayer experience, but it's a good start. +如何实现 controller 取决于你的游戏以及多人体验的具体设计。对于 `card` 模板,你可以同步两个圆形游戏区域的内容。这不足以构成完整的多人体验,但是个良好的起点。 + ![Screenshot of the card game, with arrows pointing to playing areas](/assets/images/docs/cookbook/multiplayer-areas.jpg){:.site-mobile-screenshot .site-illustration} To create a controller, copy, then paste the following code into a new file called `lib/multiplayer/firestore_controller.dart`. +要创建 controller,请将以下代码复制并粘贴到新文件 `lib/multiplayer/firestore_controller.dart`。 + ```dart import 'dart:async'; @@ -364,12 +441,18 @@ class FirebaseControllerException implements Exception { Notice the following features of this code: +请注意此代码的以下特点: + * The controller's constructor takes a `BoardState`. This enables the controller to manipulate the local state of the game. + controller 的构造函数接受 `BoardState`,使 controller 能够操作游戏的本地状态。 + * The controller subscribes to both local changes to update Firestore and to remote changes to update the local state and UI. + controller 既订阅本地变更以更新 Firestore,也订阅远程变更以更新本地状态和 UI。 + * The fields `_areaOneRef` and `_areaTwoRef` are Firebase document references. They describe where the data for each area resides, @@ -378,15 +461,23 @@ Notice the following features of this code: The Firestore API lets us subscribe to these references with `.snapshots()`, and write to them with `.set()`. + 字段 `_areaOneRef` 和 `_areaTwoRef` 是 Firebase 文档引用。它们描述每个区域数据的存放位置,以及如何在本地 Dart 对象(`List`)与远程 JSON 对象(`Map`)之间转换。Firestore API 允许我们通过 `.snapshots()` 订阅这些引用,并通过 `.set()` 写入。 + ## 5. Use the Firestore controller +## 5. 使用 Firestore controller + 1. Open the file responsible for starting the play session: `lib/play_session/play_session_screen.dart` in the case of the `card` template. You instantiate the Firestore controller from this file. + 打开负责开始对局会话的文件:对于 `card` 模板,即 `lib/play_session/play_session_screen.dart`。你将在此文件中实例化 Firestore controller。 + 2. Import Firebase and the controller: + 导入 Firebase 和 controller: + ```dart import 'package:cloud_firestore/cloud_firestore.dart'; @@ -396,6 +487,8 @@ Notice the following features of this code: 3. Add a nullable field to the `_PlaySessionScreenState` class to contain a controller instance: + 在 `_PlaySessionScreenState` 类中添加可空字段以保存 controller 实例: + ```dart FirestoreController? _firestoreController; @@ -407,6 +500,8 @@ Notice the following features of this code: You added the `FirebaseFirestore` instance to `main.dart` in the *Initialize Firestore* step. + 在同一类的 `initState()` 方法中,添加尝试读取 FirebaseFirestore 实例并在成功时构建 controller 的代码。你在*初始化 Firestore* 步骤中已将 `FirebaseFirestore` 实例添加到 `main.dart`。 + ```dart final firestore = context.read(); @@ -426,6 +521,8 @@ Notice the following features of this code: 5. Dispose of the controller using the `dispose()` method of the same class. + 在同一类的 `dispose()` 方法中释放 controller。 + ```dart _firestoreController?.dispose(); @@ -433,12 +530,18 @@ Notice the following features of this code: ## 6. Test the game +## 6. 测试游戏 + 1. Run the game on two separate devices or in 2 different windows on the same device. + 在两台独立设备上运行游戏,或在同一设备的 2 个不同窗口中运行。 + 2. Watch how adding a card to an area on one device makes it appear on the other one. + 观察在一台设备的某个区域添加卡牌后,它如何出现在另一台设备上。 + {% comment %} TBA: GIF of multiplayer working {% endcomment %} @@ -446,38 +549,60 @@ Notice the following features of this code: 3. Open the [Firebase web console][] and navigate to your project's Firestore Database. + 打开 [Firebase 网页控制台][Firebase web console],导航到你项目的 Firestore Database。 + 4. Watch how it updates the data in real time. You can even edit the data in the console and see all running clients update. + 观察它如何实时更新数据。你甚至可以在控制台中编辑数据,并看到所有运行中的客户端随之更新。 + ![Screenshot of the Firebase Firestore data view](/assets/images/docs/cookbook/multiplayer-firebase-data.png) [Firebase web console]: https://console.firebase.google.com/ ### Troubleshooting +### 故障排除 + The most common issues you might encounter when testing Firebase integration include the following: +测试 Firebase 集成时可能遇到的常见问题包括: + * **The game crashes when trying to reach Firebase.** + + **游戏在尝试连接 Firebase 时崩溃。** + * Firebase integration hasn't been properly set up. Revisit *Step 2* and make sure to run `flutterfire configure` as part of that step. + Firebase 集成未正确配置。请重新查看*步骤 2*,并确保在该步骤中运行 `flutterfire configure`。 + * **The game doesn't communicate with Firebase on macOS.** + + **游戏在 macOS 上无法与 Firebase 通信。** + * By default, macOS apps don't have internet access. Enable [internet entitlement][] first. + 默认情况下,macOS 应用没有网络访问权限。请先启用 [网络权限][internet entitlement]。 + [internet entitlement]: /data-and-backend/networking#macos ## 7. Next steps +## 7. 后续步骤 + At this point, the game has near-instant and dependable synchronization of state across clients. It lacks actual game rules: what cards can be played when, and with what results. This depends on the game itself and is left to you to try. +此时,游戏已在各客户端之间实现近乎即时且可靠的状态同步。它尚缺少实际游戏规则:何时可以打出哪些牌以及结果如何。这取决于游戏本身,留给你自行尝试。 + ![An illustration of two mobile phones and a two-way arrow between them](/assets/images/docs/cookbook/multiplayer-two-mobiles.jpg){:.site-illustration} At this point, the shared state of the match only includes @@ -488,6 +613,8 @@ If you're unsure where to start, follow [a Firestore codelab or two][] to familiarize yourself with the API. +此时,对局的共享状态仅包含两个游戏区域及其中的卡牌。你也可以将其他数据保存到 `_matchRef`,例如玩家是谁以及轮到谁。如果不确定从何入手,可跟随 [一两个 Firestore codelab][a Firestore codelab or two] 熟悉 API。 + At first, a single match should suffice for testing your multiplayer game with colleagues and friends. As you approach the release date, @@ -498,6 +625,8 @@ and the Firestore database structure can handle multiple matches. Instead of a single `match_1`, you can populate the matches collection with as many records as needed. +起初,单个对局足以与同事和朋友测试多人游戏。临近发布时,请考虑身份验证和匹配。好在 Firebase 提供 [内置的用户身份验证方式][built-in way to authenticate users],Firestore 数据库结构也能处理多个对局。除了单个 `match_1`,你可以在 matches 集合中填充所需数量的记录。 + ![Screenshot of the Firebase Firestore data view with additional matches](/assets/images/docs/cookbook/multiplayer-firebase-match.png) An online match can start in a "waiting" state, @@ -510,5 +639,7 @@ The basics remain the same: a large collection of documents, each representing one active or potential match. +在线对局可以以「等待」状态开始,仅首位玩家在场。其他玩家可在某种大厅中看到「等待」中的对局。足够玩家加入后,对局变为「活跃」。具体实现再次取决于你想要的在线体验类型。基本原理不变:一个大型文档集合,每个文档代表一个活跃或潜在的对局。 + [a Firestore codelab or two]: {{site.codelabs}}/?product=flutter&text=firestore [built-in way to authenticate users]: {{site.firebase}}/docs/auth/flutter/start diff --git a/sites/docs/src/content/cookbook/games/index.md b/sites/docs/src/content/cookbook/games/index.md index 397009074f..4a7e4d0888 100644 --- a/sites/docs/src/content/cookbook/games/index.md +++ b/sites/docs/src/content/cookbook/games/index.md @@ -1,6 +1,10 @@ --- -title: Flutter games cookbook -shortTitle: Games -description: A catalog of recipes to help you build games with Flutter. +# title: Flutter games cookbook +title: Flutter 游戏食谱 +# shortTitle: Games +shortTitle: 游戏 +# description: A catalog of recipes to help you build games with Flutter. +description: 帮助你使用 Flutter 构建游戏的食谱目录。 layout: toc +ai-translated: true --- diff --git a/sites/docs/src/content/cookbook/lists/index.md b/sites/docs/src/content/cookbook/lists/index.md index 5879064018..dc65e33048 100644 --- a/sites/docs/src/content/cookbook/lists/index.md +++ b/sites/docs/src/content/cookbook/lists/index.md @@ -1,6 +1,10 @@ --- -title: Flutter lists cookbook -shortTitle: Lists -description: A catalog of recipes for handling lists in your Flutter app. +# title: Flutter lists cookbook +title: Flutter 列表食谱 +# shortTitle: Lists +shortTitle: 列表 +# description: A catalog of recipes for handling lists in your Flutter app. +description: 在 Flutter 应用中处理列表的食谱目录。 layout: toc +ai-translated: true --- diff --git a/sites/docs/src/content/cookbook/lists/long-lists.md b/sites/docs/src/content/cookbook/lists/long-lists.md index ebbb9a8e56..4175ec09ef 100644 --- a/sites/docs/src/content/cookbook/lists/long-lists.md +++ b/sites/docs/src/content/cookbook/lists/long-lists.md @@ -5,6 +5,7 @@ title: 长列表的处理 description: 使用 ListView.builder 实现一个长或无限的列表。 tags: cookbook, 实用教程, 列表相关 keywords: 长列表,进阶,数据源 +ai-translated: true --- @@ -48,7 +49,7 @@ List.generate(10000, (i) => 'Item $i'), ## 2. Convert the data source into widgets -## 2. 将数据源渲染成组件 +## 2. 将数据源渲染成 widget To display the list of strings, render each String as a widget using `ListView.builder()`. diff --git a/sites/docs/src/content/cookbook/lists/spaced-items.md b/sites/docs/src/content/cookbook/lists/spaced-items.md index 596a7fdd21..14ac875b4a 100644 --- a/sites/docs/src/content/cookbook/lists/spaced-items.md +++ b/sites/docs/src/content/cookbook/lists/spaced-items.md @@ -1,6 +1,9 @@ --- -title: List with spaced items -description: How to create a list with spaced or expanded items +# title: List with spaced items +title: 等间距列表项 +# description: How to create a list with spaced or expanded items +description: 如何创建等间距或扩展的列表项 +ai-translated: true --- @@ -10,6 +13,10 @@ are spaced evenly, so that the items take up the visible space. For example, the four items in the following image are spaced evenly, with "Item 0" at the top, and "Item 3" at the bottom. +你也许想创建一个列表,让所有列表项均匀分布, +从而占满可见空间。例如,下图中的四个列表项均匀分布, +「Item 0」在顶部,「Item 3」在底部。 + ![Spaced items](/assets/images/docs/cookbook/spaced-items-1.png){:.site-mobile-screenshot} At the same time, you might want to allow users @@ -17,6 +24,9 @@ to scroll through the list when the list of items won't fit, maybe because a device is too small, a user resized a window, or the number of items exceeds the screen size. +同时,当列表项放不下时(例如设备太小、用户调整了窗口大小, +或列表项数量超出屏幕),你可能还希望允许用户滚动浏览列表。 + ![Scrollable items](/assets/images/docs/cookbook/spaced-items-2.png){:.site-mobile-screenshot} Typically, you use [`Spacer`][] to tune the spacing between widgets, @@ -24,30 +34,63 @@ or [`Expanded`][] to expand a widget to fill the available space. However, these solutions are not possible inside scrollable widgets, because they need a finite height constraint. +通常你会用 [`Spacer`][] 调整 widget 之间的间距, +或用 [`Expanded`][] 让 widget 占满可用空间。 +但在可滚动 widget 内部无法使用这些方案, +因为它们需要有限的高度约束。 + This recipe demonstrates how to use [`LayoutBuilder`][] and [`ConstrainedBox`][] to space out list items evenly when there is enough space, and to allow users to scroll when there is not enough space, using the following steps: +本食谱演示如何用 [`LayoutBuilder`][] 和 [`ConstrainedBox`][], +在空间足够时均匀分布列表项,在空间不足时允许用户滚动, +步骤如下: + 1. Add a [`LayoutBuilder`][] with a [`SingleChildScrollView`][]. + + 添加带 [`SingleChildScrollView`][] 的 [`LayoutBuilder`][]。 + 2. Add a [`ConstrainedBox`][] inside the [`SingleChildScrollView`][]. + + 在 [`SingleChildScrollView`][] 内添加 [`ConstrainedBox`][]。 + 3. Create a [`Column`][] with spaced items. + + 创建带等间距列表项的 [`Column`][]。 ## 1. Add a `LayoutBuilder` with a `SingleChildScrollView` +## 1. 添加带 `SingleChildScrollView` 的 `LayoutBuilder` + Start by creating a [`LayoutBuilder`][]. You need to provide a `builder` callback function with two parameters: +首先创建 [`LayoutBuilder`][]。你需要提供一个带两个参数的 `builder` 回调函数: + 1. The [`BuildContext`][] provided by the [`LayoutBuilder`][]. + + [`LayoutBuilder`][] 提供的 [`BuildContext`][]。 + 2. The [`BoxConstraints`][] of the parent widget. + + 父 widget 的 [`BoxConstraints`][]。 In this recipe, you won't be using the [`BuildContext`][], but you will need the [`BoxConstraints`][] in the next step. +在本食谱中你不会用到 [`BuildContext`][], +但下一步会用到 [`BoxConstraints`][]。 + Inside the `builder` function, return a [`SingleChildScrollView`][]. This widget ensures that the child widget can be scrolled, even when the parent container is too small. +在 `builder` 函数中返回 [`SingleChildScrollView`][]。 +该 widget 确保子 widget 可滚动, +即使父容器太小也是如此。 + ```dart LayoutBuilder( @@ -59,19 +102,30 @@ LayoutBuilder( ## 2. Add a `ConstrainedBox` inside the `SingleChildScrollView` +## 2. 在 `SingleChildScrollView` 内添加 `ConstrainedBox` + In this step, add a [`ConstrainedBox`][] as the child of the [`SingleChildScrollView`][]. +在本步中,将 [`ConstrainedBox`][] 作为 [`SingleChildScrollView`][] 的子 widget 添加。 + The [`ConstrainedBox`][] widget imposes additional constraints to its child. +[`ConstrainedBox`][] widget 会为其子 widget 施加额外约束。 + Configure the constraint by setting the `minHeight` parameter to be the `maxHeight` of the [`LayoutBuilder`][] constraints. +将 `minHeight` 参数设为 [`LayoutBuilder`][] 约束的 `maxHeight` 来配置约束。 + This ensures that the child widget is constrained to have a minimum height equal to the available space provided by the [`LayoutBuilder`][] constraints, namely the maximum height of the [`BoxConstraints`][]. +这确保子 widget 的最小高度等于 [`LayoutBuilder`][] 约束提供的可用空间, +即 [`BoxConstraints`][] 的最大高度。 + ```dart LayoutBuilder( @@ -91,13 +145,22 @@ because you need to allow the child to be larger than the [`LayoutBuilder`][] size, in case the items don't fit the screen. +但不要设置 `maxHeight` 参数, +因为当列表项放不下屏幕时,你需要允许子 widget 大于 [`LayoutBuilder`][] 的尺寸。 + ## 3. Create a `Column` with spaced items +## 3. 创建带等间距列表项的 `Column` + Finally, add a [`Column`][] as the child of the [`ConstrainedBox`][]. +最后,将 [`Column`][] 作为 [`ConstrainedBox`][] 的子 widget 添加。 + To space the items evenly, set the `mainAxisAlignment` to `MainAxisAlignment.spaceBetween`. +要均匀分布列表项,将 `mainAxisAlignment` 设为 `MainAxisAlignment.spaceBetween`。 + ```dart LayoutBuilder( @@ -123,10 +186,16 @@ Alternatively, you can use the [`Spacer`][] widget to tune the spacing between the items, or the [`Expanded`][] widget, if you want one widget to take more space than others. +或者,你可以用 [`Spacer`][] widget 调整列表项之间的间距, +或用 [`Expanded`][] widget 让某个 widget 占用比其他项更多的空间。 + For that, you have to wrap the [`Column`] with an [`IntrinsicHeight`][] widget, which forces the [`Column`][] widget to size itself to a minimum height, instead of expanding infinitely. +为此,你需要用 [`IntrinsicHeight`][] widget 包裹 [`Column`], +它会强制 [`Column`][] 按最小高度确定自身尺寸,而不是无限扩展。 + ```dart LayoutBuilder( @@ -154,15 +223,24 @@ LayoutBuilder( Play around with different devices, resizing the app, or resizing the browser window, and see how the item list adapts to the available space. + +在不同设备上试试、调整应用大小或调整浏览器窗口大小, +观察列表项如何适应可用空间。 ::: ## Interactive example +## 交互式样例 + This example shows a list of items that are spaced evenly within a column. The list can be scrolled up and down when the items don't fit the screen. The number of items is defined by the variable `items`, change this value to see what happens when the items won't fit the screen. +本示例展示在 `Column` 内均匀分布的列表项。 +当列表项放不下屏幕时,列表可以上下滚动。 +列表项数量由变量 `items` 定义,修改该值可观察列表项放不下屏幕时的表现。 + ```dartpad title="Flutter Spaced Items hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; diff --git a/sites/docs/src/content/cookbook/navigation/hero-animations.md b/sites/docs/src/content/cookbook/navigation/hero-animations.md index 9f427d1ddb..3a65b78ff0 100644 --- a/sites/docs/src/content/cookbook/navigation/hero-animations.md +++ b/sites/docs/src/content/cookbook/navigation/hero-animations.md @@ -5,6 +5,7 @@ title: 跨页面切换的动效 Widget (Hero animations) description: 如何让一个 widget 跨页面进行动画。 tags: cookbook, 实用教程, 导航 keywords: 页面切换,动画效果 +ai-translated: true --- @@ -15,13 +16,13 @@ widget from one screen to the next. This creates a visual anchor connecting the two screens. 在页面跳转过程中给用户加以引导是非常有用的。 -实现引导的一种通用做法是在页面切换时为某个组件加上转场动画, +实现引导的一种通用做法是在页面切换时为某个 widget 加上转场动画, 从而在两个页面间建立视觉上的锚定关联。 Use the [`Hero`][] widget to animate a widget from one screen to the next. -在 Flutter 中,可以通过 [`Hero`][] widget 实现页面切换时组件的转场动画。 +在 Flutter 中,可以通过 [`Hero`][] widget 实现页面切换时 widget 的转场动画。 This recipe uses the following steps: @@ -33,11 +34,11 @@ This recipe uses the following steps: 2. Add a `Hero` widget to the first screen. - 在第一个页面中加入 `Hero` 组件 + 在第一个页面中加入 `Hero` widget 3. Add a `Hero` widget to the second screen. - 在第二个页面中加入 `Hero` 组件 + 在第二个页面中加入 `Hero` widget ## 1. Create two screens showing the same image @@ -112,22 +113,22 @@ class DetailScreen extends StatelessWidget { ## 2. Add a `Hero` widget to the first screen -## 2. 在第一个页面中加入 `Hero` 组件 +## 2. 在第一个页面中加入 `Hero` widget To connect the two screens together with an animation, wrap the `Image` widget on both screens in a `Hero` widget. The `Hero` widget requires two arguments: 为了通过动画在两个页面间建立联系, -需要把每个页面的 `Image` 组件都包裹进 `Hero` 组件里面。 -`Hero` 组件有两个参数: +需要把每个页面的 `Image` widget 都包裹进 `Hero` widget 里面。 +`Hero` widget 有两个参数: `tag`
An object that identifies the `Hero`. It must be the same on both screens. `tag` -
作为 `Hero` 组件的标识, +
作为 `Hero` widget 的标识, 在这两个页面中必须相同。 `child` @@ -149,20 +150,20 @@ Hero( ## 3. Add a `Hero` widget to the second screen -## 3. 在第二个页面中加入 `Hero` 组件 +## 3. 在第二个页面中加入 `Hero` widget To complete the connection with the first screen, wrap the `Image` on the second screen with a `Hero` widget that has the same `tag` as the `Hero` in the first screen. 为了完善与第一个页面的关联, -同样需要把第二个页面中的 `Image` 组件包裹进 `Hero` 组件里面。 +同样需要把第二个页面中的 `Image` widget 包裹进 `Hero` widget 里面。 它的 `tag` 也必须和第一个页面相同。 After applying the `Hero` widget to the second screen, the animation between screens just works. -当 `Hero` 组件被应用到第二个页面后,页面的转场动画就生效了。 +当 `Hero` widget 被应用到第二个页面后,页面的转场动画就生效了。 {% comment %} RegEx removes the first "child" property name and removed the trailing comma at the end @@ -183,7 +184,7 @@ repeating code. This example uses identical code for both widgets, for simplicity. 这份代码和第一个页面中的代码是相同的。 -实际上,可以创建一个可复用的组件来代替这些重复的代码。 +实际上,可以创建一个可复用的 widget 来代替这些重复的代码。 但是在这个示例中,重复的代码会更易于讲解和演示。 ::: diff --git a/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md b/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md index 041ba08f4c..92fcd2ee49 100644 --- a/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md +++ b/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md @@ -5,6 +5,7 @@ title: 给特定的 route 传参 description: 如何向命名路由传参。 tags: cookbook, 实用教程, 路由 keywords: 参数传递,读取参数 +ai-translated: true --- @@ -16,7 +17,7 @@ In some cases, you might also need to pass arguments to a named route. For example, you might wish to navigate to the `/user` route and pass information about the user to that route. -[`Navigator`][] 组件支持通过使用通用标识符从应用程序的任何地方导航到特定路由。 +[`Navigator`][] widget 支持通过使用通用标识符从应用程序的任何地方导航到特定路由。 在某些情况下,你可能还希望能够传递参数给特定路由。 例如,你希望导航到 `/user` 路由并携带上用户信息。 @@ -63,15 +64,15 @@ and `onGenerateRoute()` using the following steps: 2. Create a widget that extracts the arguments. - 创建组件来获取参数 + 创建 widget 来获取参数 3. Register the widget in the `routes` table. - 把组件注册到路由表中 + 把 widget 注册到路由表中 4. Navigate to the widget. - 导航到组件 + 导航到 widget ## 1. Define the arguments you need to pass @@ -102,7 +103,7 @@ class ScreenArguments { ## 2. Create a widget that extracts the arguments -## 2. 创建组件来获取参数 +## 2. 创建 widget 来获取参数 Next, create a widget that extracts and displays the `title` and `message` from the `ScreenArguments`. @@ -110,7 +111,7 @@ To access the `ScreenArguments`, use the [`ModalRoute.of()`][] method. This method returns the current route with the arguments. -接着,创建组件,从 `ScreenArguments` 提取 `title` 和 `message` 参数并展示。 +接着,创建 widget,从 `ScreenArguments` 提取 `title` 和 `message` 参数并展示。 为了访问 `ScreenArguments`,可以使用 [`ModalRoute.of()`][] 方法。 这个方法返回的是当前路由及其携带的参数。 @@ -139,7 +140,7 @@ class ExtractArgumentsScreen extends StatelessWidget { ## 3. Register the widget in the `routes` table -## 3. 把组件注册到路由表中 +## 3. 把 widget 注册到路由表中 Next, add an entry to the `routes` provided to the `MaterialApp` widget. The `routes` define which widget should be created based on the name of the route. @@ -163,7 +164,7 @@ MaterialApp( ## 4. Navigate to the widget -## 4. 导航到组件 +## 4. 导航到 widget Finally, navigate to the `ExtractArgumentsScreen` when a user taps a button using [`Navigator.pushNamed()`][]. @@ -207,8 +208,8 @@ Instead of extracting the arguments directly inside the widget, you can also extract the arguments inside an [`onGenerateRoute()`][] function and pass them to a widget. -除了直接从组件里提取参数,你也可以通过 [`onGenerateRoute()`][] 函数提取参数, -然后把参数传递给组件。 +除了直接从 widget 里提取参数,你也可以通过 [`onGenerateRoute()`][] 函数提取参数, +然后把参数传递给 widget。 The `onGenerateRoute()` function creates the correct route based on the given [`RouteSettings`][]. diff --git a/sites/docs/src/content/cookbook/navigation/set-up-app-links.md b/sites/docs/src/content/cookbook/navigation/set-up-app-links.md index 964c70617e..683d34fa35 100644 --- a/sites/docs/src/content/cookbook/navigation/set-up-app-links.md +++ b/sites/docs/src/content/cookbook/navigation/set-up-app-links.md @@ -1,33 +1,59 @@ --- -title: Set up app links for Android +# title: Set up app links for Android +title: 为 Android 配置应用链接 +# description: >- +# Learn how to set up app links for an +# Android application built with Flutter. description: >- - Learn how to set up app links for an - Android application built with Flutter. + 了解如何为使用 Flutter 构建的 Android 应用配置应用链接。 +ai-translated: true --- Deep linking is a mechanism for launching an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen. +深度链接是一种通过 URI 启动应用的机制。 +该 URI 包含 scheme、host 和 path, +可将应用打开到特定屏幕。 + An _app link_ is a type of deep link that uses `http` or `https` and is exclusive to Android devices. +_应用链接_ 是一种使用 `http` 或 `https` 的深度链接,仅适用于 Android 设备。 + Setting up app links requires one to own a web domain. Otherwise, consider using [Firebase Hosting][] or [GitHub Pages][] as a temporary solution. +配置应用链接需要拥有自己的网站域名。 +否则,可考虑使用 [Firebase Hosting][] +或 [GitHub Pages][] 作为临时方案。 + Once you've set up your deep links, you can validate them. To learn more, see [Validate deep links][]. +配置好深度链接后,你可以验证它们。 +了解更多,请参阅 [验证深度链接][Validate deep links]。 + ## 1. Customize a Flutter application +## 1. 自定义 Flutter 应用 + Write a Flutter app that can handle an incoming URL. This example uses the [go_router][] package to handle the routing. The Flutter team maintains the `go_router` package. It provides a simple API to handle complex routing scenarios. +编写能处理传入 URL 的 Flutter 应用。 +本示例使用 [go_router][] package 处理路由。 +Flutter 团队维护 `go_router` package。 +它提供简单的 API 来处理复杂的路由场景。 + 1. To create a new application, type `flutter create `: + 要创建新应用,输入 `flutter create `: + ```console $ flutter create deeplink_cookbook ``` @@ -35,9 +61,14 @@ It provides a simple API to handle complex routing scenarios. 2. To include `go_router` package in your app, add a dependency for `go_router` to the project: + 要在应用中包含 `go_router` package, + 向项目添加 `go_router` 依赖: + To add the `go_router` package as a dependency, run `flutter pub add`: + 要将 `go_router` package 添加为依赖,运行 `flutter pub add`: + ```console $ flutter pub add go_router ``` @@ -45,6 +76,8 @@ It provides a simple API to handle complex routing scenarios. 3. To handle the routing, create a `GoRouter` object in the `main.dart` file: + 要处理路由,在 `main.dart` 文件中创建 `GoRouter` 对象: + ```dart title="main.dart" import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -74,13 +107,25 @@ It provides a simple API to handle complex routing scenarios. ## 2. Modify AndroidManifest.xml +## 2. 修改 AndroidManifest.xml + 1. Open the Flutter project with VS Code or Android Studio. + + 使用 VS Code 或 Android Studio 打开 Flutter 项目。 + 2. Navigate to `android/app/src/main/AndroidManifest.xml` file. + + 导航到 `android/app/src/main/AndroidManifest.xml` 文件。 + 3. Add the following metadata tag and intent filter inside the `` tag with `.MainActivity`. + 在带有 `.MainActivity` 的 `` 标签内添加以下元数据标签和 intent filter。 + Replace `example.com` with your own web domain. + 将 `example.com` 替换为你自己的网站域名。 + ```xml @@ -96,6 +141,9 @@ It provides a simple API to handle complex routing scenarios. you need to manually opt in to deep linking by adding the following metadata tag to ``: + 如果你使用的 Flutter 版本早于 3.27, + 需要手动启用深度链接,向 `` 添加以下元数据标签: + ```xml ``` @@ -107,9 +155,16 @@ It provides a simple API to handle complex routing scenarios. Flutter's default deeplink handler will break these plugins. + 如果你使用第三方插件处理深度链接, + 例如 [app_links][], + Flutter 的默认深度链接处理器会破坏这些插件。 + To opt out of using Flutter's default deep link handler, add the following metadata tag to ``: + 要退出使用 Flutter 的默认深度链接处理器, + 向 `` 添加以下元数据标签: + ```xml ``` @@ -117,6 +172,8 @@ It provides a simple API to handle complex routing scenarios. ## 3. Hosting assetlinks.json file +## 3. 托管 assetlinks.json 文件 + Host an `assetlinks.json` file in using a web server with a domain that you own. This file tells the mobile browser which Android application to open instead @@ -125,29 +182,55 @@ get the package name of the Flutter app you created in the previous step and the sha256 fingerprint of the signing key you will be using to build the APK. +在你拥有的域名的 Web 服务器上托管 `assetlinks.json` 文件。 +该文件告诉移动浏览器应打开哪个 Android 应用,而不是浏览器。 +要创建该文件,请获取上一步创建的 Flutter 应用的 package 名称, +以及用于构建 APK 的签名密钥的 sha256 指纹。 + ### Package name +### Package 名称 + Locate the package name in `AndroidManifest.xml`, the `package` property under `` tag. Package names are usually in the format of `com.example.*`. +在 `AndroidManifest.xml` 中查找 package 名称, +即 `` 标签下的 `package` 属性。 +Package 名称通常格式为 `com.example.*`。 + ### sha256 fingerprint +### sha256 指纹 + The process might differ depending on how the apk is signed. +具体流程可能因 APK 签名方式而异。 + #### Using google play app signing +#### 使用 Google Play 应用签名 + You can find the sha256 fingerprint directly from play developer console. Open your app in the play console, under **Release> Setup > App Integrity> App Signing tab**: +你可以直接从 Play 开发者控制台找到 sha256 指纹。 +在 Play 控制台中打开你的应用, +在 **Release > Setup > App Integrity > App Signing** 标签下: + Screenshot of sha256 fingerprint in play developer console #### Using local keystore +#### 使用本地 keystore + If you are storing the key locally, you can generate sha256 using the following command: +如果你在本地存储密钥, +可以使用以下命令生成 sha256: + ```console keytool -list -v -keystore ``` @@ -156,6 +239,8 @@ keytool -list -v -keystore The hosted file should look similar to this: +托管的文件应类似如下: + ```json [{ "relation": ["delegate_permission/common.handle_all_urls"], @@ -170,30 +255,50 @@ The hosted file should look similar to this: 1. Set the `package_name` value to your Android application ID. + 将 `package_name` 值设为你的 Android 应用 ID。 + 2. Set sha256_cert_fingerprints to the value you got from the previous step. + 将 sha256_cert_fingerprints 设为上一步获得的值。 + 3. Host the file at a URL that resembles the following: `/.well-known/assetlinks.json` + 在类似以下结构的 URL 托管该文件: + `/.well-known/assetlinks.json` + 4. Verify that your browser can access this file. + 验证浏览器能否访问该文件。 + :::note If you have multiple flavors, you can have many sha256_cert_fingerprint values in the sha256_cert_fingerprints field. Just add it to the sha256_cert_fingerprints list + +如果你有多个 flavor,可以在 sha256_cert_fingerprints 字段中包含多个 sha256_cert_fingerprint 值。 +只需将它们添加到 sha256_cert_fingerprints 列表中即可。 ::: ## Testing +## 测试 + You can use a real device or the Emulator to test an app link, but first make sure you have executed `flutter run` at least once on the devices. This ensures that the Flutter application is installed. +你可以使用真机或模拟器测试应用链接, +但请先在设备上至少执行一次 `flutter run`。 +这确保 Flutter 应用已安装。 + Emulator screenshot To test **only** the app setup, use the adb command: +要**仅**测试应用配置,使用 adb 命令: + ```console adb shell 'am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ @@ -206,26 +311,41 @@ This doesn't test whether the web files are hosted correctly, the command launches the app even if web files are not presented. + +这不会测试 Web 文件是否正确托管, +即使 Web 文件不存在,该命令也会启动应用。 ::: To test **both** web and app setup, you must click a link directly through web browser or another app. One way is to create a Google Doc, add the link, and tap on it. +要**同时**测试 Web 和应用配置,你必须通过 Web 浏览器或其他应用直接点击链接。 +一种方法是创建 Google 文档,添加链接并点击它。 + :::note If you are debugging locally (and not downloading the app from the Play Store), you might need to enable the toggle for **Supported web addresses** manually. + +如果你在本地调试(而不是从 Play 商店下载应用), +可能需要手动启用 **Supported web addresses** 开关。 ::: If everything is set up correctly, the Flutter application launches and displays the details screen: +如果一切配置正确,Flutter 应用会启动并显示详情屏幕: + Deeplinked Emulator screenshot ## Appendix +## 附录 + Source code: [deeplink_cookbook][] +源代码:[deeplink_cookbook 示例][deeplink_cookbook] + [deeplink_cookbook]: {{site.github}}/flutter/codelabs/tree/main/deeplink_cookbook [Firebase Hosting]: {{site.firebase}}/docs/hosting [go_router]: {{site.pub}}/packages/go_router diff --git a/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md b/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md index c742008495..31ac710318 100644 --- a/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md +++ b/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md @@ -1,34 +1,62 @@ --- -title: Set up universal links for iOS +# title: Set up universal links for iOS +title: 为 iOS 配置通用链接 +# description: >- +# Learn how to set up universal links for an +# iOS application built with Flutter. description: >- - Learn how to set up universal links for an - iOS application built with Flutter. + 了解如何为使用 Flutter 构建的 iOS 应用配置通用链接。 +ai-translated: true --- Deep linking allows an app user to launch an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen. +深度链接允许应用用户通过 URI 启动应用。 +该 URI 包含 scheme、host 和 path, +可将应用打开到特定屏幕。 + A _universal link_, a type of deep link exclusive to iOS devices, uses only the `http` or `https` protocols. +_通用链接_ 是一种仅适用于 iOS 设备的深度链接类型, +只使用 `http` 或 `https` 协议。 + To set up universal links, you need to own a web domain. As a temporary solution, consider using [Firebase Hosting][] or [GitHub Pages][]. +要配置通用链接,你需要拥有自己的网站域名。 +作为临时方案, +可考虑使用 [Firebase Hosting][] 或 [GitHub Pages][]。 + Once you've set up your deep links, you can validate them. To learn more, see [Validate deep links][]. +配置好深度链接后,你可以验证它们。 +了解更多,请参阅 [验证深度链接][Validate deep links]。 + ## Create or modify a Flutter app +## 创建或修改 Flutter 应用 + Write a Flutter app that can handle an incoming URL. +编写能处理传入 URL 的 Flutter 应用。 + This example uses the [go_router][] package to handle the routing. The Flutter team maintains the `go_router` package. It provides a simple API to handle complex routing scenarios. +本示例使用 [go_router][] package 处理路由。 +Flutter 团队维护 `go_router` package。 +它提供简单的 API 来处理复杂的路由场景。 + 1. To create a new application, type `flutter create `. + 要创建新应用,输入 `flutter create `。 + ```console $ flutter create deeplink_cookbook ``` @@ -36,12 +64,16 @@ It provides a simple API to handle complex routing scenarios. 2. To include the `go_router` package as a dependency, run `flutter pub add`: + 要将 `go_router` package 添加为依赖,运行 `flutter pub add`: + ```console $ flutter pub add go_router ``` 3. To handle the routing, create a `GoRouter` object in the `main.dart` file: + 要处理路由,在 `main.dart` 文件中创建 `GoRouter` 对象: + ```dart title="main.dart" import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -71,15 +103,24 @@ It provides a simple API to handle complex routing scenarios. ## Adjust iOS build settings +## 调整 iOS 构建设置 + 1. Launch Xcode. + 启动 Xcode。 + 1. Open the `ios/Runner.xcworkspace` file inside the Flutter project's `ios` folder. + 打开 Flutter 项目 `ios` 文件夹内的 `ios/Runner.xcworkspace` 文件。 + :::version-note If you use a Flutter version earlier than 3.27, you need to manually opt in to deep linking by adding the key and value pair `FlutterDeepLinkingEnabled` and `YES` to `info.Plist`. + + 如果你使用的 Flutter 版本早于 3.27, + 需要手动启用深度链接,向 `info.Plist` 添加键值对 `FlutterDeepLinkingEnabled` 和 `YES`。 ::: :::note @@ -88,15 +129,26 @@ It provides a simple API to handle complex routing scenarios. Flutter's default deeplink handler will break these plugins. + 如果你使用第三方插件处理深度链接, + 例如 [app_links][], + Flutter 的默认深度链接处理器会破坏这些插件。 + If you use a third-party plugin, add the key and value pair `FlutterDeepLinkingEnabled` and `NO` to `info.Plist`. + + 如果你使用第三方插件,向 `info.Plist` 添加键值对 `FlutterDeepLinkingEnabled` 和 `NO`。 ::: ### Add associated domains +### 添加关联域 + :::warning Personal development teams don't support the Associated Domains capability. To add associated domains, choose the IDE tab. + +个人开发团队不支持关联域(Associated Domains)功能。 +要添加关联域,请选择 IDE 标签页。 ::: @@ -104,17 +156,29 @@ capability. To add associated domains, choose the IDE tab. 1. Launch Xcode if necessary. + 如有必要,启动 Xcode。 + 1. Click the top-level **Runner**. + 点击顶层的 **Runner**。 + 1. In the Editor, click the **Runner** target. + 在编辑器中,点击 **Runner** target。 + 1. Click **Signing & Capabilities**. + 点击 **Signing & Capabilities**。 + 1. To add a new domain, click **+ Capability** under **Signing & Capabilities**. + 要添加新域,在 **Signing & Capabilities** 下点击 **+ Capability**。 + 1. Click **Associated Domains**. + 点击 **Associated Domains**。 + Xcode associated domains screenshot`. Replace `` with your own domain name. + 输入 `applinks:`。将 `` 替换为你自己的域名。 + Xcode add associated domains screenshot` tag. + 在 `` 标签内添加关联域。 + ```xml @@ -151,19 +223,32 @@ capability. To add associated domains, choose the IDE tab. 1. Save the `ios/Runner/Runner.entitlements` file. + 保存 `ios/Runner/Runner.entitlements` 文件。 + To check that the associated domains you created are available, perform the following steps: +要检查你创建的关联域是否可用,请执行以下步骤: + 1. Launch Xcode if necessary. + 如有必要,启动 Xcode。 + 1. Click the top-level **Runner**. + 点击顶层的 **Runner**。 + 1. In the Editor, click the **Runner** target. + 在编辑器中,点击 **Runner** target。 + 1. Click **Signing & Capabilities**. The domains should appear in the **Associated Domains** section. + 点击 **Signing & Capabilities**。 + 域应出现在 **Associated Domains** 部分。 + Xcode add associated domains screenshot.`. +Apple 将 `appID` 格式化为 `.`。 + * Locate the bundle ID in the Xcode project. + + 在 Xcode 项目中定位 bundle ID。 + * Locate the team ID in the [developer account][]. + 在 [开发者账户][developer account] 中定位 team ID。 + **For example:** Given a team ID of `S8QB4VV633` and a bundle ID of `com.example.deeplinkCookbook`, you would enter an `appID` entry of `S8QB4VV633.com.example.deeplinkCookbook`. +**例如:** 给定 team ID 为 `S8QB4VV633`, +bundle ID 为 `com.example.deeplinkCookbook`, +你应输入 `appID` 条目 +`S8QB4VV633.com.example.deeplinkCookbook`。 + ### Create and host `apple-app-site-association` JSON file +### 创建并托管 `apple-app-site-association` JSON 文件 + This file uses the JSON format. Don't include the `.json` file extension when you save this file. Per [Apple's documentation][apple-app-site-assoc], this file should resemble the following content: +该文件使用 JSON 格式。 +保存时不要包含 `.json` 文件扩展名。 +根据 [Apple 文档][apple-app-site-assoc], +该文件应类似以下内容: + ```json { "applinks": { @@ -232,38 +346,63 @@ this file should resemble the following content: 1. Set one value in the `appIDs` array to `.`. + 将 `appIDs` 数组中的一个值设为 `.`。 + 1. Set the `paths` array to `["*"]`. The `paths` array specifies the allowed universal links. Using the asterisk, `*` redirects every path to the Flutter app. If needed, change the `paths` array value to a setting more appropriate to your app. + 将 `paths` 数组设为 `["*"]`。 + `paths` 数组指定允许的通用链接。 + 使用星号 `*` 会将每个路径重定向到 Flutter 应用。 + 如有需要,将 `paths` 数组值改为更适合你应用的设置。 + 1. Host the file at a URL that resembles the following structure. + 在类似以下结构的 URL 托管该文件。 + `/.well-known/apple-app-site-association` 1. Verify that your browser can access this file. + 验证浏览器能否访问该文件。 + :::note If you have more than one scheme/flavor, you can add more than one `appID` into the `appIDs` field. + +如果你有多个 scheme/flavor,可以在 `appIDs` 字段中添加多个 `appID`。 ::: ## Test the universal link +## 测试通用链接 + Test a universal link using a physical iOS device or the Simulator. +使用实体 iOS 设备或模拟器测试通用链接。 + :::note It might take up to 24 hours before Apple's [Content Delivery Network][] (CDN) requests the `apple-app-site-association` (AASA) file from your web domain. Until the CDN requests the file, the universal link won't work. To bypass Apple's CDN, check out the [alternate mode section][]. + +Apple 的 [内容分发网络][Content Delivery Network] (CDN) 可能需要最多 24 小时 +才会从你的网站域名请求 `apple-app-site-association` (AASA) 文件。 +在 CDN 请求该文件之前,通用链接不会生效。 +要绕过 Apple 的 CDN,请参阅 [备用模式部分][alternate mode section]。 ::: 1. Before testing, install the Flutter app on the iOS device or Simulator, Use `flutter run` on the desired device. + 测试前,在 iOS 设备或模拟器上安装 Flutter 应用, + 在目标设备上使用 `flutter run`。 + Simulator screenshot/details ``` 1. If you test with a physical iOS device: + 如果使用实体 iOS 设备测试: + 1. Launch the **Note** app. + + 启动 **备忘录** 应用。 + 1. Type the URL in the **Note** app. + + 在 **备忘录** 应用中输入 URL。 + 1. Click the resulting link. + 点击生成的链接。 + If successful, the Flutter app launches and displays its details screen. + 如果成功,Flutter 应用会启动并显示其详情屏幕。 + Deeplinked Simulator screenshot -[Content Delivery Network]: https://en.wikipedia.org/wiki/Content_delivery_network - ## Find the source code +## 查找源代码 + You can find the source code for the [deeplink_cookbook][] recipe in the GitHub repo. +你可以在 GitHub 仓库中找到 [deeplink_cookbook 示例][deeplink_cookbook] 食谱的源代码。 + +[Content Delivery Network]: https://en.wikipedia.org/wiki/Content_delivery_network [apple-app-site-assoc]: {{site.apple-dev}}/documentation/xcode/supporting-associated-domains [alternate mode section]: {{site.apple-dev}}/documentation/bundleresources/entitlements/com_apple_developer_associated-domains?language=objc [deeplink_cookbook]: {{site.repo.organization}}/codelabs/tree/main/deeplink_cookbook diff --git a/sites/docs/src/content/cookbook/networking/delete-data.md b/sites/docs/src/content/cookbook/networking/delete-data.md index ec923ae319..9f039a3283 100644 --- a/sites/docs/src/content/cookbook/networking/delete-data.md +++ b/sites/docs/src/content/cookbook/networking/delete-data.md @@ -4,6 +4,7 @@ title: 删除网络数据 # description: How to use the http package to delete data on the internet. description: 如何使用 http 这个 package 来删除远程服务器的数据。 tags: cookbook, 实用教程, 网络请求 +ai-translated: true --- @@ -11,23 +12,42 @@ tags: cookbook, 实用教程, 网络请求 This recipe covers how to delete data over the internet using the `http` package. +本教程介绍如何使用 `http` package 在互联网上删除数据。 + This recipe uses the following steps: +本教程包含以下步骤: + 1. Add the `http` package. + + 添加 `http` package。 + 2. Delete data on the server. + + 在服务器上删除数据。 + 3. Update the screen. + 更新屏幕。 + ## 1. Add the `http` package +## 1. 添加 `http` package + To add the `http` package as a dependency, run `flutter pub add`: +要将 `http` package 添加为依赖, +请运行 `flutter pub add`: + ```console $ flutter pub add http ``` Import the `http` package. +导入 `http` package。 + ```dart import 'package:http/http.dart' as http; @@ -37,12 +57,20 @@ import 'package:http/http.dart' as http; ## 2. Delete data on the server +## 2. 在服务器上删除数据 + This recipe covers how to delete an album from the [JSONPlaceholder][] using the `http.delete()` method. Note that this requires the `id` of the album that you want to delete. For this example, use something you already know, for example `id = 1`. +本教程介绍如何使用 `http.delete()` 方法 +从 [JSONPlaceholder][] 删除相册。 +请注意,这需要你要删除的相册的 `id`。 +在本示例中, +使用你已知的值,例如 `id = 1`。 + ```dart Future deleteAlbum(String id) async { @@ -59,16 +87,30 @@ Future deleteAlbum(String id) async { The `http.delete()` method returns a `Future` that contains a `Response`. +`http.delete()` 方法会返回一个包含 `Response` 的 `Future`。 + * [`Future`][] is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future. + + [`Future`][] 是 Dart 中用于处理异步操作的核心类。 + Future 对象表示将来某个时刻可用的潜在值或错误。 + * The `http.Response` class contains the data received from a successful http call. + + `http.Response` 类包含成功的 http 调用所接收到的数据。 + * The `deleteAlbum()` method takes an `id` argument that is needed to identify the data to be deleted from the server. + `deleteAlbum()` 方法接受 `id` 参数, + 用于标识要从服务器删除的数据。 + ## 3. Update the screen +## 3. 更新屏幕 + In order to check whether the data has been deleted or not, first fetch the data from the [JSONPlaceholder][] using the `http.get()` method, and display it in the screen. @@ -76,6 +118,13 @@ using the `http.get()` method, and display it in the screen. You should now have a **Delete Data** button that, when pressed, calls the `deleteAlbum()` method. +为了检查数据是否已被删除, +请首先使用 `http.get()` 方法从 [JSONPlaceholder][] 获取数据, +并在屏幕上显示。 +(完整示例请参阅 [Fetch Data][] 教程。) +此时你应该有一个 **Delete Data** 按钮, +按下时会调用 `deleteAlbum()` 方法。 + ```dart Column( @@ -101,11 +150,23 @@ you are passing is the id of the data that you retrieved from the internet. This means you are going to delete the same data that you fetched from the internet. +现在,当你点击 ***Delete Data*** 按钮时, +会调用 `deleteAlbum()` 方法,而你传入的 id +正是你从互联网获取的数据的 id。这意味着你将删除 +与从互联网获取的相同的数据。 + ### Returning a response from the deleteAlbum() method + +### 从 deleteAlbum() 方法返回响应 + Once the delete request has been made, you can return a response from the `deleteAlbum()` method to notify our screen that the data has been deleted. +发出删除请求后, +你可以从 `deleteAlbum()` 方法返回响应, +以通知屏幕数据已被删除。 + ```dart Future deleteAlbum(String id) async { @@ -138,11 +199,22 @@ the `Album.fromJson()` method creates an instance of the `Album` object with a default value (`null` in our case). This behavior can be altered in any way you wish. +`FutureBuilder()` 在收到响应后会重新构建。 +由于请求成功时响应体中不会有任何数据, +`Album.fromJson()` 方法会使用默认值(在我们的例子中是 `null`) +创建 `Album` 对象实例。 +你可以按任意方式修改这一行为。 + That's all! Now you've got a function that deletes the data from the internet. +就是这样! +现在你就拥有了一个可以从互联网删除数据的函数。 + ## Complete example +## 完整样例 + ```dart import 'dart:async'; diff --git a/sites/docs/src/content/cookbook/networking/send-data.md b/sites/docs/src/content/cookbook/networking/send-data.md index 2809237a01..95d5284a63 100644 --- a/sites/docs/src/content/cookbook/networking/send-data.md +++ b/sites/docs/src/content/cookbook/networking/send-data.md @@ -5,6 +5,7 @@ title: 发送网络数据 description: 如何通过 http 包发送网络数据。 tags: cookbook, 实用教程, 网络请求 keywords: 网络数据 +ai-translated: true --- @@ -12,25 +13,51 @@ keywords: 网络数据 Sending data to the internet is necessary for most apps. The `http` package has got that covered, too. +对于大部分应用来说,向互联网发送数据都是必不可少的。 +`http` package 同样可以满足这一需求。 + This recipe uses the following steps: +本教程包含以下步骤: + 1. Add the `http` package. + + 添加 `http` package。 + 2. Send data to a server using the `http` package. + + 使用 `http` package 向服务器发送数据。 + 3. Convert the response into a custom Dart object. + + 将响应转换成一个自定义的 Dart 对象。 + 4. Get a `title` from user input. + + 从用户输入获取 `title`。 + 5. Display the response on screen. + 在屏幕上显示响应。 + ## 1. Add the `http` package +## 1. 添加 `http` package + To add the `http` package as a dependency, run `flutter pub add`: +要将 `http` package 添加为依赖, +请运行 `flutter pub add`: + ```console $ flutter pub add http ``` Import the `http` package. +导入 `http` package。 + ```dart import 'package:http/http.dart' as http; @@ -40,13 +67,20 @@ import 'package:http/http.dart' as http; ## 2. Sending data to server +## 2. 向服务器发送数据 + This recipe covers how to create an `Album` by sending an album title to the [JSONPlaceholder][] using the [`http.post()`][] method. +本教程介绍如何使用 [`http.post()`][] 方法 +将相册标题发送到 [JSONPlaceholder][] 以创建 `Album`。 + Import `dart:convert` for access to `jsonEncode` to encode the data: +导入 `dart:convert` 以使用 `jsonEncode` 编码数据: + ```dart import 'dart:convert'; @@ -54,6 +88,8 @@ import 'dart:convert'; Use the `http.post()` method to send the encoded data: +使用 `http.post()` 方法发送编码后的数据: + ```dart Future createAlbum(String title) { @@ -69,32 +105,59 @@ Future createAlbum(String title) { The `http.post()` method returns a `Future` that contains a `Response`. +`http.post()` 方法会返回一个包含 `Response` 的 `Future`。 + * [`Future`][] is a core Dart class for working with asynchronous operations. A Future object represents a potential value or error that will be available at some time in the future. + + [`Future`][] 是 Dart 中用于处理异步操作的核心类。 + Future 对象表示将来某个时刻可用的潜在值或错误。 + * The `http.Response` class contains the data received from a successful http call. + + `http.Response` 类包含成功的 http 调用所接收到的数据。 + * The `createAlbum()` method takes an argument `title` that is sent to the server to create an `Album`. + `createAlbum()` 方法接受参数 `title`, + 该参数会发送到服务器以创建 `Album`。 + ## 3. Convert the `http.Response` to a custom Dart object +## 3. 将 `http.Response` 转换成自定义的 Dart 对象 + While it's easy to make a network request, working with a raw `Future` isn't very convenient. To make your life easier, convert the `http.Response` into a Dart object. +虽然发起网络请求很容易, +但直接处理原始的 `Future` 并不方便。 +为了让后续工作更轻松, +请将 `http.Response` 转换成 Dart 对象。 + ### Create an Album class +### 创建一个 `Album` 类 + First, create an `Album` class that contains the data from the network request. It includes a factory constructor that creates an `Album` from JSON. +首先,创建一个包含网络请求数据的 `Album` 类。 +它包含一个工厂构造器,用于从 JSON 创建 `Album`。 + Converting JSON with [pattern matching][] is only one option. For more information, see the full article on [JSON and serialization][]. +使用 [pattern matching][] 转换 JSON 只是其中一种方式。 +想了解更多,请查看完整文章:[JSON and serialization][]。 + ```dart class Album { @@ -114,14 +177,26 @@ class Album { ### Convert the `http.Response` to an `Album` +### 将 `http.Response` 转换成 `Album` + Use the following steps to update the `createAlbum()` function to return a `Future`: +按以下步骤更新 `createAlbum()` 函数, +使其返回 `Future`: + 1. Convert the response body into a JSON `Map` with the `dart:convert` package. + + 使用 `dart:convert` package 将响应体转换成 JSON `Map`。 + 2. If the server returns a `CREATED` response with a status code of 201, then convert the JSON `Map` into an `Album` using the `fromJson()` factory method. + + 如果服务器返回状态码为 201 的 `CREATED` 响应, + 则使用 `fromJson()` 工厂方法将 JSON `Map` 转换成 `Album`。 + 3. If the server doesn't return a `CREATED` response with a status code of 201, then throw an exception. (Even in the case of a "404 Not Found" server response, @@ -129,6 +204,10 @@ function to return a `Future`: This is important when examining the data in `snapshot`, as shown below.) + 如果服务器没有返回状态码为 201 的 `CREATED` 响应,则抛出异常。 + (即使是「404 Not Found」的服务器响应,也要抛出异常。不要返回 `null`。 + 在检查如下所示的 `snapshot` 中的数据时,这一点很重要。) + ```dart Future createAlbum(String title) async { @@ -155,16 +234,27 @@ Future createAlbum(String title) async { Hooray! Now you've got a function that sends the title to a server to create an album. +太棒了!现在你就拥有了一个将标题发送到服务器以创建相册的函数。 + ## 4. Get a title from user input +## 4. 从用户输入获取标题 + Next, create a `TextField` to enter a title and a `ElevatedButton` to send data to server. Also define a `TextEditingController` to read the user input from a `TextField`. +接下来,创建一个用于输入标题的 `TextField` 和一个用于向服务器发送数据的 `ElevatedButton`。 +还要定义一个 `TextEditingController`, +用于从 `TextField` 读取用户输入。 + When the `ElevatedButton` is pressed, the `_futureAlbum` is set to the value returned by `createAlbum()` method. +当按下 `ElevatedButton` 时, +`_futureAlbum` 会被设置为 `createAlbum()` 方法返回的值。 + ```dart Column( @@ -191,20 +281,38 @@ which sends the data in the `TextField` to the server as a `POST` request. The Future, `_futureAlbum`, is used in the next step. +按下 **Create Data** 按钮后,发起网络请求, +以 `POST` 请求将 `TextField` 中的数据发送到服务器。 +下一步会使用 Future `_futureAlbum`。 + ## 5. Display the response on screen +## 5. 在屏幕上显示响应 + To display the data on screen, use the [`FutureBuilder`][] widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with asynchronous data sources. You must provide two parameters: +要在屏幕上显示数据,请使用 [`FutureBuilder`][] widget。 +`FutureBuilder` widget 随 Flutter 提供, +可让你轻松处理异步数据源。 +你必须提供两个参数: + 1. The `Future` you want to work with. In this case, the future returned from the `createAlbum()` function. + + 你想要处理的 `Future`。在本例中, + 即 `createAlbum()` 函数返回的 future。 + 2. A `builder` function that tells Flutter what to render, depending on the state of the `Future`: loading, success, or error. + 一个 `builder` 函数,根据 `Future` 的状态(loading、success 或 error) + 告诉 Flutter 渲染什么内容。 + Note that `snapshot.hasData` only returns `true` when the snapshot contains a non-null data value. This is why the `createAlbum()` function should throw an exception @@ -212,6 +320,13 @@ even in the case of a "404 Not Found" server response. If `createAlbum()` returns `null`, then `CircularProgressIndicator` displays indefinitely. +请注意,只有当快照包含非空数据值时, +`snapshot.hasData` 才会返回 `true`。 +因此,即使在「404 Not Found」的服务器响应情况下, +`createAlbum()` 函数也应抛出异常。 +如果 `createAlbum()` 返回 `null`, +则 `CircularProgressIndicator` 会无限期显示。 + ```dart FutureBuilder( @@ -230,6 +345,8 @@ FutureBuilder( ## Complete example +## 完整样例 + ```dart import 'dart:async'; diff --git a/sites/docs/src/content/cookbook/networking/update-data.md b/sites/docs/src/content/cookbook/networking/update-data.md index e1b8f513f0..52ef7a034d 100644 --- a/sites/docs/src/content/cookbook/networking/update-data.md +++ b/sites/docs/src/content/cookbook/networking/update-data.md @@ -4,6 +4,7 @@ title: 更新网络数据 # description: How to use the http package to update data over the internet. description: 如何使用 http 这个 package 来更新远程服务器的数据。 tags: cookbook, 实用教程, 网络请求 +ai-translated: true --- @@ -11,26 +12,55 @@ tags: cookbook, 实用教程, 网络请求 Updating data over the internet is necessary for most apps. The `http` package has got that covered! +对于大部分应用来说,在网络上更新数据都是必不可少的。 +`http` package 正好可以满足这一需求! + This recipe uses the following steps: +本教程包含以下步骤: + 1. Add the `http` package. + + 添加 `http` package。 + 2. Update data over the internet using the `http` package. + + 使用 `http` package 在网络上更新数据。 + 3. Convert the response into a custom Dart object. + + 将响应转换成一个自定义的 Dart 对象。 + 4. Get the data from the internet. + + 从互联网获取数据。 + 5. Update the existing `title` from user input. + + 根据用户输入更新现有的 `title`。 + 6. Update and display the response on screen. + 更新数据并在屏幕上显示响应。 + ## 1. Add the `http` package +## 1. 添加 `http` package + To add the `http` package as a dependency, run `flutter pub add`: +要将 `http` package 添加为依赖, +请运行 `flutter pub add`: + ```console $ flutter pub add http ``` Import the `http` package. +导入 `http` package。 + ```dart import 'package:http/http.dart' as http; @@ -40,9 +70,14 @@ import 'package:http/http.dart' as http; ## 2. Updating data over the internet using the `http` package +## 2. 使用 `http` package 在网络上更新数据 + This recipe covers how to update an album title to the [JSONPlaceholder][] using the [`http.put()`][] method. +本教程介绍如何使用 [`http.put()`][] 方法 +将相册标题更新到 [JSONPlaceholder][]。 + ```dart Future updateAlbum(String title) { @@ -58,31 +93,58 @@ Future updateAlbum(String title) { The `http.put()` method returns a `Future` that contains a `Response`. +`http.put()` 方法会返回一个包含 `Response` 的 `Future`。 + * [`Future`][] is a core Dart class for working with async operations. A `Future` object represents a potential value or error that will be available at some time in the future. + + [`Future`][] 是 Dart 中用于处理异步操作的核心类。 + `Future` 对象表示将来某个时刻可用的潜在值或错误。 + * The `http.Response` class contains the data received from a successful http call. + + `http.Response` 类包含成功的 http 调用所接收到的数据。 + * The `updateAlbum()` method takes an argument, `title`, which is sent to the server to update the `Album`. + `updateAlbum()` 方法接受参数 `title`, + 该参数会发送到服务器以更新 `Album`。 + ## 3. Convert the `http.Response` to a custom Dart object +## 3. 将 `http.Response` 转换成自定义的 Dart 对象 + While it's easy to make a network request, working with a raw `Future` isn't very convenient. To make your life easier, convert the `http.Response` into a Dart object. +虽然发起网络请求很容易, +但直接处理原始的 `Future` 并不方便。 +为了让后续工作更轻松, +请将 `http.Response` 转换成 Dart 对象。 + ### Create an Album class +### 创建一个 `Album` 类 + First, create an `Album` class that contains the data from the network request. It includes a factory constructor that creates an `Album` from JSON. +首先,创建一个包含网络请求数据的 `Album` 类。 +它包含一个工厂构造器,用于从 JSON 创建 `Album`。 + Converting JSON with [pattern matching][] is only one option. For more information, see the full article on [JSON and serialization][]. +使用 [pattern matching][] 转换 JSON 只是其中一种方式。 +想了解更多,请查看完整文章:[JSON and serialization][]。 + ```dart class Album { @@ -102,14 +164,26 @@ class Album { ### Convert the `http.Response` to an `Album` +### 将 `http.Response` 转换成 `Album` + Now, use the following steps to update the `updateAlbum()` function to return a `Future`: +现在,按以下步骤更新 `updateAlbum()` 函数, +使其返回 `Future`: + 1. Convert the response body into a JSON `Map` with the `dart:convert` package. + + 使用 `dart:convert` package 将响应体转换成 JSON `Map`。 + 2. If the server returns an `UPDATED` response with a status code of 200, then convert the JSON `Map` into an `Album` using the `fromJson()` factory method. + + 如果服务器返回状态码为 200 的 `UPDATED` 响应, + 则使用 `fromJson()` 工厂方法将 JSON `Map` 转换成 `Album`。 + 3. If the server doesn't return an `UPDATED` response with a status code of 200, then throw an exception. (Even in the case of a "404 Not Found" server response, @@ -117,6 +191,10 @@ function to return a `Future`: This is important when examining the data in `snapshot`, as shown below.) + 如果服务器没有返回状态码为 200 的 `UPDATED` 响应,则抛出异常。 + (即使是「404 Not Found」的服务器响应,也要抛出异常。不要返回 `null`。 + 在检查如下所示的 `snapshot` 中的数据时,这一点很重要。) + ```dart Future updateAlbum(String title) async { @@ -143,11 +221,19 @@ Future updateAlbum(String title) async { Hooray! Now you've got a function that updates the title of an album. +太棒了! +现在你就拥有了一个可以更新相册标题的函数。 + ### Get the data from the internet +### 从互联网获取数据 + Get the data from internet before you can update it. For a complete example, see the [Fetch data][] recipe. +在更新之前,需要先从互联网获取数据。 +完整示例请参阅 [Fetch data][] 教程。 + ```dart Future fetchAlbum() async { @@ -171,17 +257,29 @@ Ideally, you will use this method to set `_futureAlbum` during `initState` to fetch the data from the internet. +理想情况下,你会在 `initState` 中使用此方法设置 `_futureAlbum`, +以从互联网获取数据。 + ## 4. Update the existing title from user input +## 4. 根据用户输入更新现有标题 + Create a `TextField` to enter a title and a `ElevatedButton` to update the data on server. Also define a `TextEditingController` to read the user input from a `TextField`. +创建一个用于输入标题的 `TextField` 和一个用于在服务器上更新数据的 `ElevatedButton`。 +还要定义一个 `TextEditingController`, +用于从 `TextField` 读取用户输入。 + When the `ElevatedButton` is pressed, the `_futureAlbum` is set to the value returned by `updateAlbum()` method. +当按下 `ElevatedButton` 时, +`_futureAlbum` 会被设置为 `updateAlbum()` 方法返回的值。 + ```dart Column( @@ -210,20 +308,38 @@ On pressing the **Update Data** button, a network request sends the data in the `TextField` to the server as a `PUT` request. The `_futureAlbum` variable is used in the next step. +按下 **Update Data** 按钮后,网络请求会以 `PUT` 请求 +将 `TextField` 中的数据发送到服务器。 +下一步会使用 `_futureAlbum` 变量。 + ## 5. Display the response on screen +## 5. 在屏幕上显示响应 + To display the data on screen, use the [`FutureBuilder`][] widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with async data sources. You must provide two parameters: +要在屏幕上显示数据,请使用 [`FutureBuilder`][] widget。 +`FutureBuilder` widget 随 Flutter 提供, +可让你轻松处理异步数据源。 +你必须提供两个参数: + 1. The `Future` you want to work with. In this case, the future returned from the `updateAlbum()` function. + + 你想要处理的 `Future`。在本例中, + 即 `updateAlbum()` 函数返回的 future。 + 2. A `builder` function that tells Flutter what to render, depending on the state of the `Future`: loading, success, or error. + 一个 `builder` 函数,根据 `Future` 的状态(loading、success 或 error) + 告诉 Flutter 渲染什么内容。 + Note that `snapshot.hasData` only returns `true` when the snapshot contains a non-null data value. This is why the `updateAlbum` function should throw an exception @@ -231,6 +347,13 @@ even in the case of a "404 Not Found" server response. If `updateAlbum` returns `null` then `CircularProgressIndicator` will display indefinitely. +请注意,只有当快照包含非空数据值时, +`snapshot.hasData` 才会返回 `true`。 +因此,即使在「404 Not Found」的服务器响应情况下, +`updateAlbum` 函数也应抛出异常。 +如果 `updateAlbum` 返回 `null`, +则 `CircularProgressIndicator` 会无限期显示。 + ```dart FutureBuilder( @@ -249,6 +372,8 @@ FutureBuilder( ## Complete example +## 完整样例 + ```dart import 'dart:async'; diff --git a/sites/docs/src/content/cookbook/networking/web-sockets.md b/sites/docs/src/content/cookbook/networking/web-sockets.md index 480e14a3da..6b2c2380d9 100644 --- a/sites/docs/src/content/cookbook/networking/web-sockets.md +++ b/sites/docs/src/content/cookbook/networking/web-sockets.md @@ -5,6 +5,7 @@ title: 使用 WebSockets 进行通信 description: 如何建立 web socket 连接。 tags: cookbook, 实用教程, 网络请求 keywords: WebSockets +ai-translated: true --- @@ -91,8 +92,8 @@ In this example, use a [`StreamBuilder`][] widget to listen for new messages, and a [`Text`][] widget to display them. -在这个例子中,我们用 [`StreamBuilder`][] 组件来监听新消息, -并使用 [`Text`][] 组件来展示它们。 +在这个例子中,我们用 [`StreamBuilder`][] widget 来监听新消息, +并使用 [`Text`][] widget 来展示它们。 ```dart diff --git a/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md b/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md index fb3b6efabe..f1c348d229 100644 --- a/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md +++ b/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md @@ -1,7 +1,11 @@ --- -title: Add ads to your mobile Flutter app or game -shortTitle: Show ads -description: How to use the google_mobile_ads package to show ads in Flutter. +# title: Add ads to your mobile Flutter app or game +title: 为移动 Flutter 应用或游戏添加广告 +# shortTitle: Show ads +shortTitle: 展示广告 +# description: How to use the google_mobile_ads package to show ads in Flutter. +description: 如何使用 google_mobile_ads package 在 Flutter 中展示广告。 +ai-translated: true --- @@ -23,6 +27,9 @@ Many developers use advertising to monetize their mobile apps and games. This allows their app to be downloaded free of charge, which improves the app's popularity. +许多开发者通过广告为移动应用和游戏变现。 +这允许应用免费下载,从而提高应用的人气。 + ![An illustration of a smartphone showing an ad](/assets/images/docs/cookbook/ads-device.jpg){:.site-illustration} To add ads to your Flutter project, use @@ -32,36 +39,65 @@ This recipe demonstrates how to use the [`google_mobile_ads`]({{site.pub-pkg}}/google_mobile_ads) package to add a banner ad to your app or game. +要向 Flutter 项目添加广告,请使用 Google 的移动广告平台 +[AdMob](https://admob.google.com/home/)。 +本食谱演示如何使用 +[`google_mobile_ads`]({{site.pub-pkg}}/google_mobile_ads) +package 向应用或游戏添加横幅广告。 + :::note Apart from AdMob, the `google_mobile_ads` package also supports Ad Manager, a platform intended for large publishers. Integrating Ad Manager resembles integrating AdMob, but it won't be covered in this cookbook recipe. To use Ad Manager, follow the [Ad Manager documentation]({{site.developers}}/ad-manager/mobile-ads-sdk/flutter/quick-start). + +除了 AdMob,`google_mobile_ads` package 还支持面向大型发布商的 Ad Manager 平台。 +集成 Ad Manager 与集成 AdMob 类似,但本食谱不会涵盖。 +要使用 Ad Manager,请遵循 +[Ad Manager 文档]({{site.developers}}/ad-manager/mobile-ads-sdk/flutter/quick-start)。 ::: ## 1. Get AdMob App IDs +## 1. 获取 AdMob 应用 ID + 1. Go to [AdMob](https://admob.google.com/) and set up an account. This could take some time because you need to provide banking information, sign contracts, and so on. + 前往 [AdMob](https://admob.google.com/) 并设置账户。 + 这可能需要一些时间,因为你需要提供银行信息、签署合同等。 + 2. With the AdMob account ready, create two *Apps* in AdMob: one for Android and one for iOS. + AdMob 账户就绪后,在 AdMob 中创建两个*应用*:一个用于 Android,一个用于 iOS。 + 3. Open the **App settings** section. + 打开 **App settings**(应用设置)部分。 + 4. Get the AdMob *App IDs* for both the Android app and the iOS app. They resemble `ca-app-pub-1234567890123456~1234567890`. Note the tilde (`~`) between the two numbers. {% comment %} https://support.google.com/admob/answer/7356431 for future reference {% endcomment %} + 获取 Android 应用和 iOS 应用的 AdMob *应用 ID*。 + 它们类似 `ca-app-pub-1234567890123456~1234567890`。注意两个数字之间的 + 波浪号 (`~`)。 + {% comment %} https://support.google.com/admob/answer/7356431 for future reference {% endcomment %} + ![Screenshot from AdMob showing the location of the App ID](/assets/images/docs/cookbook/ads-app-id.png) ## 2. Platform-specific setup +## 2. 平台特定配置 + Update your Android and iOS configurations to include your App IDs. +更新 Android 和 iOS 配置以包含你的应用 ID。 + {% comment %} Content below is more or less a copypaste from devsite: https://developers.google.com/admob/flutter/quick-start#platform_specific_setup @@ -69,19 +105,33 @@ Update your Android and iOS configurations to include your App IDs. ### Android +### Android + Add your AdMob app ID to your Android app. +将 AdMob 应用 ID 添加到你的 Android 应用。 + 1. Open the app's `android/app/src/main/AndroidManifest.xml` file. + 打开应用的 `android/app/src/main/AndroidManifest.xml` 文件。 + 2. Add a new `` tag. + 添加新的 `` 标签。 + 3. Set the `android:name` element with a value of `com.google.android.gms.ads.APPLICATION_ID`. + 将 `android:name` 元素的值设为 + `com.google.android.gms.ads.APPLICATION_ID`。 + 4. Set the `android:value` element with the value to your own AdMob app ID that you got in the previous step. Include them in quotes as shown: + 将 `android:value` 元素设为上一步获得的 AdMob 应用 ID。 + 按所示用引号括起来: + ```xml @@ -97,15 +147,25 @@ Add your AdMob app ID to your Android app. ### iOS +### iOS + Add your AdMob app ID to your iOS app. +将 AdMob 应用 ID 添加到你的 iOS 应用。 + 1. Open your app's `ios/Runner/Info.plist` file. + 打开应用的 `ios/Runner/Info.plist` 文件。 + 2. Enclose `GADApplicationIdentifier` with a `key` tag. + 用 `key` 标签包裹 `GADApplicationIdentifier`。 + 3. Enclose your AdMob app ID with a `string` tag. You created this AdMob App ID in [step 1](#1-get-admob-app-ids). + 用 `string` 标签包裹你的 AdMob 应用 ID。该 AdMob 应用 ID 在[第 1 步](#1-get-admob-app-ids)中创建。 + ```xml GADApplicationIdentifier ca-app-pub-################~########## @@ -113,9 +173,13 @@ Add your AdMob app ID to your iOS app. ## 3. Add the `google_mobile_ads` plugin +## 3. 添加 `google_mobile_ads` 插件 + To add the `google_mobile_ads` plugin as a dependency, run `flutter pub add`: +要将 `google_mobile_ads` 插件添加为依赖,运行 `flutter pub add`: + ```console $ flutter pub add google_mobile_ads ``` @@ -124,6 +188,8 @@ $ flutter pub add google_mobile_ads Once you add the plugin, your Android app might fail to build with a `DexArchiveMergerException`: +添加插件后,你的 Android 应用可能会因 `DexArchiveMergerException` 构建失败: + ```plaintext Error while merging dex archives: The number of method references in a .dex file cannot exceed 64K. @@ -134,16 +200,26 @@ through an IDE plugin. The `flutter` tool can detect the issue and ask whether it should try to solve it. Answer `y`, and the problem goes away. You can return to running your app from an IDE after that. +要解决此问题,在终端执行 `flutter run` 命令,而不是通过 IDE 插件。 +`flutter` 工具可以检测问题并询问是否尝试解决。回答 `y`,问题就会消失。 +之后你可以再从 IDE 运行应用。 + ![Screenshot of the `flutter` tool asking about multidex support](/assets/images/docs/cookbook/ads-multidex.png) ::: ## 4. Initialize the Mobile Ads SDK +## 4. 初始化 Mobile Ads SDK + You need to initialize the Mobile Ads SDK before loading ads. +加载广告前需要初始化 Mobile Ads SDK。 + 1. Call `MobileAds.instance.initialize()` to initialize the Mobile Ads SDK. + 调用 `MobileAds.instance.initialize()` 初始化 Mobile Ads SDK。 + ```dart void main() async { @@ -157,6 +233,9 @@ You need to initialize the Mobile Ads SDK before loading ads. Run the initialization step at startup, as shown above, so that the AdMob SDK has enough time to initialize before it is needed. +如上所示,在启动时运行初始化步骤, +以便 AdMob SDK 在需要之前有足够时间完成初始化。 + :::note `MobileAds.instance.initialize()` returns a `Future` but, the way the SDK is built, you don't need to `await` it. @@ -164,18 +243,33 @@ If you try to load an ad before that `Future` is completed, the SDK will gracefully wait until the initialization, and _then_ load the ad. You can await the `Future` if you want to know the exact time when the AdMob SDK is ready. + +`MobileAds.instance.initialize()` 返回 `Future`,但 +SDK 的构建方式使你无需 `await` 它。 +如果你在 `Future` 完成前尝试加载广告, +SDK 会优雅地等待初始化完成,_然后_ 再加载广告。 +如果你想知道 AdMob SDK 就绪的确切时间,可以 `await` 该 `Future`。 ::: ## 5. Load a banner ad +## 5. 加载横幅广告 + To show an ad, you need to request it from AdMob. +要展示广告,你需要向 AdMob 请求广告。 + To load a banner ad, construct a `BannerAd` instance, and call `load()` on it. +要加载横幅广告,构造 `BannerAd` 实例并对其调用 `load()`。 + :::note The following code snippet refers to fields such a `adSize`, `adUnitId` and `_bannerAd`. This will all make more sense in a later step. + +以下代码片段引用了 `adSize`、`adUnitId` 和 `_bannerAd` 等字段。 +这些在后续步骤中会更容易理解。 ::: @@ -213,10 +307,17 @@ void _loadAd() { To view a complete example, check out the last step of this recipe. +要查看完整示例,请参阅本食谱的最后一步。 + + ## 6. Show banner ad +## 6. 展示横幅广告 + Once you have a loaded instance of `BannerAd`, use `AdWidget` to show it. +获得已加载的 `BannerAd` 实例后,使用 `AdWidget` 展示它。 + ```dart AdWidget(ad: _bannerAd) ``` @@ -225,6 +326,9 @@ It's a good idea to wrap the widget in a `SafeArea` (so that no part of the ad is obstructed by device notches) and a `SizedBox` (so that it has its specified, constant size before and after loading). +建议用 `SafeArea` 包裹 widget(避免广告被设备刘海遮挡), +并用 `SizedBox` 包裹(使加载前后具有指定的固定尺寸)。 + ```dart @override @@ -248,6 +352,10 @@ practice for when to call `dispose()` is either after the `AdWidget` is removed from the widget tree or in the `BannerAdListener.onAdFailedToLoad()` callback. +不再需要访问广告时必须释放它。调用 `dispose()` 的最佳实践是 +在 `AdWidget` 从 widget 树移除后,或在 +`BannerAdListener.onAdFailedToLoad()` 回调中调用。 + ```dart _bannerAd?.dispose(); @@ -256,35 +364,59 @@ _bannerAd?.dispose(); ## 7. Configure ads +## 7. 配置广告 + To show anything beyond test ads, you have to register ad units. +要展示测试广告以外的内容,你必须注册广告单元。 + 1. Open [AdMob](https://admob.google.com/). + 打开 [AdMob](https://admob.google.com/)。 + 2. Create an *Ad unit* for each of the AdMob apps. + 为每个 AdMob 应用创建*广告单元*。 + ![Screenshot of the location of Ad Units in AdMob web UI](/assets/images/docs/cookbook/ads-ad-unit.png) This asks for the Ad unit's format. AdMob provides many formats beyond banner ads --- interstitials, rewarded ads, app open ads, and so on. The API for those is similar, and documented in the - [AdMob documentation]({{site.developers}}/admob/flutter/quick-start) + [Ad Manager documentation]({{site.developers}}/admob/flutter/quick-start) and through [official samples](https://github.com/googleads/googleads-mobile-flutter/tree/main/samples/admob). + 这会询问广告单元的格式。AdMob 提供横幅广告以外的多种格式 + ——插页式广告、激励广告、应用开屏广告等。 + 这些格式的 API 类似,记录在 + [Ad Manager 文档]({{site.developers}}/admob/flutter/quick-start)和 + [官方示例](https://github.com/googleads/googleads-mobile-flutter/tree/main/samples/admob)中。 + 3. Choose banner ads. + 选择横幅广告。 + 4. Get the *Ad unit IDs* for both the Android app and the iOS app. You can find these in the **Ad units** section. They look something like `ca-app-pub-1234567890123456/1234567890`. The format resembles the *App ID* but with a slash (`/`) between the two numbers. This distinguishes an *Ad unit ID* from an *App ID*. + 获取 Android 应用和 iOS 应用的*广告单元 ID*。 + 你可以在 **Ad units**(广告单元)部分找到它们。它们类似 + `ca-app-pub-1234567890123456/1234567890`。格式与*应用 ID* 类似, + 但两个数字之间是斜杠 (`/`) 而不是波浪号。 + 这用于区分*广告单元 ID* 与*应用 ID*。 + ![Screenshot of an Ad Unit ID in AdMob web UI](/assets/images/docs/cookbook/ads-ad-unit-id.png) 5. Add these *Ad unit IDs* to the constructor of `BannerAd`, depending on the target app platform. + 根据目标应用平台,将这些*广告单元 ID* 添加到 `BannerAd` 的构造函数中。 + ```dart final String adUnitId = Platform.isAndroid @@ -296,18 +428,30 @@ To show anything beyond test ads, you have to register ad units. ## 8. Final touches +## 8. 收尾工作 + To display the ads in a published app or game (as opposed to debug or testing scenarios), your app must meet additional requirements: +要在已发布的应用或游戏中展示广告(而非调试或测试场景), +你的应用必须满足额外要求: + 1. Your app must be reviewed and approved before it can fully serve ads. Follow AdMob's [app readiness guidelines](https://support.google.com/admob/answer/10564477). For example, your app must be listed on at least one of the supported stores such as Google Play Store or Apple App Store. + 你的应用必须经过审核和批准才能完整投放广告。 + 遵循 AdMob 的[应用就绪指南](https://support.google.com/admob/answer/10564477)。 + 例如,你的应用必须至少上架 Google Play 商店或 Apple App Store 等受支持的商店之一。 + 2. You must [create an `app-ads.txt`](https://support.google.com/admob/answer/9363762) file and publish it on your developer website. + 你必须[创建 `app-ads.txt`](https://support.google.com/admob/answer/9363762) + 文件并将其发布在开发者网站上。 + ![An illustration of a smartphone showing an ad](/assets/images/docs/cookbook/ads-device.jpg){:.site-illustration} To learn more about app and game monetization, @@ -315,12 +459,20 @@ visit the official sites of [AdMob](https://admob.google.com/) and [Ad Manager](https://admanager.google.com/). +要了解更多应用和游戏变现信息, +请访问 [AdMob](https://admob.google.com/) 和 +[Ad Manager](https://admanager.google.com/) 的官方网站。 + ## 9. Complete example +## 9. 完整示例 + The following code implements a simple stateful widget that loads a banner ad and shows it. +以下代码实现了一个加载并展示横幅广告的简单 stateful widget。 + ```dart import 'dart:io'; @@ -413,12 +565,21 @@ class _MyBannerAdWidgetState extends State { :::tip In many cases, you will want to load the ad _outside_ a widget. +在许多情况下,你会希望在 widget _外部_ 加载广告。 + For example, you can load it in a `ChangeNotifier`, a BLoC, a controller, or whatever else you are using for app-level state. This way, you can preload a banner ad in advance, and have it ready to show for when the user navigates to a new screen. +例如,你可以在 `ChangeNotifier`、BLoC、controller +或你用于应用级状态的其他方式中加载它。这样你可以提前预加载横幅广告, +在用户导航到新屏幕时即可展示。 + Verify that you have loaded the `BannerAd` instance before showing it with an `AdWidget`, and that you dispose of the instance when it is no longer needed. + +在使用 `AdWidget` 展示前,请确认已加载 `BannerAd` 实例, +并在不再需要时释放该实例。 ::: diff --git a/sites/docs/src/content/cookbook/plugins/picture-using-camera.md b/sites/docs/src/content/cookbook/plugins/picture-using-camera.md index 4e648ef777..6e072832f5 100644 --- a/sites/docs/src/content/cookbook/plugins/picture-using-camera.md +++ b/sites/docs/src/content/cookbook/plugins/picture-using-camera.md @@ -5,6 +5,7 @@ title: 使用 Camera 插件实现拍照功能 description: 如何在移动设备上使用 camera 插件。 tags: cookbook, 实用教程, 原生插件 keywords: Flutter使用相机,录像,相机预览 +ai-translated: true --- @@ -70,7 +71,7 @@ take a photo, and display it using the following steps: 6. Display the picture with an `Image` widget. - 使用 `Image` 组件展示图片 + 使用 `Image` widget 展示图片 ## 1. Add the required dependencies @@ -159,7 +160,7 @@ the device's camera that allows you to control the camera and display a preview of the camera's feed. 在选择了一个相机后,你需要创建并初始化 `CameraController`。 -在这个过程中,与设备相机建立了连接并允许你控制相机并展示相机的预览帧流。 +在这个过程中,与设备相机建立了连接并让你控制相机并展示相机的预览帧流。 To achieve this, please: @@ -167,7 +168,7 @@ To achieve this, please: 1. Create a `StatefulWidget` with a companion `State` class. - 创建一个带有 `State` 类的 `StatefulWidget` 组件 + 创建一个带有 `State` 类的 `StatefulWidget` widget 2. Add a variable to the `State` class to store the `CameraController`. diff --git a/sites/docs/src/content/cookbook/plugins/play-video.md b/sites/docs/src/content/cookbook/plugins/play-video.md index b61965194e..ec05f6962b 100644 --- a/sites/docs/src/content/cookbook/plugins/play-video.md +++ b/sites/docs/src/content/cookbook/plugins/play-video.md @@ -5,6 +5,7 @@ title: 视频的播放和暂停 description: 如何使用 video_player 插件。 tags: cookbook, 实用教程, 原生插件 keywords: Flutter播放视频 +ai-translated: true --- @@ -176,7 +177,7 @@ videos and control playback. `video_player` 插件成功安装且权限设置完成后, 需要创建一个 `VideoPlayerController`。 -`VideoPlayerController` 类允许你播放不同类型的视频并进行播放控制。 +`VideoPlayerController` 类让你播放不同类型的视频并进行播放控制。 Before you can play videos, you must also `initialize` the controller. This establishes the connection to the video and prepare the @@ -191,7 +192,7 @@ To create and initialize the `VideoPlayerController` do the following: 1. Create a `StatefulWidget` with a companion `State` class - 创建一个 `StatefulWidget` 组件和 `State` 类 + 创建一个 `StatefulWidget` widget 和 `State` 类 2. Add a variable to the `State` class to store the `VideoPlayerController` @@ -275,8 +276,8 @@ to be displayed in a specific aspect ratio, such as 16x9 or 4x3. Therefore, wrap the `VideoPlayer` widget in an [`AspectRatio`][] widget to ensure that the video has the correct proportions. -因此,你可以把 `VideoPlayer` 组件嵌进一个 -[`AspectRatio`][] 组件中,保证视频播放保持正确的比例。 +因此,你可以把 `VideoPlayer` widget 嵌进一个 +[`AspectRatio`][] widget 中,保证视频播放保持正确的比例。 Furthermore, you must display the `VideoPlayer` widget after the `_initializeVideoPlayerFuture()` completes. Use `FutureBuilder` to diff --git a/sites/docs/src/content/cookbook/testing/integration/introduction.md b/sites/docs/src/content/cookbook/testing/integration/introduction.md index 628c58c5cb..c871d7e585 100644 --- a/sites/docs/src/content/cookbook/testing/integration/introduction.md +++ b/sites/docs/src/content/cookbook/testing/integration/introduction.md @@ -5,6 +5,7 @@ title: 集成测试的理念 description: 了解 Flutter 中的集成测试。 # shortTitle: Introduction shortTitle: 介绍 +ai-translated: true --- @@ -16,14 +17,26 @@ together in whole or capture the performance of an app running on a real device. To perform these tasks, use *integration tests*. +单元测试和 widget 测试验证单个类、函数或 widget。 +它们不会验证各个部分如何作为一个整体协同工作, +也不会反映应用在真实设备上运行时的性能。 +要完成这些任务,请使用*集成测试*(integration tests)。 + Integration tests verify the behavior of the complete app. This test can also be called end-to-end testing or GUI testing. +集成测试验证完整应用的行为。 +这类测试也可称为端到端测试或 GUI 测试。 + ## Testing frameworks +## 测试框架 + Two packages are commonly used to perform Flutter integration tests. These are: +Flutter 集成测试常用两个 package,分别是: + - [integration_test][integration_test] package: The official integration test package that is part of the Flutter SDK. Tests written with `integration_test` can perform the following tasks: run on the @@ -32,16 +45,25 @@ and use `flutter_test` APIs. This makes integration tests similar to writing [widget tests][]. However, `integration_test` can't interact with native platform UI. + [integration_test][integration_test] package:官方集成测试 package,属于 Flutter SDK 的一部分。使用 `integration_test` 编写的测试可以:在目标设备上运行、通过 `flutter test integration_test` 在主机上运行,并使用 `flutter_test` API。这使得集成测试的编写方式与[ widget 测试][widget tests]类似。不过,`integration_test` 无法与原生平台 UI 交互。 + - [patrol][] package: A popular third-party integration test package that has many of the features supported by the `integration_test` package, but can additionally interact with native platform UI such as permission dialogs, notifications, or the contents of platform views. + [patrol][] package:流行的第三方集成测试 package,具备 `integration_test` package 支持的许多功能,还能额外与原生平台 UI 交互,例如权限对话框、通知或平台视图的内容。 + ## Terminology +## 术语 + **host machine** : The system on which you develop your app, like a desktop computer. +**主机(host machine)** +: 你开发应用所在的系统,例如台式计算机。 + **target device** : The mobile device, browser, or desktop application that runs your Flutter app. @@ -49,21 +71,40 @@ permission dialogs, notifications, or the contents of platform views. If you run your app in a web browser or as a desktop application, the host machine and the target device are the same. +**目标设备(target device)** +: 运行 Flutter 应用的移动设备、浏览器或桌面应用。 + + 若在 Web 浏览器或桌面应用中运行应用, + 主机与目标设备是同一台机器。 + ## Getting started +## 入门 + To use `integration_test`, add it as a dependency for your Flutter app test file. +要使用 `integration_test`,请将其作为依赖添加到你的 Flutter 应用测试文件中。 + To migrate existing projects that use `flutter_driver`, consult the [Migrating from flutter_driver][] guide. +若要将使用 `flutter_driver` 的现有项目迁移, +请参阅[从 flutter_driver 迁移][Migrating from flutter_driver]指南。 + To use `patrol`, follow the [Patrol setup guide][]. +要使用 `patrol`,请遵循 [Patrol 设置指南][Patrol setup guide]。 + ## Use cases for integration testing +## 集成测试的用例 + The other guides in this section explain how to use integration tests to validate [functionality][] and [performance][]. +本小节的其他指南说明如何使用集成测试验证[功能][functionality]和[性能][performance]。 + [functionality]: /testing/integration-tests/ [integration_test]: {{site.repo.flutter}}/tree/main/packages/integration_test [Migrating from flutter_driver]: /release/breaking-changes/flutter-driver-migration diff --git a/sites/docs/src/content/cookbook/testing/integration/profiling.md b/sites/docs/src/content/cookbook/testing/integration/profiling.md index bd79cff4e3..c203457b65 100644 --- a/sites/docs/src/content/cookbook/testing/integration/profiling.md +++ b/sites/docs/src/content/cookbook/testing/integration/profiling.md @@ -5,6 +5,7 @@ title: 通过集成测试测量性能 description: 如何测量 Flutter 应用的性能。 tags: cookbook, 实用教程, 测试 keywords: 性能优化,卡顿,时间轴 +ai-translated: true --- @@ -81,7 +82,7 @@ list of items. To focus on performance profiling, this recipe builds on the [Scrolling][] recipe in widget tests. 在这一小节,我们将记录当滚动列表条目时应用程序的性能。 -为了专注于性能分析,这一小节在组件测试中 +为了专注于性能分析,这一小节在 widget 测试中 [Scrolling in integration tests(列表滚动集成测试)][Scrolling] 的基础上进行。 diff --git a/sites/docs/src/content/cookbook/testing/widget/index.md b/sites/docs/src/content/cookbook/testing/widget/index.md index 08fbec4888..5d2ec09be6 100644 --- a/sites/docs/src/content/cookbook/testing/widget/index.md +++ b/sites/docs/src/content/cookbook/testing/widget/index.md @@ -1,6 +1,10 @@ --- -title: Widget testing +# title: Widget testing +title: Widget 测试 +# breadcrumb: Widget breadcrumb: Widget -description: A catalog of recipes for adding widget testing to your Flutter app. +# description: A catalog of recipes for adding widget testing to your Flutter app. +description: 为 Flutter 应用添加 widget 测试的食谱目录。 layout: toc +ai-translated: true --- diff --git a/sites/docs/src/content/cookbook/testing/widget/orientation.md b/sites/docs/src/content/cookbook/testing/widget/orientation.md index 2cba48f2b0..0956672b49 100644 --- a/sites/docs/src/content/cookbook/testing/widget/orientation.md +++ b/sites/docs/src/content/cookbook/testing/widget/orientation.md @@ -1,6 +1,9 @@ --- -title: Test orientation -description: How to test if an app is in portrait or landscape mode. +# title: Test orientation +title: 测试屏幕方向 +# description: How to test if an app is in portrait or landscape mode. +description: 如何测试应用处于竖屏还是横屏模式。 +ai-translated: true --- In Flutter, you can build different layouts depending on a given @@ -9,26 +12,55 @@ is in portrait mode, and three columns if in landscape mode. Additionally, you can create tests that check if the correct number of columns are being shown for each orientation. +在 Flutter 中,你可以根据给定的 [orientation][] 构建不同布局。 +例如,应用在竖屏模式下用两列展示数据,横屏模式下用三列。 +此外,你还可以编写测试,检查每种方向下是否显示了正确数量的列。 + In this recipe, you can learn how check if the orientation of an app is `portrait` or `landscape`, and also how many columns are displayed for each orientation. +在本食谱中,你将学习如何检查应用方向是 `portrait` 还是 `landscape`, +以及每种方向下显示多少列。 + This recipe uses the following steps: +本食谱使用以下步骤: + 1. Create an app that updates the layout of the content, based on the orientation. + + 创建一个根据方向更新内容布局的应用。 + 1. Create an orientation test group. + + 创建方向测试组。 + 1. Create a portrait orientation test. + + 创建竖屏方向测试。 + 1. Create a landscape orientation test. + + 创建横屏方向测试。 + 1. Run the tests. + + 运行测试。 ## 1. Create an app that updates based on orientation +## 1. 创建根据方向更新的应用 + Create a Flutter app that changes how many columns are shown when an app is in portrait or landscape mode: +创建一个 Flutter 应用,在竖屏或横屏模式下显示不同数量的列: + 1. Create a new Flutter project called `orientation_tests`. + 创建名为 `orientation_tests` 的新 Flutter 项目。 + ```console flutter create orientation_tests ``` @@ -36,14 +68,25 @@ app is in portrait or landscape mode: 2. Follow the steps in [Update the UI based on orientation][] to set up the project. + 按照 [根据方向更新 UI][Update the UI based on orientation] 中的步骤配置项目。 + ## 2. Create an orientation test group +## 2. 创建方向测试组 + After you've set up your `orientation_tests` project, complete these steps to group your future orientation tests: +配置好 `orientation_tests` 项目后,完成以下步骤以组织后续的方向测试: + 1. In your Flutter project, open `test/widget_test.dart`. + + 在 Flutter 项目中打开 `test/widget_test.dart`。 + 1. Replace the existing contents with the following: + 将现有内容替换为以下内容: + ```dart title="widget_test.dart" import 'package:flutter/material.dart'; @@ -59,13 +102,20 @@ group your future orientation tests: ## 3. Create a portrait orientation test +## 3. 创建竖屏方向测试 + Add the portrait orientation test to the `Orientation` group. This test makes sure that the orientation is `portrait` and that only `2` columns of data appear in the app: +将竖屏方向测试添加到 `Orientation` 组。 +该测试确保方向为 `portrait`,且应用中只显示 `2` 列数据: + 1. In `test/widget_test.dart`, replace `...` inside of the `Orientation` group with the following test: + 在 `test/widget_test.dart` 中,将 `Orientation` 组内的 `...` 替换为以下测试: + ```dart title="widget_test.dart" // Check if portrait mode displays correctly. @@ -98,13 +148,21 @@ only `2` columns of data appear in the app: ## 4. Create a landscape orientation test +## 4. 创建横屏方向测试 + Add the landscape orientation test to the `Orientation` group. This test makes sure that the orientation is `landscape` and that only `3` columns of data appear in the app: +将横屏方向测试添加到 `Orientation` 组。 +该测试确保方向为 `landscape`,且应用中只显示 `3` 列数据: + 1. In `test/widget_test.dart`, inside of the `Orientation` group, add the following test after the landscape test: + 在 `test/widget_test.dart` 的 `Orientation` 组内, + 在竖屏测试之后添加以下测试: + ```dart title="widget_test.dart" // Check if landscape mode displays correctly. @@ -137,14 +195,20 @@ only `3` columns of data appear in the app: ## 5. Run the tests +## 5. 运行测试 + Run the tests using the following command from the root of the project: +在项目根目录运行以下命令执行测试: + ```console flutter test test/widget_test.dart ``` ## Complete example +## 完整示例 + ```dart title="widget_test.dart" import 'package:flutter/material.dart'; diff --git a/sites/docs/src/content/data-and-backend/google-apis.md b/sites/docs/src/content/data-and-backend/google-apis.md index dff883e9a9..601481d5d0 100644 --- a/sites/docs/src/content/data-and-backend/google-apis.md +++ b/sites/docs/src/content/data-and-backend/google-apis.md @@ -50,6 +50,10 @@ To add authentication to Firebase explicitly, check out the codelab and the [Get Started with Firebase Authentication on Flutter][fb-auth] docs. +若要显式地为 Firebase 添加身份验证,请查阅 +[使用 FirebaseUI 为 Flutter 应用添加用户身份验证流程][fb-lab] codelab, +以及 [在 Flutter 上开始使用 Firebase 身份验证][fb-auth] 文档。 + [fb-lab]: {{site.firebase}}/codelabs/firebase-auth-in-flutter-apps [Calendar]: {{site.pub-api}}/googleapis/latest/calendar_v3/calendar_v3-library.html [fb-auth]: {{site.firebase}}/docs/auth/flutter/start @@ -261,6 +265,9 @@ Once you have a signed-in user, request the relevant client authorization tokens using [`authorizationForScopes`][] for the API scopes that your app requires. +当你拥有已登录的用户后,使用 [`authorizationForScopes`][] +为应用所需的 API 作用域请求相关的客户端授权令牌。 + ```dart const relevantScopes = [YouTubeApi.youtubeReadonlyScope]; @@ -272,6 +279,9 @@ final authorization = await currentUser.authorizationClient If your scopes require user interaction, you'll need to use [`authorizeScopes`][] from an interaction handler instead of `authorizationForScopes`. + +如果你的作用域需要用户交互, +则需要在交互处理器中使用 [`authorizeScopes`][],而非 `authorizationForScopes`。 ::: Once you have the relevant authorization tokens, @@ -279,6 +289,10 @@ use the [`authClient`][] extension from [`package:extension_google_sign_in_as_googleapis_auth`][] to set up an authenticated HTTP client with the relevant credentials applied. +当你拥有相关的授权令牌后,使用 +[`package:extension_google_sign_in_as_googleapis_auth`][] 提供的 [`authClient`][] 扩展, +配置一个应用了相关凭证、已通过身份验证的 HTTP 客户端。 + ```dart import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart'; @@ -296,9 +310,6 @@ final authenticatedClient = authorization!.authClient( [`authClient`]: {{site.pub-api}}/extension_google_sign_in_as_googleapis_auth/latest/extension_google_sign_in_as_googleapis_auth/GoogleApisGoogleSignInAuth/authClient.html [`package:extension_google_sign_in_as_googleapis_auth`]: {{site.pub-pkg}}/extension_google_sign_in_as_googleapis_auth -[`Client`]({{site.pub-api}}/http/latest/http/Client-class.html) 实例 -包含了调用 Google API 类时所需的凭证。 - ## 5. Create and use the desired API class ## 5. 创建并使用所需的 API 类 diff --git a/sites/docs/src/content/data-and-backend/persistence/index.md b/sites/docs/src/content/data-and-backend/persistence/index.md index d369907d45..b6600e7d52 100644 --- a/sites/docs/src/content/data-and-backend/persistence/index.md +++ b/sites/docs/src/content/data-and-backend/persistence/index.md @@ -1,5 +1,8 @@ --- layout: toc -title: Persistence -description: Content covering persistence in Flutter apps. +# title: Persistence +title: 持久化 +# description: Content covering persistence in Flutter apps. +description: 涵盖 Flutter 应用中持久化的内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/data-and-backend/serialization/index.md b/sites/docs/src/content/data-and-backend/serialization/index.md index de01c502b1..ceeb4aaea6 100644 --- a/sites/docs/src/content/data-and-backend/serialization/index.md +++ b/sites/docs/src/content/data-and-backend/serialization/index.md @@ -1,5 +1,8 @@ --- layout: toc -title: Serialization -description: Content covering serialization in Flutter apps. +# title: Serialization +title: 序列化 +# description: Content covering serialization in Flutter apps. +description: 涵盖 Flutter 应用中序列化的内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/data-and-backend/serialization/json.md b/sites/docs/src/content/data-and-backend/serialization/json.md index b736f490a1..8f9ad149db 100644 --- a/sites/docs/src/content/data-and-backend/serialization/json.md +++ b/sites/docs/src/content/data-and-backend/serialization/json.md @@ -42,8 +42,8 @@ To avoid confusion, this doc uses "serialization" when referring to the overall process, and "encoding" and "decoding" when specifically referring to those processes. -为了避免混淆,本文档在涉及到整个过程时将使用“序列化数据”, -在特指其中某个过程时使用“编码”和“解码”。 +为了避免混淆,本文档在涉及到整个过程时将使用「序列化数据」, +在特指其中某个过程时使用「编码」和「解码」。 ::: @@ -173,7 +173,7 @@ code from your release builds. This optimizes the app's size significantly. 这样的库需要使用运行时进行 [反射][reflection],这在 Flutter 中是被禁用的。 运行时反射会影响 Dart 支持了相当久的 [摇树优化][tree shaking]。 -通过 tree shaking,你可以从你的发布版本中“抖掉”不需要使用的代码。 +通过 tree shaking,你可以从你的发布版本中「抖掉」不需要使用的代码。 这会显著优化 App 的体积。 Since reflection makes all code implicitly used by default, it makes tree @@ -251,7 +251,7 @@ you lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Your code will become instantly more error-prone. -不幸的是,`jsonDecode()` 返回一个 `Map`, +不幸的是,`jsonDecode()` 返回一个 `dynamic`, 这意味着你在运行时以前都不知道值的类型。 使用这个方法,你失去了大部分的静态类型语言特性: 类型安全、自动补全以及最重要的编译时异常。 diff --git a/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md b/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md index b44c107223..d18ee2d1ca 100644 --- a/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md +++ b/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md @@ -53,7 +53,7 @@ and the UI rebuilds from scratch. (例如,用户在设置界面中点击了一个开关选项) 你改变了状态,这将会触发用户界面的重绘。 去改变用户界面本身是没有必要的 -(例如 widget.setText )—你改变了状态,那么用户界面将重新构建。 +(例如 `widget.setText`)—你改变了状态,那么用户界面将重新构建。 Read more about the declarative approach to UI programming in the [Introduction to declarative UI][]. diff --git a/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md b/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md index cb5cbdca1e..4bd478fdd7 100644 --- a/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md +++ b/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md @@ -43,7 +43,7 @@ be separated into two conceptual types: ephemeral state and app state. 首先,你不需要管理一些状态(例如纹理),框架本身会替你管理。 所以对于状态的更有用的定义是 -“当任何时候你需要重建你的用户界面时你所需要的数据”。 +「当任何时候你需要重建你的用户界面时你所需要的数据」。 其次,你需要自己 **管理** 的状态可以分为两种概念类型: 短时 (ephemeral) 状态和应用 (app) 状态。 @@ -130,7 +130,7 @@ you don't mind that `_index` resets to zero. 在这里,使用 `setState()` 和一个在有状态 widget 的 State 类中的变量是很自然的。 你的 app 中的其他部分不需要访问 `_index`。 这个变量只会在 `MyHomepage` widget 中改变。 -而且,如果用户关闭并重启这个 app,你不会介意 `_index` 重置回 0. +而且,如果用户关闭并重启这个 app,你不会介意 `_index` 重置回 0。 ## App state @@ -187,7 +187,7 @@ simple app samples (including the starter app that you get with every `flutter create`). 需要说明的是,你 **可以** 使用 `State` 和 `setState()` 管理你的应用中的所有状态。 -实际上Flutter团队在很多简单的示例程序 +实际上 Flutter 团队在很多简单的示例程序 (包括你每次使用 `flutter create` 命令创建的初始应用)中正是这么做的。 It goes the other way, too. For example, you might decide that—in @@ -199,7 +199,7 @@ In that case, the `_index` variable is app state. 也可以用另外一种方式。比如,在一个特定的应用中, 你可以指定底部导航栏中被选中的项目 **不是** 一个短时状态。 你可能需要在底部导航栏类的外部来改变这个值,并在对话期间保留它。 -在种情况下 `_index` 就是一个应用状态。 +在这种情况下 `_index` 就是一个应用状态。 There is no clear-cut, universal rule to distinguish whether a particular variable is ephemeral or app state. @@ -226,11 +226,11 @@ When asked about React's setState versus Redux's store, the author of Redux, Dan Abramov, replied: 当我们就 React 的 setState 和 Redux 的 Store -哪个好这个问题问 Redux 的作者 Dan Abramov 时, 他如此回答: +哪个好这个问题问 Redux 的作者 Dan Abramov 时,他如此回答: > "The rule of thumb is: [Do whatever is less awkward][]." > -> "经验原则是: [选择能够减少麻烦的方式][Do whatever is less awkward]" +> 「经验原则是:[选择能够减少麻烦的方式][Do whatever is less awkward]」 In summary, there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using `State` and `setState()`, @@ -241,7 +241,7 @@ the two depends on your own preference and the complexity of the app. 总之,在任何 Flutter 应用中都存在两种概念类型的状态, 短时状态经常被用于一个单独 widget 的本地状态, 通常使用 `State` 和 `setState()` 来实现。 -其他的是你的应用应用状态,在任何一个 Flutter 应用中这两种状态都有自己的位置。 +其他的是你的应用状态,在任何一个 Flutter 应用中这两种状态都有自己的位置。 如何划分这两种状态取决于你的偏好以及应用的复杂度。 [Do whatever is less awkward]: {{site.github}}/reduxjs/redux/issues/1287#issuecomment-175351978 diff --git a/sites/docs/src/content/data-and-backend/state-mgmt/intro.md b/sites/docs/src/content/data-and-backend/state-mgmt/intro.md index 63ae2e80f9..306e288565 100644 --- a/sites/docs/src/content/data-and-backend/state-mgmt/intro.md +++ b/sites/docs/src/content/data-and-backend/state-mgmt/intro.md @@ -1,7 +1,8 @@ --- # title: State management title: 状态 (State) 管理介绍 -description: How to structure an app to manage the state of the data flowing through it. +# description: How to structure an app to manage the state of the data flowing through it. +description: 如何组织应用结构,以管理流经其中的数据状态。 tags: Flutter状态管理 keywords: 概览 next: diff --git a/sites/docs/src/content/data-and-backend/state-mgmt/options.md b/sites/docs/src/content/data-and-backend/state-mgmt/options.md index b77ad829c8..49a4b29b25 100644 --- a/sites/docs/src/content/data-and-backend/state-mgmt/options.md +++ b/sites/docs/src/content/data-and-backend/state-mgmt/options.md @@ -99,6 +99,8 @@ update state and notify the UI of changes. * [State Management using ValueNotifier and InheritedNotifier][], by Tadas Petra + [使用 ValueNotifier 与 InheritedNotifier 进行状态管理 (State Management using ValueNotifier and InheritedNotifier)][State Management using ValueNotifier and InheritedNotifier],Tadas Petra 著。 + [State Management using ValueNotifier and InheritedNotifier]: https://www.hungrimind.com/articles/flutter-state-management @@ -159,14 +161,23 @@ State management packages often help reduce boilerplate code, provide specialized debugging tools, and can help enable a clearer and consistent application architecture. +根据应用的复杂度与团队的偏好,你可能会发现采用状态管理 package 很有帮助。 +状态管理 package 通常有助于减少样板代码、提供专门的调试工具,并能促成更清晰、更一致的应用架构。 + The Flutter community offers a wide variety of state management packages. The best choice for your app often depends on the app's complexity, your team's preferences, and the specific problems you need to solve. +Flutter 社区提供了种类繁多的状态管理 package。 +最适合你应用的选择,往往取决于应用的复杂度、团队的偏好,以及你需要解决的具体问题。 + To begin exploring the available options, check out the [`#state-management`][]{: target="_blank"} topic on the pub.dev site and refine the search to find packages that match your needs. +要开始探索可用的选项,请查看 pub.dev 上的 [`#state-management`][]{: target="_blank"} 主题, +并细化搜索以找到符合你需求的 package。 +
diff --git a/sites/docs/src/content/data-and-backend/state-mgmt/simple.md b/sites/docs/src/content/data-and-backend/state-mgmt/simple.md index 25fe6f0bb6..5b83a9999d 100644 --- a/sites/docs/src/content/data-and-backend/state-mgmt/simple.md +++ b/sites/docs/src/content/data-and-backend/state-mgmt/simple.md @@ -305,7 +305,7 @@ Now you can `import 'package:provider/provider.dart';` and start building. 现在可以在代码里加入 `import 'package:provider/provider.dart';` -进而开始构建你的应用了/ +进而开始构建你的应用了。 With `provider`, you don't need to worry about callbacks or `InheritedWidgets`. But you do need to understand 3 concepts: @@ -503,7 +503,9 @@ In this case, we want `CartModel`, so we write the `provider` package won't be able to help you. `provider` is based on types, and without the type, it doesn't know what you want. -我们必须指定要访问的模型类型。在这个示例中,我们要访问 `CartModel` 那么就写上 `Consumer`。 +我们必须指定要访问的模型类型。在这个示例中,我们要访问 `CartModel`,那么就写上 `Consumer`。 +如果你不指定泛型 (``),`provider` package 将无法帮到你。 +`provider` 基于类型工作,没有类型,它就无法知道你想要什么。 The only required argument of the `Consumer` widget is the builder. Builder is a function that is called whenever the @@ -511,8 +513,8 @@ is the builder. Builder is a function that is called whenever the in your model, all the builder methods of all the corresponding `Consumer` widgets are called.) -`Consumer` widget 唯一必须的参数就是 builder。 当 `ChangeNotifier` -发生变化的时候会调用 builder 这个函数。 +`Consumer` widget 唯一必须的参数就是 builder。 +当 `ChangeNotifier` 发生变化的时候会调用 builder 这个函数。 (换言之,当你在模型中调用 `notifyListeners()` 时, 所有相关的 `Consumer` widget 的 builder 方法都会被调用。) diff --git a/sites/docs/src/content/deployment/cd.md b/sites/docs/src/content/deployment/cd.md index cb10556b84..81508d6a87 100644 --- a/sites/docs/src/content/deployment/cd.md +++ b/sites/docs/src/content/deployment/cd.md @@ -153,7 +153,7 @@ Visit the [fastlane docs][fastlane] for more info. ![iOS](/assets/images/docs/cd/ios.png) iTunes Connect 用户名已经存在于你的 `Appfile` 的 `apple_id` 字段中, 你需要将你的 iTunes 密码设置到 `FASTLANE_PASSWORD` 这个环境变量里。 - 否则,上传到 iTunes/TestFlight时会提示你。 + 否则,上传到 iTunes/TestFlight 时会提示你。 1. Set up code signing. @@ -167,7 +167,7 @@ Visit the [fastlane docs][fastlane] for more info. distribution certificate instead of a development certificate when you're ready to test and deploy using TestFlight or App Store. - ![iOS](/assets/images/docs/cd/ios.png) 在iOS上, + ![iOS](/assets/images/docs/cd/ios.png) 在 iOS 上, 当你准备使用 TestFlight 或 App Store 进行测试和部署时, 使用分发证书而不是开发证书进行创建和签名。 @@ -196,7 +196,7 @@ Visit the [fastlane docs][fastlane] for more info. [fastlane Android beta deployment guide][] 指引操作。 你可以简单的编辑一下文件,加一个名叫 `upload_to_play_store` 的 `lane`。 为了使用 `flutter build` 命令编译 `aab`, - 要把 `apk` 参数设置为 `../build/app/outputs/bundle/release/app-release.aab`。 + 要把 `aab` 参数设置为 `../build/app/outputs/bundle/release/app-release.aab`。 * ![iOS](/assets/images/docs/cd/ios.png) On iOS, follow the [fastlane iOS beta deployment guide][]. @@ -253,7 +253,7 @@ process to a continuous integration (CI) system. First, follow the local setup section described in 'Local setup' to make sure the process works before migrating onto a cloud system like Travis. -首先,按照“本地设置”中描述的本地设置部分,确保在迁移到 Travis 等云系统之前,该过程有效。 +首先,按照「本地设置」中描述的本地设置部分,确保在迁移到 Travis 等云系统之前,该过程有效。 The main thing to consider is that since cloud instances are ephemeral and untrusted, you won't be leaving your credentials like your Play Store service @@ -279,7 +279,7 @@ secrets in pull requests that you accept and merge. **采取预防措施,不要在测试脚本中将这些变量值重新回显到控制台。** 在合并之前,这些变量在拉取请求中也不可用, 以确保恶意行为者无法创建打印这些密钥的拉取请求。 -在接受和合并的 pull 请求中,请注意与这些密钥。 +在接受和合并的 pull 请求中,请注意与这些密钥的交互。 1. Make login credentials ephemeral. @@ -307,14 +307,14 @@ secrets in pull requests that you accept and merge. system during the install phase with 序列化你的上传密钥(例如,使用 base64)并将其另存为加密环境变量。 - 可以可以在安装阶段在 CI 系统上对其进行反序列化 + 可以在安装阶段在 CI 系统上对其进行反序列化 ```bash echo "$PLAY_STORE_UPLOAD_KEY" | base64 --decode > [path to your upload keystore] ``` * ![iOS](/assets/images/docs/cd/ios.png) On iOS: - ![iOS](/assets/images/docs/cd/ios.png) 在 iOS 上: + ![iOS](/assets/images/docs/cd/ios.png) 在 iOS 上: * Move the local environment variable `FASTLANE_PASSWORD` to use encrypted environment variables on the CI system. @@ -325,7 +325,7 @@ secrets in pull requests that you accept and merge. fastlane's [Match][] system is recommended to synchronize your certificates across machines. - CI 系统需要有权限拿到你的分发证书。建议使用fastlane 的 [Match][] 系统在不同的机器上同步你的证书。 + CI 系统需要有权限拿到你的分发证书。建议使用 fastlane 的 [Match][] 系统在不同的机器上同步你的证书。 2. It's recommended to use a Gemfile instead of using an indeterministic `gem install fastlane` on the CI system each time to ensure the fastlane @@ -352,13 +352,13 @@ secrets in pull requests that you accept and merge. * When running locally, use `bundle exec fastlane` instead of `fastlane`. - 当你在本地运行的时候,请使用 `bundle exec fastlane` 而不是 `fastlane`。 + 当你在本地运行的时候,请使用 `bundle exec fastlane` 而不是 `fastlane`。 3. Create the CI test script such as `.travis.yml` or `.cirrus.yml` in your repository root. 在你的仓库根目录创建一个 CI 测试脚本, - 例如: `.travis.yml` 或 `.cirrus.yml`。 + 例如:`.travis.yml` 或 `.cirrus.yml`。 * See [fastlane CI documentation][] for CI specific setup. @@ -392,7 +392,7 @@ secrets in pull requests that you accept and merge. * For iOS, you might have to specify a dependency on Xcode (for example, `osx_image: xcode9.2`). - 在 iOS 平台上,你需要为 Xcode 指定依赖 (比如: `osx_image: xcode9.2`) + 在 iOS 平台上,你需要为 Xcode 指定依赖(比如:`osx_image: xcode9.2`) * In the script phase of the CI task: diff --git a/sites/docs/src/content/deployment/flavors-ios.md b/sites/docs/src/content/deployment/flavors-ios.md index 6dcc4df21e..fd0a6ca055 100644 --- a/sites/docs/src/content/deployment/flavors-ios.md +++ b/sites/docs/src/content/deployment/flavors-ios.md @@ -1,52 +1,66 @@ --- -title: Set up Flutter flavors for iOS and macOS +# title: Set up Flutter flavors for iOS and macOS +title: 为 iOS 和 macOS 配置 Flutter flavor shortTitle: Flavors (iOS and macOS) +# description: > +# How to create Flutter flavors for an iOS or macOS app. description: > - How to create Flutter flavors for an iOS or macOS app. + 如何为 iOS 或 macOS 应用创建 Flutter flavor。 +ai-translated: true --- This guide shows you how to create Flutter flavors for an iOS or macOS app. +本指南介绍如何为 iOS 或 macOS 应用创建 Flutter flavor。 + ## Overview +## 概览 + A Flutter flavor is basically a collection of settings that define how a specific version of your app should be built and run. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app. +Flutter flavor 本质上是一组设置,定义应用的特定版本应如何构建和运行。例如,flavor 可决定特定版本关联的图标、应用名称、API 密钥、功能开关和日志级别。 + If you want to create Flutter flavors for an iOS app, you'll need to do so in Xcode. Xcode does not have a concept called "flavor". Instead, you'll need to set up something called a scheme and attach custom configurations to it. +若要为 iOS 应用创建 Flutter flavor,需在 Xcode 中操作。Xcode 没有名为「flavor」的概念,你需要设置 scheme 并为其附加自定义配置。 + The following illustrates an example of two Flutter flavors (staging, production) as Xcode schemes with custom Xcode configurations assigned to them: +下图示例展示两个 Flutter flavor(staging、production)作为 Xcode scheme,并分配了自定义 Xcode 配置: + - + @@ -54,18 +68,27 @@ configurations assigned to them: ## Configure Xcode schemes +## 配置 Xcode scheme + The following steps show how to configure two Xcode schemes called `staging` and `production` for your Flutter iOS project. You can also use these steps to set up a macOS project by replacing any reference to `iOS` with `macOS`. +以下步骤说明如何为 Flutter iOS 项目配置名为 `staging` 和 `production` 的两个 Xcode scheme。 +也可将 `iOS` 替换为 `macOS` 来配置 macOS 项目。 + For a seamless workflow, we've started with a new Flutter project called `flavors_example`, but you can always start with an existing project. +为流程顺畅,我们从名为 `flavors_example` 的新 Flutter 项目开始,你也可以从现有项目入手。 + 1. Create a new Flutter project called `flavors_example`. + 创建一个名为 `flavors_example` 的新 Flutter 项目。 + ```console title="console" $ flutter create flavors_example ``` @@ -73,6 +96,8 @@ always start with an existing project. 1. Open the default Xcode workspace for the iOS version of the `flavors_example` project. + 为 `flavors_example` 项目的 iOS 版本打开默认的 Xcode workspace。 + ```console title="console" $ cd flavors_example && open ios/Runner.xcworkspace ``` @@ -80,29 +105,49 @@ always start with an existing project. 1. Open the `flavors_example` project in the Xcode project navigator: + 在 Xcode 项目导航器中打开 `flavors_example` 项目: + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **project navigator**(**View** > **Navigators** > **Project**)。 + * In the **project navigator**, at the top, select **Runner**. + 在 **project navigator** 顶部选择 **Runner**。 + 1. Create schemes in Xcode: + 在 Xcode 中创建 scheme: + * Open the **New Scheme** window (**Product > Scheme > New Scheme**). + 打开 **New Scheme** 窗口(**Product > Scheme > New Scheme**)。 + * In the **Target** field, select **Runner**. + 在 **Target** 字段中选择 **Runner**。 + * In the **Name** box, enter `staging`. + 在 **Name** 框中输入 `staging`。 + * Click **Okay** to add the new scheme. + 点击 **Okay** 添加新 scheme。 + * Repeat the previous steps for a scheme called `production`. + 对名为 `production` 的 scheme 重复上述步骤。 + * When finished, check to make sure that you have the following schemes: + 完成后,检查确认你已拥有以下 scheme: + ![Schemes for Flutter flavors](/assets/images/docs/flavors/flavors-ios-schemes.png){:width="100%"} :::note @@ -113,40 +158,67 @@ always start with an existing project. (**Product > Scheme > Manage Schemes**) and make sure that the **Shared** checkbox to the right of your new scheme is checked. + + 默认情况下新 scheme 是共享的。scheme 必须共享,Flutter flavor 才能正常工作。 + 要再次确认已启用共享,请打开 **Manage Schemes** 窗口(**Product > Scheme > Manage Schemes**), + 确保新 scheme 右侧的 **Shared** 复选框已勾选。 ::: 1. Create configurations for the schemes in Xcode: + 在 Xcode 中为这些 scheme 创建配置: + * In the **project navigator**, select **Runner**. + 在 **project navigator** 中选择 **Runner**。 + * In the main window under **PROJECT**, select **Runner**. + 在主窗口的 **PROJECT** 下选择 **Runner**。 + * Open the **Info tab** if it isn’t open. + 若 **Info tab** 未打开,请将其打开。 + * Go to the **Configurations** section and add new `Debug` configurations. + 进入 **Configurations** 部分,添加新的 `Debug` 配置。 + * Click **+**, select **Duplicate "Debug" configuration**, and name the new configuration `Debug-staging`. + + 点击 **+**,选择 **Duplicate "Debug" configuration**, + 并将新配置命名为 `Debug-staging`。 + * Click **+**, select **Duplicate "Debug" configuration**, and name the new configuration `Debug-production`. + 点击 **+**,选择 **Duplicate "Debug" configuration**, + 并将新配置命名为 `Debug-production`。 + * Repeat the previous step for the `Release` configurations and the `Profile` configurations. + 对 `Release` 配置和 `Profile` 配置重复上一步骤。 + * When finished, check to make sure that you have the following configurations: + 完成后,检查确认你已拥有以下配置: + ![Scheme configurations for Flutter flavors](/assets/images/docs/flavors/flavors-ios-scheme-configurations.png){:width="100%"} :::note The scheme name (example: `staging`) that is appended to a configuration name must be lowercase if you want to use it with the Flutter CLI command. + + 若要与 Flutter CLI 配合,追加到配置名的 scheme 名称(如 `staging`)须为小写。 ::: :::note @@ -155,18 +227,29 @@ always start with an existing project. and `Release.xcconfig` files, not the `Pods-Runner.xcconfig` file. You can check this by expanding the configuration names in Xcode. + + 配置应基于 `Debug.xcconfig`、`Profile.xcconfig`、`Release.xcconfig`,而非 `Pods-Runner.xcconfig`。 + 可在 Xcode 中展开配置名检查。 ::: 1. Assign the configurations to the schemes in Xcode: + 在 Xcode 中将配置分配给这些 scheme: + * Open the **Manage Schemes** window (**Product > Scheme > Manage Schemes**). + 打开 **Manage Schemes** 窗口(**Product > Scheme > Manage Schemes**)。 + * Select the `staging` scheme and edit it. + 选择 `staging` scheme 并进行编辑。 + * In the following tabs, update the **Build Configuration** field as follows: + 在以下各个 tab 中,按如下方式更新 **Build Configuration** 字段: + * **Run**: `Debug-staging` * **Test**: `Debug-staging` * **Profile**: `Profile-staging` @@ -175,56 +258,90 @@ always start with an existing project. * Click **Close**. + 点击 **Close**。 + * Repeat the previous steps for the `production` scheme. + 对 `production` scheme 重复上述步骤。 + 1. If you are working with a pre-existing Flutter project that has at least one Podfile, update it. For more information, see [Update Podfiles][]. + 如果你使用的是已有的、至少包含一个 Podfile 的 Flutter 项目,请更新它。 + 更多信息请参阅 [更新 Podfile][Update Podfiles]。 + 1. To make sure that you've set up everything correctly, run your app on the new schemes in Xcode. You won't see any differences because the configuration settings haven't changed, but you do want to make sure that the app can run. + 为确保一切设置正确,请在 Xcode 中用这些新 scheme 运行你的应用。 + 由于配置设置尚未改变,你不会看到任何差异,但你需要确认应用可以正常运行。 + * Select the `staging` scheme (**Product > Schemes > staging**). + 选择 `staging` scheme(**Product > Schemes > staging**)。 + * To the right of `staging` in the toolbar, select the iOS device you want to test against. In the following example, the device is `iPhone 16 Pro`. + 在工具栏中 `staging` 右侧,选择你想要测试的 iOS 设备。 + 在以下示例中,设备为 `iPhone 16 Pro`。 + ![Run a Flutter flavor](/assets/images/docs/flavors/flavors-ios-test-scheme.png){:width="100%"} * Run the app scheme (**Product > Run**). + 运行该应用 scheme(**Product > Run**)。 + * Repeat the previous steps for the `production` scheme. + 对 `production` scheme 重复上述步骤。 + 1. If everything runs, you're ready to customize your configurations. For more information, see [Customize configurations][]. + 如果一切都能运行,你就可以开始自定义配置了。更多信息请参阅 + [自定义配置][Customize configurations]。 + [Update Podfiles]: #update-podfiles [Customize configurations]: #customize-configurations ## Launch an Xcode scheme +## 启动 Xcode scheme + After you've created the schemes for an iOS app in Xcode, you can launch a specific scheme through Xcode or Flutter. You can also use these steps to launch a macOS project by replacing any reference to `iOS` with `macOS`. +在 Xcode 中为 iOS 应用创建 scheme 后,可通过 Xcode 或 Flutter 启动特定 scheme。将 `iOS` 替换为 `macOS` 也可用于 macOS 项目。 + ### Use the flavor flag (Flutter CLI) +### 使用 flavor 标志(Flutter CLI) + You can launch an Xcode scheme in `Debug` mode with the Flutter CLI using the following steps: +可使用 Flutter CLI 按以下步骤以 `Debug` 模式启动 Xcode scheme: + 1. In your IDE, start the iOS simulator. + 在 IDE 中启动 iOS 模拟器。 + 1. In the console, navigate to the `flavors_example` directory and enter the following command: + 在控制台中进入 `flavors_example` 目录,并输入以下命令: + ```console title="console" $ flutter run --flavor ``` @@ -233,16 +350,26 @@ Flutter CLI using the following steps: your Xcode scheme (for example, `staging` or `production`). + ``:替换为你的 Xcode scheme 名称(例如 `staging` 或 `production`)。 + Example: + 示例: + ```console title="console" $ flutter run --flavor staging ``` ### Access the current flavor +### 访问当前 flavor + 1. **Import the services library:** To access the `appFlavor` constant, add the following import to your Dart file: + + **导入 services 库:** + 要访问 `appFlavor` 常量,在 Dart 文件中添加以下 import: + ```dart import 'package:flutter/services.dart'; ``` @@ -250,6 +377,9 @@ Flutter CLI using the following steps: 1. **Check the flavor value:** Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configurations: + **检查 flavor 值:** + 在应用逻辑(通常在 `main()` 中)使用 `appFlavor` 常量处理 flavor 特定的配置: + ```dart void main() { // appFlavor will match the name of the Xcode scheme @@ -267,62 +397,102 @@ Flutter CLI using the following steps: :::note The value of `appFlavor` matches the name of the Xcode scheme you defined (for example, `staging` or `production`). If no flavor is specified during the build, `appFlavor` returns `null`. + +`appFlavor` 的值与你定义的 Xcode scheme 名称一致(如 `staging` 或 `production`)。构建时未指定 flavor 则 `appFlavor` 返回 `null`。 ::: ### Use the run command (Xcode) +### 使用 run 命令(Xcode) + You can launch a specific scheme in Xcode using the following steps: +可按以下步骤在 Xcode 中启动特定 scheme: + 1. Select the scheme you want to test (**Product > Schemes > Choose scheme**). + 选择你想要测试的 scheme(**Product > Schemes > Choose scheme**)。 + 1. Next to the scheme name in the toolbar, select the device you want to test against. + 在工具栏中 scheme 名称旁边,选择你想要测试的设备。 + 1. Run the scheme for your app (**Product > Run**). + 为你的应用运行该 scheme(**Product > Run**)。 + ## Customize configurations +## 自定义配置 + After you've added Xcode schemes, you can customize them for your iOS app. You can also use these steps to configure a macOS project by replacing any reference to `iOS` with `macOS`. +添加 Xcode scheme 后,可为 iOS 应用自定义。将 `iOS` 替换为 `macOS` 也可配置 macOS 项目。 + ### Create distinct app display names {: #create_a_distinct_app_display_name } +### 创建不同的应用显示名称 {: #create_a_distinct_app_display_name } + If you have multiple schemes, a distinct app name can quickly identify which scheme your deployed app is using. +若有多个 scheme,不同的应用名称可快速识别已部署应用使用的 scheme。 + Rename a Flutter flavor The following steps show how to add distinct app display names in Xcode for two schemes called `staging` and `production` in a project called `flavors_example`. +以下步骤说明如何在名为 `flavors_example` 的项目中,为 `staging` 和 `production` 两个 scheme 在 Xcode 中添加不同的应用显示名称。 + 1. Create user-defined settings in Xcode: + 在 Xcode 中创建用户自定义设置: + * Open the **project navigator** (**View > Navigators > Project**). + 打开 **project navigator**(**View > Navigators > Project**)。 + * In the **project navigator**, at the top, select **Runner**. + 在 **project navigator** 顶部选择 **Runner**。 + * In the main window under **TARGETS**, select **Runner**. + 在主窗口的 **TARGETS** 下选择 **Runner**。 + * Open the **Build Settings** tab. + 打开 **Build Settings** tab。 + * To the left of the Basic tab, click **+** and select **Add User-Defined Setting**. + 在 Basic tab 左侧,点击 **+** 并选择 **Add User-Defined Setting**。 + * Create a setting named `APP_DISPLAY_NAME`. + 创建一个名为 `APP_DISPLAY_NAME` 的设置。 + * Expand the **APP_DISPLAY_NAME** setting. + 展开 **APP_DISPLAY_NAME** 设置。 + * Assign the following values to the following keys: + 为以下键分配对应的值: + * **Debug-production**: `Flavors prod` * **Debug-staging**: `Flavors staging` * **Profile-production**: `Flavors prod` @@ -332,13 +502,20 @@ names in Xcode for two schemes called `staging` and 1. Update `Info.plist` in Xcode: + 在 Xcode 中更新 `Info.plist`: + * In the project navigator, select **Runner > Runner > Info** to open `flavor_test/ios/Runner/Info.plist`. + 在项目导航器中,选择 **Runner > Runner > Info** 以打开 + `flavor_test/ios/Runner/Info.plist`。 + * Under **Information Property List**, find the following key and update the value for it: + 在 **Information Property List** 下,找到以下键并更新其值: + * **Key**: `CFBundleDisplayName` * **Value**: `$(APP_DISPLAY_NAME)` @@ -347,70 +524,117 @@ names in Xcode for two schemes called `staging` and changed for each. To launch a scheme, see the steps in [Launch an Xcode scheme][]. + 为每个 scheme(`staging`、`production`)启动应用,确认每个 scheme 的应用显示名称都已改变。 + 要启动某个 scheme,请参阅 [启动 Xcode scheme][Launch an Xcode scheme] 中的步骤。 + [Launch an Xcode scheme]: #launch-an-xcode-scheme ### Create distinct icons +### 创建不同的图标 + If you have multiple schemes, a distinct icon for each configuration can help you quickly identify which scheme your deployed app is using. +若有多个 scheme,为各配置使用不同图标有助于快速识别已部署应用使用的 scheme。 + Rename a Flutter flavor The following steps show how to add a distinct icon in Xcode for two schemes called `staging` and `production` in an iOS project called `flavors_example`. +以下步骤说明如何在名为 `flavors_example` 的 iOS 项目中,为 `staging` 和 `production` 两个 scheme 在 Xcode 中添加不同图标。 + 1. Prepare your icons: + 准备你的图标: + * Design your staging icon and production icon in the design tool of your choice. + 用你喜欢的设计工具设计 staging 图标和 production 图标。 + * Generate versions of the staging icon and production icon in the sizes that you need. Save them in PNG format. + 生成你所需尺寸的 staging 图标和 production 图标,并以 PNG 格式保存。 + :::note You can use a tool like [App Icon Generator][] to generate the versions of your icons. + + 可使用 [App Icon Generator][] 等工具生成各尺寸图标。 ::: 1. Add the icons to your Xcode project: + 将图标添加到你的 Xcode 项目: + * Open the **project navigator** (**View > Navigators > Project**). + 打开 **project navigator**(**View > Navigators > Project**)。 + * In the **project navigator**, select **Runner > Runner > Assets** to open the **Assets** window. + 在 **project navigator** 中,选择 **Runner > Runner > Assets** 以打开 + **Assets** 窗口。 + * Complete the following steps for the staging icon: + 对 staging 图标完成以下步骤: + * Click **+ > iOS > iOS App icon**. + 点击 **+ > iOS > iOS App icon**。 + * Name the icon `AppIcon-staging`. + 将图标命名为 `AppIcon-staging`。 + * Drag your staging icons into the **AppIcon-staging** window and make sure the icons are assigned to the correct sizes. + 将你的 staging 图标拖入 **AppIcon-staging** 窗口, + 并确保这些图标已分配到正确的尺寸。 + * Repeat the previous step for the production icon. + 对 production 图标重复上一步骤。 + 1. Connect the icons to your schemes: + 将图标关联到你的 scheme: + * Open the **project navigator**. + 打开 **project navigator**。 + * In the main window under **TARGETS**, select **Runner**. + 在主窗口的 **TARGETS** 下选择 **Runner**。 + * Open the **General** tab if it's not already open. + 若 **General** tab 尚未打开,请将其打开。 + * Go to the **Apps Icons and Launch Screen** section and expand it. + 进入 **Apps Icons and Launch Screen** 部分并展开它。 + * To the right of the **App icon** field, click **+** and update the fields as follows: + 在 **App icon** 字段右侧,点击 **+** 并按如下方式更新各字段: + * **Debug-staging**: `AppIcon-staging` * **Profile-staging**: `AppIcon-staging` * **Release-staging**: `AppIcon-staging` @@ -423,11 +647,16 @@ an iOS project called `flavors_example`. changed for each. To launch a scheme, see the steps in [Launch an Xcode scheme][]. + 为每个 scheme(`staging`、`production`)启动应用,确认每个 scheme 的应用图标都已改变。 + 要启动某个 scheme,请参阅 [启动 Xcode scheme][Launch an Xcode scheme] 中的步骤。 + [Launch an Xcode scheme]: #launch-an-xcode-scheme [App Icon Generator]: https://www.appicon.co/ ### Add distinct bundle identifiers +### 添加不同的 bundle identifier + A bundle identifier is a unique identifier for your application on Apple's platforms. If you are using multiple Xcode schemes as Flutter flavors, you can have Apple treat @@ -437,26 +666,47 @@ This allows you to test new features or bug fixes in one version of the app (for example `staging`) without affecting another version of the app (for example, `production`). +bundle identifier 是应用在 Apple 平台上的唯一标识。 +若将多个 Xcode scheme 用作 Flutter flavor, +可让 Apple 将每个 scheme 视为独立应用, +需为每个 scheme 分配不同的 bundle identifier, +这样可在某一版本(如 `staging`)测试新功能或修复而不影响另一版本(如 `production`)。 + The following steps show how to set a unique bundle identifier for two Xcode schemes called `staging` and `production` in an iOS project called `flavors_example`. +以下步骤说明如何在名为 `flavors_example` 的 iOS 项目中 +为 `staging` 和 `production` 两个 Xcode scheme 设置唯一的 bundle identifier。 + 1. In Xcode, open the **project navigator** (**View > Navigators > Project**). + 在 Xcode 中打开 **project navigator**(**View > Navigators > Project**)。 + 1. In the main window under **TARGETS**, select **Runner**. + 在主窗口的 **TARGETS** 下选择 **Runner**。 + 1. Open the **Build Settings** tab. + 打开 **Build Settings** tab。 + 1. Navigate to the **Packaging** section. + 进入 **Packaging** 部分。 + 1. Expand the **Product Bundle Identifier** setting to see the different build configurations. + 展开 **Product Bundle Identifier** 设置,查看不同的构建配置。 + 1. For each scheme's build configuration, set the desired bundle identifier. For example: + 为每个 scheme 的构建配置设置所需的 bundle identifier。例如: + * Debug-staging, Profile-staging, Release-staging: `com.example.flavorsExample.staging` @@ -469,10 +719,15 @@ and `production` in an iOS project called `flavors_example`. 1. Ensure that these bundle identifiers are included in your App ID and your App ID is [registered in your Apple Developer account][]. + 确保这些 bundle identifier 已包含在你的 App ID 中,并且你的 App ID 已 + [在你的 Apple Developer 账号中注册][registered in your Apple Developer account]。 + [registered in your Apple Developer account]: https://developer.apple.com/help/account/identifiers/register-an-app-id/ ### Bundle assets +### 打包资源 + If you have assets that are only used in a specific flavor in your app, you can configure them to only be bundled into your app when launching that flavor. This prevents your @@ -482,25 +737,40 @@ to the `assets` field in your project's pubspec. To learn more, see the [`assets` field][] in [Flutter pubspec options][]. +若资源仅在应用的特定 flavor 中使用,可配置为仅在该 flavor 启动时打包,避免未使用资源增大包体积。为每个 flavor 打包资源,请在项目 pubspec 的 `assets` 字段添加 `flavors` 子字段。详见 [Flutter pubspec options][] 中的 [`assets` field][]。 + [`assets` field]: /tools/pubspec#assets [Flutter pubspec options]: /tools/pubspec ### Update Podfiles +### 更新 Podfile + If you are creating new Xcode schemes for a Flutter iOS project and you have an iOS Podfile in an existing Flutter project, you must update the Flutter iOS Podfile to match the changes you made in Xcode. +若为 Flutter iOS 项目创建新 Xcode scheme 且现有 Flutter 项目有 iOS Podfile,必须更新 Flutter iOS Podfile 以匹配 Xcode 中的更改。 + The following steps show how to update your iOS Podfile to include two new Xcode schemes called `staging` and `production` in a Flutter project called `flavors_example`. You can also use these steps to update a macOS project by replacing any reference to `iOS` with `macOS`. +以下步骤说明如何在名为 `flavors_example` 的 Flutter 项目中 +更新 iOS Podfile 以包含 `staging` 和 `production` 两个新 Xcode scheme。 +将 `iOS` 替换为 `macOS` 也可用于 macOS 项目。 + 1. In your IDE, open the `ios/Podfile` file. + + 在你的 IDE 中打开 `ios/Podfile` 文件。 + 1. Make the following updates and save your changes. + 做出以下更新并保存改动。 + ```ruby title="flavors_example/ios/Podfile" project 'Runner', { ... @@ -518,6 +788,8 @@ project by replacing any reference to `iOS` with `macOS`. ### Add unique build settings +### 添加唯一构建设置 + You can use [build settings][] to govern your iOS build process from compilation and linking to debugging and distribution. One way that you can use build settings @@ -526,6 +798,10 @@ to Xcode build configurations. For example, you might want to assign different API URLs to `Debug-staging` and `Debug-production`. For example: +可使用 [build settings][](构建设置)管理从编译、链接到调试和分发的 iOS 构建流程。 +将构建设置用于 Flutter flavor 的一种方式是为 Xcode 构建配置分配这些设置, +例如为 `Debug-staging` 和 `Debug-production` 分配不同的 API URL。例如: + ```plaintext title="debug-staging-settings.xcconfig" # Debug-staging build settings API_BASE_URL = staging.flavors.com/api @@ -540,27 +816,44 @@ If you would like to add additional build settings for a specific build configuration, see Apple's [Adding a build configuration file to your project][]. +若要为特定构建配置添加更多构建设置, +请参阅 Apple 的 [Adding a build configuration file to your project][](向项目添加构建配置文件)。 + [build settings]: https://developer.apple.com/documentation/xcode/build-settings-reference/ [Adding a build configuration file to your project]: https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project ### Add additional customizations +### 添加更多自定义项 + This document contains a few common Xcode scheme configurations, but there are many more that you can apply. To learn about them, see [Customizing the build schemes for a project][]. +本文档包含若干常见 Xcode scheme 配置,还可应用更多配置。 +详见 [Customizing the build schemes for a project][](自定义项目的构建 scheme)。 + [Customizing the build schemes for a project]: https://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project ## More information +## 更多信息 + For more information on creating and using flavors, check out the following resources: +有关创建和使用 flavor 的更多信息,请参阅以下资源: + * [How to set up Flutter & Firebase with Multiple Flavors using the FlutterFire CLI][flutterfire-cli] + + [使用 FlutterFire CLI 配置多 Flavor 的 Flutter 与 Firebase][flutterfire-cli] + * [Build flavors in Flutter (Android and iOS) with different Firebase projects per flavor Flutter Ready to Go][flavors-firebase] + [Flutter 中按 flavor 使用不同 Firebase 项目的构建 flavor(Android 与 iOS)][flavors-firebase] + [flutterfire-cli]: https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/ [flavors-firebase]: {{site.medium}}/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b diff --git a/sites/docs/src/content/deployment/flavors.md b/sites/docs/src/content/deployment/flavors.md index d029f82f16..bbf160670f 100644 --- a/sites/docs/src/content/deployment/flavors.md +++ b/sites/docs/src/content/deployment/flavors.md @@ -8,34 +8,50 @@ shortTitle: Flavors (Android) description: 如何使用 flavor 配置多渠道构建。 tags: 发布应用,跨平台,Android keywords: 配置flavor +ai-translated: true --- This guide shows you how to create Flutter flavors for an Android app. +本指南将向你展示如何为 Android 应用创建 Flutter flavor。 + ## Overview +## 概览 + A Flutter flavor when used with Android represents a unified term for various platform-specific features. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app. +Flutter flavor 用于 Android 时,是各种平台特定特性的统一称呼。 +例如,一个 flavor 可以决定应用某个特定版本所关联的图标、应用名称、 +API key、功能开关 (feature flag) 以及日志级别。 + If you want to create Flutter flavors for an Android app, you can do this in Flutter. In Android, a Flutter flavor is referred to as a [_product flavor_][]. +如果你想为 Android 应用创建 Flutter flavor,可以在 Flutter 中完成。 +在 Android 中,Flutter flavor 被称为 [**产品 flavor (product flavor)**][_product flavor_]。 + The following illustrates an example of the Android [_build variants_] that are created when an Android app has two product flavors (`staging`, `production`) and two build types (`debug`, `release`): +下面演示了一个示例:当 Android 应用拥有两个产品 flavor(`staging`、`production`) +和两种构建类型(`debug`、`release`)时,所生成的 Android +[**构建变体 (build variants)**][_build variants_]: +
SchemeConfigurations for the schemeConfigurations for the scheme该 scheme 的配置
staging - Debug-staging
- Profile-staging
- Release-staging
+ Debug-staging
+ Profile-staging
+ Release-staging
production - Debug-production
- Profile-production
- Release-production
+ Debug-production
+ Profile-production
+ Release-production
- - - + + + @@ -63,16 +79,26 @@ types (`debug`, `release`): ## Configure your product flavors {: #using-flavors-in-android } +## 配置你的产品 flavor + Complete the following steps to add two Android product flavors called `staging` and `production` to a new Flutter project called `flavors_example`, and then test your project to make sure that the flavors work as expected. +完成以下步骤,向一个名为 `flavors_example` 的新 Flutter 项目添加两个名为 +`staging` 和 `production` 的 Android 产品 flavor,然后测试你的项目, +确保这些 flavor 按预期工作。 + 1. Create a new Flutter project called `flavors_example` with Kotlin as the preferred Android language. By default, the project includes the `debug` and `release` Android build types. + 创建一个名为 `flavors_example` 的新 Flutter 项目, + 并将 Kotlin 作为首选的 Android 语言。默认情况下, + 该项目包含 `debug` 和 `release` 两种 Android 构建类型。 + ```console title="console" $ flutter create --android-language kotlin flavors_example ``` @@ -80,15 +106,25 @@ to make sure that the flavors work as expected. 1. Add the product flavors called `staging` and `production` to the `flavors_example` project. + 将名为 `staging` 和 `production` 的产品 flavor 添加到 + `flavors_example` 项目中。 + * In the `flavors_example` project, navigate to the `android/app/` directory and open `build.gradle.kts`. + 在 `flavors_example` 项目中,进入 `android/app/` + 目录并打开 `build.gradle.kts`。 + * Add the `flavorsDimension` property and the `productFlavors` properties inside of the `android {} block`. Make sure that the `android {}` block also contains the default `debug` and `release` build types: + 在 `android {}` 块内添加 `flavorsDimension` 属性和 + `productFlavors` 属性。确保 `android {}` 块同时包含默认的 + `debug` 和 `release` 构建类型: + ```kotlin title="build.gradle.kts" android { ... @@ -117,39 +153,62 @@ to make sure that the flavors work as expected. haven't changed, but you do want to make sure that the app can run. + 为确保一切设置正确,请在这些 Android 产品 flavor 上运行你的应用。 + 由于配置设置尚未改变,你不会看到任何差异,但你需要确认应用可以正常运行。 + * Start an Android emulator or connect a physical device with developer options enabled. + 启动一个 Android 模拟器,或连接一台已启用开发者选项的真机。 + * In the console, navigate to the `flavors_example` directory and enter the following command to test the `staging` flavor: + 在控制台中,进入 `flavors_example` 目录并输入以下命令来测试 + `staging` flavor: + ```console title="console" $ flutter run --flavor staging ``` * Repeat the previous step for the `production` flavor. + 对 `production` flavor 重复上一步骤。 + 1. If everything runs, you're ready to customize your configurations. For more information, see [Customize configurations][]. + 如果一切都能运行,你就可以开始自定义配置了。更多信息请参阅 + [自定义配置][Customize configurations]。 + [Customize configurations]: #customize-configurations ## Launch a flavor {: #launching-your-app-flavors } +## 启动某个 flavor + After you've created the product flavors for an Android app, you can launch a specific product flavor through Flutter. +为 Android 应用创建产品 flavor 后,你可以通过 Flutter 启动某个特定的产品 flavor。 + You can launch a product flavor with the Flutter CLI using the following steps: +你可以通过以下步骤使用 Flutter CLI 启动某个产品 flavor: + 1. Start an Android emulator or connect a physical device with developer options enabled. + 启动一个 Android 模拟器,或连接一台已启用开发者选项的真机。 + 1. In the console, navigate to the `flavors_example` directory and enter the following command: + 在控制台中,进入 `flavors_example` 目录并输入以下命令: + ```console title="console" $ flutter (run | build ) --flavor ``` @@ -159,25 +218,48 @@ $ flutter (run | build ) --flavor * `build`: Builds either an APK or an appbundle. * ``: Either `apk` or `appbundle`. + `(run | build )`:替换为以下之一: + * `run`:以 debug 模式运行应用。 + * `build`:构建 APK 或 appbundle。 + * ``:`apk` 或 `appbundle`。 + * ``: Replace this with the name of your Android product flavor (for example: `staging`, `production`). + ``:替换为你的 Android 产品 flavor 名称(例如:`staging`、`production`)。 + Example: +例如: + ```console title="console" $ flutter build apk --flavor staging ``` ## Use flavors in Flutter code +## 在 Flutter 代码中使用 flavor + After you've configured your product flavors, you can change your app's behavior—such as pointing to different API endpoints or changing the theme—based on the active flavor. +配置好产品 flavor 后,你可以根据当前生效的 flavor 来改变应用的行为—— +例如指向不同的 API 端点或更换主题。 + The Flutter framework provides the `appFlavor` constant, which retrieves the name of the current flavor as a `String`. This value matches the flavor name passed to the `--flavor` flag during the `flutter run` or `flutter build` process. +Flutter 框架提供了 `appFlavor` 常量,它以 `String` 形式获取当前 flavor 的名称。 +该值与 `flutter run` 或 `flutter build` 过程中传给 `--flavor` 参数的 flavor 名称一致。 + ### Access the current flavor +### 访问当前 flavor + 1. **Import the services library:** To access the `appFlavor` constant, add the following import to your Dart file: + + **导入 services 库:** + 要访问 `appFlavor` 常量,请在你的 Dart 文件中添加以下导入: + ```dart import 'package:flutter/services.dart'; ``` @@ -185,6 +267,9 @@ The Flutter framework provides the `appFlavor` constant, which retrieves the nam 1. **Check the flavor value:** Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configurations: + **检查 flavor 的值:** + 在你的应用逻辑中(通常在 `main()` 内)使用 `appFlavor` 常量来处理特定于 flavor 的配置: + ```dart void main() { // appFlavor will match the flavor name from build.gradle.kts @@ -202,34 +287,56 @@ The Flutter framework provides the `appFlavor` constant, which retrieves the nam :::note The value of `appFlavor` matches the name of the product flavor you defined in your `build.gradle.kts` file (for example, `staging` or `production`). If no flavor is specified during the build, `appFlavor` returns `null`. + + `appFlavor` 的值与你在 `build.gradle.kts` 文件中定义的产品 flavor 名称一致 + (例如 `staging` 或 `production`)。如果构建时未指定 flavor,`appFlavor` 返回 `null`。 ::: ## Customize configurations +## 自定义配置 + After you've added product flavors, you can customize them for your Android app. +添加产品 flavor 后,你可以为 Android 应用对它们进行自定义。 + ### Create a distinct app display name +### 创建独特的应用显示名称 + If you have multiple product flavors, a distinct app name can quickly identify which flavor your deployed app is using. +如果你有多个产品 flavor,一个独特的应用名称能让你快速识别已部署的应用使用的是哪个 flavor。 + ![Distinct app names in menu](/assets/images/docs/flavors/flavors-android-app-names-1.png){:width="40%"} The following steps show how to add distinct app display names for two product flavors called `staging` and `production` in a project called `flavors_example`. +以下步骤演示如何在名为 `flavors_example` 的项目中,为 `staging` 和 +`production` 两个产品 flavor 添加独特的应用显示名称。 + 1. Update `build.gradle.kts` in your IDE: + 在你的 IDE 中更新 `build.gradle.kts`: + * In the `flavors_example` project, navigate to the `android/app/` directory and open `build.gradle.kts`. + 在 `flavors_example` 项目中,进入 `android/app/` + 目录并打开 `build.gradle.kts`。 + * In the `flavorsDimension` block, add a `resValue()` property called `app_name` to the `staging` and `production` flavors: + 在 `flavorsDimension` 块中,为 `staging` 和 `production` + flavor 添加一个名为 `app_name` 的 `resValue()` 属性: + ```kotlin title="build.gradle.kts" android { ... @@ -256,12 +363,19 @@ names for two product flavors called `staging` and 1. Update `AndroidManifest.xml` in your IDE: + 在你的 IDE 中更新 `AndroidManifest.xml`: + * In the `flavors_example` project, navigate to `android/app/src/main` and open `AndroidManifest.xml`. + 在 `flavors_example` 项目中,进入 `android/app/src/main` + 并打开 `AndroidManifest.xml`。 + * Replace the value for `android:label` with `@string/app_name`. + 将 `android:label` 的值替换为 `@string/app_name`。 + ```xml title="AndroidManifest.xml" ``` ## Overview of snapcraft +## snapcraft 概览 + The `snapcraft` tool builds snaps based on the instructions listed in a `snapcraft.yaml` file. To get a basic understanding of snapcraft and its @@ -96,13 +136,23 @@ and the [Introduction to snapcraft][]. Additional links and information are listed at the bottom of this page. +`snapcraft` 工具会根据 `snapcraft.yaml` 文件中列出的指令来构建 snap。 +若想对 snapcraft 及其核心概念有基本的了解,请查阅 +[Snap 文档][Snap documentation] 与 [snapcraft 简介][Introduction to snapcraft]。 +本页底部还列出了更多链接与信息。 + ## Flutter snapcraft.yaml example +## Flutter snapcraft.yaml 示例 + Place the YAML file in your Flutter project under `/snap/snapcraft.yaml`. (And remember that YAML files are sensitive to white space!) For example: +将该 YAML 文件放在你 Flutter 项目的 `/snap/snapcraft.yaml` 路径下。 +(并且记住,YAML 文件对空白字符很敏感!)例如: + ```yaml name: super-cool-app version: 0.1.0 @@ -136,12 +186,18 @@ parts: The following sections explain the various pieces of the YAML file. +以下各节将解释该 YAML 文件的各个组成部分。 + ### Metadata +### 元数据 + This section of the `snapcraft.yaml` file defines and describes the application. The snap version is derived (adopted) from the build section. +`snapcraft.yaml` 文件的这一部分用于定义和描述应用。snap 的版本号取自(沿用)构建部分。 + ```yaml name: super-cool-app version: 0.1.0 @@ -151,8 +207,12 @@ description: Super Cool App that does everything! ### Grade, confinement, and base +### Grade、confinement 与 base + This section defines how the snap is built. +这一部分定义 snap 的构建方式。 + ```yaml confinement: strict base: core22 @@ -160,28 +220,42 @@ grade: stable ``` **Grade** -: Specifies the quality of the snap; this is relevant for +
Specifies the quality of the snap; this is relevant for the publication step later. +**Grade** +
指定 snap 的质量等级;这与后续的发布步骤相关。 + **Confinement** -: Specifies what level of system resource access the snap +
Specifies what level of system resource access the snap will have once installed on the end-user system. Strict confinement limits the application access to specific resources (defined by plugs in the `app` section). +**Confinement** +
指定 snap 安装到终端用户系统后所拥有的系统资源访问级别。 + strict(严格)限制会将应用的访问限制在特定资源上(由 `app` 部分中的 plug 定义)。 + **Base** -: Snaps are designed to be self-contained applications, +
Snaps are designed to be self-contained applications, and therefore, they require their own private core root filesystem known as `base`. The `base` keyword specifies the version used to provide the minimal set of common libraries, and mounted as the root filesystem for the application at runtime. +**Base** +
snap 被设计为自包含的应用,因此它们需要拥有自己私有的核心根文件系统,即 `base`。 + `base` 关键字指定用于提供最小公共库集合的版本,并在运行时作为应用的根文件系统挂载。 + ### Apps This section defines the application(s) that exist inside the snap. There can be one or more applications per snap. This example has a single application—super_cool_app. +这一部分定义 snap 内部存在的应用。每个 snap 可以有一个或多个应用。 +本示例只有一个应用——super_cool_app。 + ```yaml apps: super-cool-app: @@ -190,11 +264,14 @@ apps: ``` **Command** -: Points to the binary, relative to the snap's root, +
Points to the binary, relative to the snap's root, and runs when the snap is invoked. +**Command** +
指向相对于 snap 根目录的二进制文件,在 snap 被调用时运行。 + **Extensions** -: A list of one or more extensions. Snapcraft extensions +
A list of one or more extensions. Snapcraft extensions are reusable components that can expose sets of libraries and tools to a snap at build and runtime, without the developer needing to have specific knowledge @@ -202,15 +279,23 @@ apps: the GTK 3 libraries to the Flutter snap. This ensures a smaller footprint and better integration with the system. +**Extensions** +
一个或多个扩展的列表。Snapcraft 扩展是可复用的组件, + 能在构建和运行时向 snap 暴露成组的库与工具,而开发者无需了解所含框架的具体细节。 + `gnome` 扩展会向 Flutter snap 暴露 GTK 3 库,从而确保更小的体积以及与系统更好的集成。 **Plugs** -: A list of one or more plugs for system interfaces. +
A list of one or more plugs for system interfaces. These are required to provide necessary functionality when snaps are strictly confined. This Flutter snap needs access to the network. +**Plugs** +
一个或多个系统接口 plug 的列表。当 snap 处于严格限制状态时,需要这些 plug 来提供必要的功能。 + 这个 Flutter snap 需要访问网络。 + **DBus interface** -: The [DBus interface][] provides a way for snaps to +
The [DBus interface][] provides a way for snaps to communicate over DBus. The snap providing the DBus service declares a slot with the well-known DBus name and which bus it uses. Snaps wanting to communicate @@ -221,6 +306,13 @@ apps: snap to the store and request a manual review and a reviewer will take a look). +**DBus interface** +
[DBus 接口][DBus interface] 为 snap 提供了一种通过 DBus 通信的方式。 + 提供 DBus 服务的 snap 会声明一个带有知名 DBus 名称及其所用总线的 slot。 + 想要与提供方 snap 的服务通信的 snap,则为该提供方 snap 声明一个 plug。 + 请注意,你的 snap 需要一份 snap 声明,才能通过 snap store 分发并占用这个知名 DBus 名称 + (只需将 snap 上传到商店并申请人工审核,审核人员便会查看)。 + When a providing snap is installed, snapd will generate security policy that will allow it to listen on the well-known DBus name on the specified @@ -233,6 +325,11 @@ apps: snaps might only communicate with the providing snap by connecting the snaps' interface. + 当提供方 snap 被安装时,snapd 会生成安全策略,允许它在指定总线上监听该知名 DBus 名称。 + 如果指定的是系统总线,snapd 还会生成 DBus 总线策略,允许 'root' 占用该名称, + 并允许任何用户与该服务通信。非 snap 进程可在通过传统权限检查后与提供方 snap 通信。 + 其他(消费方)snap 则只能通过连接 snap 的接口来与提供方 snap 通信。 + ```plaintext dbus-super-cool-app: # adjust accordingly to your app name interface: dbus @@ -245,20 +342,31 @@ dbus-super-cool-app: # adjust accordingly to your app name This section defines the sources required to assemble the snap. +这一部分定义组装 snap 所需的来源。 + Parts can be downloaded and built automatically using plugins. Similar to extensions, snapcraft can use various plugins (such as Python, C, Java, and Ruby) to assist in the building process. Snapcraft also has some special plugins. +part 可以使用插件自动下载并构建。与扩展类似,snapcraft 可以使用各种插件 +(如 Python、C、Java 和 Ruby)来辅助构建过程。snapcraft 还有一些特殊的插件。 + **nil** plugin -: Performs no action and the actual build process is +
Performs no action and the actual build process is handled using a manual override. +**nil** 插件 +
不执行任何操作,实际的构建过程通过手动覆盖来处理。 + **flutter** plugin -: Provides the necessary Flutter SDK tools so you can +
Provides the necessary Flutter SDK tools so you can use it without having to manually download and set up the build tools. +**flutter** 插件 +
提供必要的 Flutter SDK 工具,让你无需手动下载和配置构建工具即可使用。 + ```yaml parts: super-cool-app: @@ -267,9 +375,9 @@ parts: flutter-target: lib/main.dart # The main entry-point file of the application ``` - ## Desktop file and icon +## 桌面文件与图标 Desktop entry files are used to add an application to the desktop menu. These files specify the name and @@ -278,16 +386,27 @@ related search keywords and more. These files have the extension .desktop and follow the XDG Desktop Entry Specification version 1.1. +桌面入口文件用于将应用添加到桌面菜单。这些文件指定应用的名称和图标、所属类别、 +相关的搜索关键字等等。这些文件的扩展名为 .desktop,遵循 XDG Desktop Entry 规范 1.1 版。 + ### Flutter super-cool-app.desktop example +### Flutter super-cool-app.desktop 示例 + Place the .desktop file in your Flutter project under `/snap/gui/super-cool-app.desktop`. +将 .desktop 文件放在你 Flutter 项目的 `/snap/gui/super-cool-app.desktop` 路径下。 + **Notice**: icon and .desktop file name must be the same as your app name in yaml file! +**注意**:图标和 .desktop 文件名必须与 yaml 文件中你的应用名称一致! + For example: +例如: + ```yaml [Desktop Entry] Name=Super Cool App @@ -302,112 +421,198 @@ Categories=Education; # Adjust accordingly your snap category. Place your icon with .png extension in your Flutter project under `/snap/gui/super-cool-app.png`. +将扩展名为 .png 的图标放在你 Flutter 项目的 `/snap/gui/super-cool-app.png` 路径下。 ## Build the snap +## 构建 snap + Once the `snapcraft.yaml` file is complete, run `snapcraft` as follows from the root directory of the project. +`snapcraft.yaml` 文件完成后,从项目根目录按以下方式运行 `snapcraft`。 + To use the Multipass VM backend: +要使用 Multipass 虚拟机后端: + ```console $ snapcraft ``` To use the LXD container backend: +要使用 LXD 容器后端: + ```console $ snapcraft --use-lxd ``` ## Test the snap +## 测试 snap + Once the snap is built, you'll have a `.snap` file in your root project directory. +snap 构建完成后,你的项目根目录下会出现一个 `.snap` 文件。 + $ sudo snap install ./super-cool-app_0.1.0_amd64.snap --dangerous ## Publish +## 发布 + You can now publish the snap. The process consists of the following: +现在你可以发布该 snap 了。整个过程包含以下步骤: + 1. Create a developer account at [snapcraft.io][], if you haven't already done so. + + 若你尚未创建,请在 [snapcraft.io][] 创建一个开发者账号。 + 1. Register the app's name. Registration can be done either using the Snap Store Web UI portal, or from the command line, as follows: + + 注册应用名称。注册可以通过 Snap Store 的 Web UI 门户完成, + 也可以通过命令行完成,如下: + ```console $ snapcraft login $ snapcraft register ``` + 1. Release the app. After reading the next section to learn about selecting a Snap Store channel, push the snap to the store: + + 发布应用。 + 阅读下一节了解如何选择 Snap Store 渠道后, + 将 snap 推送到商店: + ```console $ snapcraft upload --release= .snap ``` ### Snap Store channels +### Snap Store 渠道 + The Snap Store uses channels to differentiate among different versions of snaps. +Snap Store 使用渠道来区分 snap 的不同版本。 + The `snapcraft upload` command uploads the snap file to the store. However, before you run this command, you need to learn about the different release channels. Each channel consists of three components: +`snapcraft upload` 命令会将 snap 文件上传到商店。不过,在运行该命令之前, +你需要先了解不同的发布渠道。每个渠道由三个部分组成: + **Track** -: All snaps must have a default track called latest. +
All snaps must have a default track called latest. This is the implied track unless specified otherwise. +**Track** +
所有 snap 都必须有一个名为 latest 的默认 track(轨道)。除非另行指定,否则就是这个隐含的 track。 + **Risk** -: Defines the readiness of the application. +
Defines the readiness of the application. The risk levels used in the snap store are: `stable`, `candidate`, `beta`, and `edge`. +**Risk** +
定义应用的成熟度。snap store 使用的 risk(风险)级别有: + `stable`、`candidate`、`beta` 和 `edge`。 + **Branch** -: Allows creation of short-lived snap +
Allows creation of short-lived snap sequences to test bug-fixes. +**Branch** +
允许创建短期存在的 snap 序列,用于测试 bug 修复。 + ### Snap Store automatic review +### Snap Store 自动审核 + The Snap Store runs several automated checks against your snap. There might also be a manual review, depending on how the snap was built, and if there are any specific security concerns. If the checks pass without errors, the snap becomes available in the store. +Snap Store 会对你的 snap 运行若干自动检查。根据 snap 的构建方式以及是否存在特定的安全顾虑, +还可能进行人工审核。如果检查无误通过,该 snap 便会在商店中上架。 + ## Additional snapcraft resources +## 更多 snapcraft 资源 + You can learn more from the following links on the [snapcraft.io][] site: +你可以从 [snapcraft.io][] 站点上的以下链接了解更多: + * [Channels][] + + [渠道 (Channels)][Channels] + * [Environment variables][] + + [环境变量 (Environment variables)][Environment variables] + * [Interface management][] + + [接口管理 (Interface management)][Interface management] + * [Parts environment variables][] + + [Parts 环境变量 (Parts environment variables)][Parts environment variables] + * [Releasing to the Snap Store][] + + [发布到 Snap Store (Releasing to the Snap Store)][Releasing to the Snap Store] + * [Snapcraft extensions][] + + [Snapcraft 扩展 (Snapcraft extensions)][Snapcraft extensions] + * [Supported plugins][] + [支持的插件 (Supported plugins)][Supported plugins] + ## Additional deployment resources +## 更多部署资源 + ### [fastforge][] > An all-in-one Flutter application packaging and distribution tool, providing you with a one-stop solution to meet various distribution needs. +> 一个集打包与分发于一体的 Flutter 应用工具, +为你提供满足各种分发需求的一站式解决方案。 + Supports popular packaging formats like, appimage, deb, pacman, rpm, and more. +支持 appimage、deb、pacman、rpm 等常见的打包格式。 + ### [flatpak-flutter][] > Flatpak manifest tooling for the offline build of Flutter apps. +> 用于离线构建 Flutter 应用的 Flatpak manifest 工具。 + Supports Flatpak preparation for publishing on [Flathub][]. +支持为在 [Flathub][] 上发布做 Flatpak 准备工作。 [Environment variables]: https://snapcraft.io/docs/environment-variables [Flutter wiki]: {{site.repo.flutter}}/tree/main/docs diff --git a/sites/docs/src/content/embedded/index.md b/sites/docs/src/content/embedded/index.md index 2070cd3ae3..5f206ca5be 100644 --- a/sites/docs/src/content/embedded/index.md +++ b/sites/docs/src/content/embedded/index.md @@ -1,20 +1,32 @@ --- -title: Embedded support for Flutter +# title: Embedded support for Flutter +title: Flutter 嵌入式支持 +# description: > +# Details of how Flutter supports the creation of embedded experiences. description: > - Details of how Flutter supports the creation of embedded experiences. + 介绍 Flutter 如何支持创建嵌入式体验。 +ai-translated: true --- If you would like to embed Flutter engine into a car, a refrigerator, a thermostat... you CAN! For example, you might embed Flutter in the following situations: +若你想把 Flutter 引擎嵌入汽车、冰箱、恒温器等设备,完全可以! +例如,你可能在以下场景中嵌入 Flutter: + * Using Flutter on an "embedded device", typically a low-powered hardware device such as a smart-display, a thermostat, or similar. + + 在「嵌入式设备」上使用 Flutter,通常是智能显示屏、恒温器等低功耗硬件设备。 + * Embedding Flutter into a new operating system or environment, for example a new mobile platform or a new operating system. + 将 Flutter 嵌入新的操作系统或环境,例如新的移动平台或新的操作系统。 + The ability to embed Flutter, while stable, uses low-level API and is _not_ for beginners. In addition to the resources listed below, you @@ -24,17 +36,38 @@ various aspects of Flutter. The Flutter [community][] page has info on more community resources. +嵌入 Flutter 的能力虽稳定,但使用底层 API,**不适合** 初学者。 +除下方列出的资源外,你也可以加入 [Discord][],Flutter 开发者(包括 Google 工程师)会在其中讨论 Flutter 的各个方面。 +Flutter [community][](社区)页面有更多社区资源信息。 + * [Custom Flutter Engine Embedders][], on the Flutter wiki. + + [Custom Flutter Engine Embedders][](自定义 Flutter 引擎嵌入器),见 Flutter wiki。 + * The doc comments in the [Flutter engine `embedder.h` file][] on GitHub. + + GitHub 上 [Flutter 引擎 `embedder.h` 文件][Flutter engine `embedder.h` file] 中的文档注释。 + * The [Flutter architectural overview][] on docs.flutter.dev. + + docs.flutter.dev 上的 [Flutter architectural overview][](架构概览)。 + * A small, self-contained [Flutter Embedder Engine GLFW example][] in the Flutter engine GitHub repo. + + Flutter 引擎 GitHub 仓库中的小型自包含 [Flutter Embedder Engine GLFW 示例][Flutter Embedder Engine GLFW example]。 + * An exploration into [embedding Flutter in a terminal][] by implementing Flutter's custom embedder API. + + 通过实现 Flutter 自定义嵌入器 API,探索 [在终端中嵌入 Flutter][embedding Flutter in a terminal]。 + * [Issue 31043][]: _Questions for porting flutter engine to a new os_ might also be helpful. + [Issue 31043][]:**Questions for porting flutter engine to a new os**(将 Flutter 引擎移植到新操作系统的问题)也可能有帮助。 + [community]: {{site.main-url}}/community [Discord]: https://discord.com/invite/N7Yshp4 diff --git a/sites/docs/src/content/flutter-for/compose-devs.md b/sites/docs/src/content/flutter-for/compose-devs.md index 3ac65633a6..a7deeb27c2 100644 --- a/sites/docs/src/content/flutter-for/compose-devs.md +++ b/sites/docs/src/content/flutter-for/compose-devs.md @@ -1,6 +1,9 @@ --- -title: Flutter for Jetpack Compose developers -description: Learn how to apply Jetpack Compose developer knowledge when building Flutter apps. +# title: Flutter for Jetpack Compose developers +title: 给 Jetpack Compose 开发者的 Flutter 指南 +# description: Learn how to apply Jetpack Compose developer knowledge when building Flutter apps. +description: 学习在构建 Flutter 应用时运用 Jetpack Compose 开发经验。 +ai-translated: true --- @@ -8,17 +11,27 @@ description: Learn how to apply Jetpack Compose developer knowledge when buildin :::note If you have experience building Android apps with Views (XML), check out [Flutter for Android developers][]. + +若你有使用 Views (XML) 构建 Android 应用的经验, +请参阅 [给 Android 开发者的 Flutter 指南][Flutter for Android developers]。 ::: Flutter is a framework for building cross-platform applications that uses the Dart programming language. +Flutter 是使用 Dart 编程语言构建跨平台应用的框架。 + Your Jetpack Compose knowledge and experience are highly valuable when building with Flutter. +你的 Jetpack Compose 知识与经验在构建 Flutter 时非常宝贵。 + :::tip To integrate Flutter code into an **existing** Android app, check out [Add Flutter to existing app][]. + +若要将 Flutter 代码集成到 **现有** Android 应用, +请参阅 [将 Flutter 添加到现有应用][Add Flutter to existing app]。 ::: This document can be used as a reference by jumping around @@ -27,21 +40,36 @@ This guide embeds sample code. By using the "Open in DartPad" button that appears on hover or focus, you can open and run some of the examples on DartPad. +本文档可跳转查阅,找到最符合你需求的问题。 +本指南嵌入示例代码;通过悬停或聚焦时出现的「Open in DartPad」按钮, +可在 DartPad 中打开并运行部分示例。 + ## Overview +## 概览 + Flutter and Jetpack Compose code describe how the UI looks and works. Developers call this type of code a _declarative framework_. +Flutter 与 Jetpack Compose 代码描述 UI 的外观与行为,开发者称此类代码为 **declarative framework(声明式框架)**。 + While there are key differences especially when it comes to interacting with legacy Android code, there are many commonalities between the two frameworks. +两者尤其在与传统 Android 代码交互方面存在关键差异,但框架之间也有许多共同点。 + ### Composables vs. Widgets +### Composable 与 Widget + **Jetpack Compose** represents UI components as _composable functions_, later noted in this document as _composables_. Composables can be altered or decorated through the use of _Modifier_ objects. +**Jetpack Compose** 将 UI 组件表示为 **composable functions(composable 函数)**, +本文档中简称 **composables**。可通过 **Modifier** 对象修改或装饰 Composable。 + ``` kotlin Text("Hello, World!", modifier: Modifier.padding(10.dp) @@ -52,13 +80,21 @@ Text("Hello, World!", **Flutter** represents UI components as _widgets_. +**Flutter** 将 UI 组件表示为 **widget**。 + Both composables and widgets only exist until they need to change. These languages call this property _immutability_. + +Composable 与 widget 仅在需要变更前存在,这种特性称为 **immutability(不可变性)**。 + Jetpack Compose modifies UI component properties using an optional _modifier_ property backed by a `Modifier` object. By contrast, Flutter widgets configure their properties directly through constructor parameters. +Jetpack Compose 通过由 `Modifier` 对象支持的 **modifier** 属性修改 UI 组件属性; +Flutter widget 则通过构造函数参数直接配置属性。 + ```dart Padding( // <-- This is a Widget padding: EdgeInsets.all(10.0), // <-- a parameter to Padding @@ -70,63 +106,112 @@ To compose layouts, both Jetpack Compose and Flutter nest UI components within one another. Jetpack Compose nests `Composables` while Flutter nests `Widgets`. +组合布局时,Jetpack Compose 与 Flutter 都将 UI 组件相互嵌套: +Jetpack Compose 嵌套 `Composable`,Flutter 嵌套 `Widget`。 + ### Layout process +### 布局过程 + Jetpack Compose and Flutter handle layout in similar ways. Both of them lay out the UI in a single pass and parent elements provide layout constraints down to their children. More specifically, +Jetpack Compose 与 Flutter 布局方式相似:单次传递布局 UI,父元素向子元素提供布局约束。更具体地说: + 1. The parent measures itself and its children recursively providing any constraints from the parent to the child. + + 父级递归测量自身与子级,将来自父级的约束传给子级。 + 2. The children try to size themselves using the above methods and provide their own children both their constraints and any that might apply from their ancestor nodes. + + 子级尝试按上述方法确定尺寸,并向自己的子级提供约束及可能来自祖先节点的约束。 + 3. Upon encountering a leaf node (a node with no children), the size and properties are determined based on the provided constraints and the element is placed in the UI. + + 遇到叶节点(无子节点)时,根据约束确定尺寸与属性并放置到 UI。 + 4. With all the children sized and placed, the root nodes can determine their measurement, size, and placement. + 所有子级确定尺寸并放置后,根节点可确定自身的测量、尺寸与位置。 + In both Jetpack Compose and Flutter, the parent component can override or constrain the child's desired size. The widget cannot have any size it wants. It also cannot _usually_ know or decide its position on screen as its parent makes that decision. +在 Jetpack Compose 与 Flutter 中,父组件可覆盖或约束子组件期望的尺寸; +widget 不能任意尺寸,也 **通常** 无法知晓或决定屏幕位置,由父组件决定。 + To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value. +要强制子 widget 以特定尺寸渲染,父级须设置 tight constraints(紧约束); +当最小尺寸等于最大尺寸时约束为紧约束。 + To learn how constraints work in Flutter, visit [Understanding constraints][]. +要了解 Flutter 约束机制,请参阅 [理解布局约束][Understanding constraints]。 + ### Design system +### 设计系统 + Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. While this guide features [Material][] widgets, your Flutter app can use many different design systems: +Flutter 面向多平台,应用不必遵循特定设计系统。本指南使用 [Material][] widget,但 Flutter 应用可采用多种设计系统: + - Custom Material widgets + + 自定义 Material widget + - Community built widgets + + 社区构建的 widget + - Your own custom widgets + 你自己的自定义 widget + If you're looking for a great reference app that features a custom design system, check out [Wonderous][]. +若要参考采用自定义设计系统的优秀应用,请参阅 [Wonderous][]。 + ## UI basics +## UI 基础 + This section covers the basics of UI development in Flutter and how it compares to Jetpack Compose. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more. +本节涵盖 Flutter UI 开发基础及其与 Jetpack Compose 的对比, +包括如何开始开发、显示静态文本、创建按钮、响应点击、显示列表与网格等。 + ### Getting started +### 入门 + For **Compose** apps, your main entry point will be _Activity_ or one of its descendants, generally _ComponentActivity_. +**Compose** 应用的主入口通常是 **Activity** 或其子类,一般为 **ComponentActivity**。 + ```kotlin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -157,6 +242,8 @@ fun Greeting(name: String, modifier: Modifier = Modifier) { To start your **Flutter** app, pass an instance of your app to the `runApp` function. +启动 **Flutter** 应用时,将应用实例传给 `runApp` 函数。 + ```dart void main() { runApp(const MyApp()); @@ -168,6 +255,9 @@ user interface it represents. It's common to begin your app with a [`WidgetApp`][] class, like [`MaterialApp`][]. +`App` 是 widget,其 `build` 方法描述所代表的用户界面部分。 +通常以 [`WidgetApp`][] 类(如 [`MaterialApp`][])开始应用。 + ```dart class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -184,6 +274,8 @@ class MyApp extends StatelessWidget { The widget used in the `HomePage` might begin with the `Scaffold` class. `Scaffold` implements a basic layout structure for an app. +`HomePage` 中使用的 widget 可能以 `Scaffold` 类开始,`Scaffold` 实现应用的基本布局结构。 + ```dart class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -203,21 +295,34 @@ class HomePage extends StatelessWidget { Note how Flutter uses the [`Center`][] widget. +注意 Flutter 使用 [`Center`][] widget。 + Compose has a number of defaults from its ancestor Android Views. Unless otherwise specified, most components "wrap" their size to content meaning they only take up as much space as needed when rendered. That's not always the case with Flutter. +Compose 继承 Android Views 的若干默认行为: +除非另有说明,多数组件按内容「包裹」尺寸,即仅占用渲染所需空间。Flutter 并非总是如此。 + To center the text, wrap it in a `Center` widget. To learn about different widgets and their default behaviors, check out the [Widget catalog][]. +要居中文本,请用 `Center` widget 包裹。 +要了解不同 widget 及其默认行为,请参阅 [Widget 目录][Widget catalog]。 + ### Adding Buttons +### 添加按钮 + In **Compose**, you use the `Button` composable or one of its variants to create a button. `Button` is an alias for `FilledTonalButton` when using a Material theme. +在 **Compose** 中,使用 `Button` composable 或其变体创建按钮; +使用 Material 主题时 `Button` 是 `FilledTonalButton` 的别名。 + ```kotlin Button(onClick = {}) { Text("Do something") @@ -227,6 +332,8 @@ Button(onClick = {}) { To achieve the same result in **Flutter**, use the `FilledButton` class: +在 **Flutter** 中,使用 `FilledButton` 类可达到相同效果: + ```dart FilledButton( onPressed: () { @@ -238,14 +345,23 @@ FilledButton( **Flutter** gives you access to a variety of buttons with pre-defined styles. +**Flutter** 提供多种预定义样式的按钮。 + ### Aligning components horizontally or vertically + +### 水平或垂直对齐组件 + Jetpack Compose and Flutter handle horizontal and vertical collections of items similarly. +Jetpack Compose 与 Flutter 以相似方式处理水平与垂直排列的项。 + The following Compose snippet adds a globe image and text in both `Row` and `Column` containers with centering of the items: +以下 Compose 片段在 `Row` 与 `Column` 容器中添加地球图标与文本并居中: + ```kotlin Row(horizontalArrangement = Arrangement.Center) { Image(Icons.Default.Public, contentDescription = "") @@ -261,6 +377,10 @@ Column(verticalArrangement = Arrangement.Center) { **Flutter** uses [`Row`][] and [`Column`][] as well but there are some slight differences for specifying child widgets and alignment. The following is equivalent to the Compose example. +**Flutter** 也使用 [`Row`][] 与 [`Column`][], +但在指定子 widget 与对齐方面略有不同。 +以下与 Compose 示例等价: + ```dart Row( mainAxisAlignment: MainAxisAlignment.center, @@ -286,7 +406,12 @@ with extra space. `MainAxisAlignment.center` positions children in the center of the main axis. For `Row`, the main axis is the horizontal axis, inversely for `Column`, the main axis is the vertical axis. -::: note +`Row` 与 `Column` 的 `children` 参数需要 `List`。 +`mainAxisAlignment` 告诉 Flutter 如何在额外空间中定位子项; +`MainAxisAlignment.center` 将子项放在主轴中心。 +`Row` 的主轴为水平轴,`Column` 则为垂直轴。 + +:::note Whereas Flutter's `Row` and `Column` have `MainAxisAlignment` and `CrossAxisAlignment` to control how items are placed, the properties that control placement in Jetpack Compose are one vertical and horizontal property @@ -294,19 +419,32 @@ from the following: `verticalArrangement`, `verticalAlignment`, `horizontalAlignment`, and `horizontalArrangement`. The trick to determine which is the `MainAxis` is to look for the property that ends in `arrangement`. The `CrossAxis` will be the property that ends in `alignment`. + +Flutter 的 `Row` 与 `Column` 用 `MainAxisAlignment` 与 `CrossAxisAlignment` 控制项的位置; +Jetpack Compose 用以下垂直与水平属性之一: +`verticalArrangement`、`verticalAlignment`、`horizontalAlignment`、`horizontalArrangement`。 +判断 `MainAxis` 的技巧是找以 `arrangement` 结尾的属性; +`CrossAxis` 则以 `alignment` 结尾的属性为准。 ::: ### Displaying a list view +### 显示列表视图 + In **Compose**, you have a couple ways to create a list based on the size of the list you need to display. For a small number of items that can all be displayed at once, you can iterate over a collection inside a `Column` or `Row`. +在 **Compose** 中,可根据列表规模用几种方式创建列表: +少量可一次显示的项可在 `Column` 或 `Row` 内遍历集合。 + For a list with a large number of items, `LazyList` has better performance. It only lays out the components that will be visible versus all of them. +大量项的列表用 `LazyList` 性能更好,仅布局可见组件而非全部。 + ```kotlin data class Person(val name: String) @@ -337,6 +475,8 @@ fun ListDemo2(people: List) { To lazily build a list in Flutter, .... +在 Flutter 中惰性构建列表…… + ```dart class Person { String name; @@ -370,22 +510,35 @@ class HomePage extends StatelessWidget { Flutter has some conventions for lists: +Flutter 列表有一些约定: + - The [`ListView`] widget has a builder method. This works like the `item` closure inside a Compose `LazyList`. + [`ListView`] widget 有 builder 方法,类似 Compose `LazyList` 内的 `item` 闭包。 + - The `itemCount` parameter of the `ListView` sets how many items the `ListView` displays. + `ListView` 的 `itemCount` 参数设置显示项数。 + - The `itemBuilder` has an index parameter that will be between zero and one less than itemCount. + `itemBuilder` 的 index 参数介于 0 与 itemCount 减 1 之间。 + The previous example returned a [`ListTile`][] widget for each item. The `ListTile` widget includes properties like `height` and `font-size`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data. +上一示例为每项返回 [`ListTile`][] widget,其包含 `height`、`font-size` 等属性有助于构建列表; +但 Flutter 允许返回几乎任何表示数据的 widget。 + ### Displaying a grid +### 显示网格 + Constructing a grid in **Compose** is similar to a LazyList (`LazyColumn` or `LazyRow`). You can use the same `items` closure. There are properties on each @@ -393,6 +546,9 @@ grid type to specify how to arrange the items, whether or not to use adaptive or fixed layout, amongst others. +在 **Compose** 中构建网格类似 LazyList(`LazyColumn` 或 `LazyRow`), +可使用相同 `items` 闭包;各网格类型有属性指定项排列方式、自适应或固定布局等。 + ```kotlin val widgets = arrayOf( @@ -423,6 +579,10 @@ This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the `.builder()` initializer: +在 **Flutter** 中用 [`GridView`] widget 显示网格; +该 widget 有多种构造函数,目标相似但参数不同。 +以下示例使用 `.builder()` 初始化: + ```dart const widgets = [ Text('Row 1'), @@ -457,20 +617,34 @@ various parameters that the grid uses to lay out its components. This includes `crossAxisCount` that dictates the number of items displayed on each row. +`SliverGridDelegateWithFixedCrossAxisCount` delegate 决定网格布局组件的多种参数, +包括每行项数的 `crossAxisCount`。 + Jetpack Compose's `LazyHorizontalGrid`, `LazyVerticalGrid`, and Flutter's `GridView` are somewhat similar. `GridView` uses a delegate to decide how the grid should lay out its components. The `rows`, `columns`, and other associated properties on `LazyHorizontalGrid` \ `LazyVerticalGrid` serve the same purpose. +Jetpack Compose 的 `LazyHorizontalGrid`、`LazyVerticalGrid` 与 Flutter 的 `GridView` 有些相似。 +`GridView` 用 delegate 决定布局; +`LazyHorizontalGrid`/`LazyVerticalGrid` 的 `rows`、`columns` 等属性作用相同。 + ### Creating a scroll view +### 创建滚动视图 + `LazyColumn` and `LazyRow` in **Jetpack Compose** have built-in support for scrolling. +**Jetpack Compose** 的 `LazyColumn` 与 `LazyRow` 内置滚动支持。 + To create a scrolling view, **Flutter** uses [`SingleChildScrollView`][]. In the following example, the function `mockPerson` mocks instances of the `Person` class to create the custom `PersonView` widget. +**Flutter** 用 [`SingleChildScrollView`][] 创建滚动视图。 +以下示例中 `mockPerson` 模拟 `Person` 实例以创建自定义 `PersonView` widget。 + ```dart SingleChildScrollView( child: Column( @@ -487,32 +661,63 @@ SingleChildScrollView( ### Responsive and adaptive design +### 响应式与自适应设计 + Adaptive Design in **Compose** is a complex topic with many viable solutions: + +**Compose** 中的自适应设计是复杂主题,有多种可行方案: + * Using a custom layout + + 使用自定义布局 + * Using `WindowSizeClass` alone + + 单独使用 `WindowSizeClass` + * Using `BoxWithConstraints` to control what is shown based on available space + + 使用 `BoxWithConstraints` 根据可用空间控制显示内容 + * Using the Material 3 adaptive library that uses `WindowSizeClass` along with specialized composable layouts for common layouts + 使用 Material 3 自适应库,结合 `WindowSizeClass` 与常见布局的专用 composable 布局 + For that reason, you are encouraged to look into the **Flutter** options directly and see what fits your requirements versus attempting to find something that is a one to one translation. +因此建议你直接了解 **Flutter** 选项,看何者符合需求,而非强求一一对应。 + To create relative views in **Flutter**, you can use one of two options: +在 **Flutter** 中创建相对视图有两种方式: + - Get the `BoxConstraints` object in the [`LayoutBuilder`][] class. + + 在 [`LayoutBuilder`][] 类中获取 `BoxConstraints` 对象。 + - Use the [`MediaQuery.of()`][] in your build functions to get the size and orientation of your current app. + 在 build 函数中使用 [`MediaQuery.of()`][] 获取当前应用的尺寸与方向。 + To learn more, check out [Creating responsive and adaptive apps][]. +了解更多请参阅 [创建响应式与自适应应用][Creating responsive and adaptive apps]。 + ### Managing state +### 管理状态 + **Compose** stores state with the `remember` API and descendants of the `MutableState` interface. +**Compose** 用 `remember` API 与 `MutableState` 接口的后代存储状态。 + ```kotlin Scaffold( content = { padding -> @@ -534,15 +739,27 @@ Scaffold( **Flutter** manages local state using a [`StatefulWidget`][]. Implement a stateful widget with the following two classes: +**Flutter** 用 [`StatefulWidget`][] 管理本地状态,需以下两个类实现: + - a subclass of `StatefulWidget` + + `StatefulWidget` 的子类 + - a subclass of `State` + `State` 的子类 + The `State` object stores the widget's state. To change a widget's state, call `setState()` from the `State` subclass to tell the framework to redraw the widget. +`State` 对象存储 widget 状态;要变更状态, +在 `State` 子类中调用 `setState()` 通知框架重绘 widget。 + The following example shows a part of a counter app: +以下示例展示计数器应用的一部分: + ```dart class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @@ -577,17 +794,27 @@ class _MyHomePageState extends State { To learn more ways to manage state, check out [State management][]. +更多状态管理方式请参阅 [状态管理][State management]。 + ### Drawing on the Screen +### 在屏幕上绘制 + In **Compose**, you use the `Canvas` composable to draw shapes, images, and text to the screen. +在 **Compose** 中,用 `Canvas` composable 在屏幕上绘制形状、图像与文本。 + **Flutter** has an API based on the `Canvas` class, with two classes that help you draw: +**Flutter** 基于 `Canvas` 类提供 API,有两个类辅助绘制: + 1. [`CustomPaint`][] that requires a painter: + [`CustomPaint`][] 需要一个 painter: + ```dart CustomPaint( painter: SignaturePainter(_points), @@ -597,6 +824,8 @@ with two classes that help you draw: 2. [`CustomPainter`][] that implements your algorithm to draw to the canvas. + [`CustomPainter`][] 实现了用于在画布上绘制的算法。 + ```dart class SignaturePainter extends CustomPainter { SignaturePainter(this.points); @@ -624,21 +853,31 @@ with two classes that help you draw: ## Themes, styles, and media +## 主题、样式与媒体 + You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps. +可轻松为 Flutter 应用设置样式,包括在浅色与深色主题间切换、更改文本与 UI 组件设计等。本节介绍如何设置样式。 + ### Using dark mode +### 使用深色模式 + In **Compose**, you can control light and dark at any arbitrary level by wrapping a component with a `Theme` composable. +在 **Compose** 中,可用 `Theme` composable 包裹组件,在任意层级控制浅色与深色。 + In **Flutter**, you can control light and dark mode at the app-level. To control the brightness mode, use the `theme` property of the `App` class: +在 **Flutter** 中,可在应用级控制浅色与深色模式,通过 `App` 类的 `theme` 属性控制亮度模式: + ```dart const MaterialApp( theme: ThemeData( @@ -650,9 +889,13 @@ const MaterialApp( ### Styling text +### 设置文本样式 + In **Compose**, you use the properties on `Text` for one or two attributes or construct a `TextStyle` object to set many at once. +在 **Compose** 中,用 `Text` 上的属性设置一两个属性,或构建 `TextStyle` 一次设置多个。 + ```kotlin Text("Hello, world!", color = Color.Green, fontWeight = FontWeight.Bold, fontSize = 30.sp) @@ -670,6 +913,8 @@ Text("Hello, world!", To style text in **Flutter**, add a `TextStyle` widget as the value of the `style` parameter of the `Text` widget. +在 **Flutter** 中,将 `TextStyle` 作为 `Text` widget 的 `style` 参数值以设置文本样式。 + ```dart Text( 'Hello, world!', @@ -683,10 +928,15 @@ Text( ### Styling buttons +### 设置按钮样式 + In **Compose**, you modify the colors of a button using the `colors` property. If left unmodified, they use the defaults from the current theme. +在 **Compose** 中,用 `colors` 属性修改按钮颜色; +未修改则使用当前主题默认色。 + ```kotlin Button(onClick = {}, colors = ButtonDefaults.buttonColors().copy( @@ -699,6 +949,8 @@ Button(onClick = {}, To style button widgets in **Flutter**, you similarly set the style of its child, or modify properties on the button itself. +在 **Flutter** 中,可类似地设置子项样式或修改按钮自身属性。 + ```dart FilledButton( onPressed: (){}, @@ -713,17 +965,26 @@ FilledButton( ) ) ``` + ## Bundling assets for use in Flutter +## 为 Flutter 打包资源 + There is commonly a need to bundle resources for use in your application. They can be animations, vector graphics, images, fonts, or other general files. +应用常需打包资源:动画、矢量图、图像、字体或其他文件。 + Unlike native Android apps that expect a set directory structure under `/res//` where the qualifier could be indicating the type of file, a specific orientation, or android version, Flutter doesn't require a specific location as long as the referenced files are listed in the `pubspec.yaml` file. Below is an excerpt from a `pubspec.yaml` referencing several images and a font file. +与原生 Android 在 `/res//` 下要求特定目录结构不同, +Flutter 只要资源列在 `pubspec.yaml` 中即可,无需固定位置。 +以下是引用若干图像与字体的 `pubspec.yaml` 摘录。 + ```yaml flutter: assets: @@ -737,17 +998,27 @@ flutter: ### Using fonts +### 使用字体 + In **Compose**, you have two options for using fonts in your app. You can use a runtime service I to retrieve them [Google Fonts][]. Alternatively, they may be bundled in resource files. +在 **Compose** 中,使用字体有两种方式:通过运行时服务获取 [Google Fonts][],或打包在资源文件中。 + **Flutter** has similar methods to use fonts, let's discuss them both inline. +**Flutter** 有类似字体用法,下面一并说明。 + ### Using bundled fonts +### 使用打包字体 + The following are roughly equivalent Compose and Flutter code for using a font file in the `/res/` or `fonts` directory as listed above. +以下 Compose 与 Flutter 代码大致等价,用于使用上文所列 `/res/` 或 `fonts` 目录中的字体文件。 + ```kotlin // Font files bundled with app val firaSansFamily = FontFamily( @@ -771,12 +1042,18 @@ Text( ### Using a font provider (Google Fonts) +### 使用字体提供方 (Google Fonts) + One point of difference is using fonts from a font provider like Google Fonts. In **Compose**, the instantiation is done inline with the same approximate code to reference a local file. +差异之一是从 Google Fonts 等提供方使用字体:在 **Compose** 中,实例化方式与引用本地文件大致相同。 + After instantiating a provider that references the special strings for the font service, you would use the same `FontFamily` declaration. +实例化引用字体服务特殊字符串的 provider 后,使用相同 `FontFamily` 声明。 + ```kotlin // Font files bundled with app val provider = GoogleFont.Provider( @@ -799,6 +1076,8 @@ Text(text = "Compose", fontFamily = firaSansFamily, fontWeight = FontWeight.Ligh For Flutter, this is provided by the [google_fonts][] plugin using the name of the font. +Flutter 由 [google_fonts][] 插件按字体名称提供。 + ```dart import 'package:google_fonts/google_fonts.dart'; //... @@ -812,13 +1091,19 @@ Text( ### Using images +### 使用图片 + In **Compose**, typically image files to the drawable directory in resources `/res/drawable` and one uses `Image` composable to display the images. Assets are referenced by using the resource locator in the style of `R.drawable.` without the file extension. +在 **Compose** 中,图像通常放在 `/res/drawable`,用 `Image` composable 显示,通过 `R.drawable.<文件名>`(无扩展名)引用。 + In **Flutter**, the resource location is a listed in `pubspec.yaml` as shown in the snippet below. +在 **Flutter** 中,资源位置列在 `pubspec.yaml` 中,如下片段所示。 + ```yaml flutter: assets: @@ -828,8 +1113,12 @@ In **Flutter**, the resource location is a listed in `pubspec.yaml` as shown in After adding your image, you can display it using the `Image` widget's `.asset()` constructor. This constructor: +添加图像后,可用 `Image` widget 的 `.asset()` 构造函数显示。该构造函数: + To review a complete example, check out the [`Image`][] docs. +完整示例请参阅 [`Image`][] 文档。 + [Flutter for Android developers]: /flutter-for/android-devs [Add Flutter to existing app]: /add-to-app diff --git a/sites/docs/src/content/flutter-for/dart-swift-concurrency.md b/sites/docs/src/content/flutter-for/dart-swift-concurrency.md index fc44bab3c6..98ffd615d3 100644 --- a/sites/docs/src/content/flutter-for/dart-swift-concurrency.md +++ b/sites/docs/src/content/flutter-for/dart-swift-concurrency.md @@ -1,7 +1,11 @@ --- -title: Flutter concurrency for Swift developers +# title: Flutter concurrency for Swift developers +title: 给 Swift 开发者的 Flutter 并发指南 +# description: > +# Leverage your Swift concurrency knowledge while learning Flutter and Dart. description: > - Leverage your Swift concurrency knowledge while learning Flutter and Dart. + 在学习 Flutter 和 Dart 时发挥你的 Swift 并发知识。 +ai-translated: true --- @@ -12,6 +16,10 @@ concurrency works in Dart and how it compares to Swift. With this understanding, you can create high-performing iOS apps. +Dart 和 Swift 都支持并发编程。 +本指南帮助你理解 Dart 中的并发机制及其与 Swift 的对比。 +掌握这些后,你可以构建高性能 iOS 应用。 + When developing in the Apple ecosystem, some tasks might take a long time to complete. These tasks include fetching or processing large amounts of data. @@ -20,6 +28,10 @@ to schedule tasks using a shared thread pool. With GCD, developers add tasks to dispatch queues and GCD decides on which thread to execute them. +在 Apple 生态中开发时,某些任务可能耗时较长,例如获取或处理大量数据。 +iOS 开发者通常使用 Grand Central Dispatch (GCD) 通过共享线程池调度任务: +将任务加入 dispatch 队列,由 GCD 决定在哪条线程执行。 + But, GCD spins up threads to handle remaining work items. This means you can end up with a large number of threads @@ -28,6 +40,9 @@ With Swift, the structured concurrency model reduced the number of threads and context switches. Now, each core has only one thread. +但 GCD 会创建线程处理剩余工作项,可能导致线程过多、系统过载。 +Swift 的结构化并发模型减少了线程数和上下文切换,现在每个核心只有一条线程。 + Dart has a single-threaded execution model, with support for `Isolates`, an event loop, and asynchronous code. An `Isolate` is Dart's implementation of a lightweight thread. @@ -37,6 +52,11 @@ Flutter's event loop is equivalent to the iOS main loop—in other words, the Looper attached to the main thread. +Dart 采用单线程执行模型,支持 `Isolate`、事件循环和异步代码。 +`Isolate` 是 Dart 对轻量线程的实现。 +除非你 spawn 一个 `Isolate`,否则 Dart 代码在由事件循环驱动的主 UI 线程中运行。 +Flutter 的事件循环相当于 iOS 主循环,即附加在主线程上的 Looper。 + Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. @@ -44,8 +64,13 @@ Instead, use the asynchronous features that the Dart language provides, such as `async`/`await`. +Dart 的单线程模型并不意味着你必须把所有操作都作为阻塞操作导致 UI 冻结, +而应使用 Dart 提供的异步特性,例如 `async`/`await`。 + ## Asynchronous Programming +## 异步编程 + An asynchronous operation allows other operations to execute before it completes. Both Dart and Swift support asynchronous functions @@ -58,8 +83,16 @@ suspend the function, if necessary. For more details on asynchronous programming, check out [Concurrency in Dart]({{site.dart-site}}/guides/language/concurrency). +异步操作允许其他操作在其完成前执行。 +Dart 和 Swift 都使用 `async` 和 `await` 关键字支持异步函数: +`async` 标记函数执行异步工作,`await` 告诉系统等待函数返回结果, +这意味着 Dart VM **可能** 在必要时挂起该函数。 +有关异步编程的更多细节,请参阅 [Dart 中的并发]({{site.dart-site}}/guides/language/concurrency)。 + ### Leveraging the main thread/isolate +### 利用主线程 / 主 isolate + For Apple operating systems, the primary (also called the main) thread is where the application begins running. Rendering the user interface always happens on the main thread. @@ -69,15 +102,25 @@ and Swift doesn't guarantee which thread is used. So, when dispatching UI updates in Swift, you might need to ensure that the work occurs on the main thread. +在 Apple 操作系统上,主线程是应用开始运行的地方,用户界面渲染始终在主线程进行。 +Swift 与 Dart 的一个区别是 Swift 可能对不同任务使用不同线程,且不保证使用哪条线程, +因此在 Swift 中调度 UI 更新时可能需要确保工作发生在主线程。 + Say you want to write a function that fetches the weather asynchronously and displays the results. +假设你要编写一个异步获取天气并显示结果的函数。 + In GCD, to manually dispatch a process to the main thread, you might do something like the following. +在 GCD 中,若要手动将进程派发到主线程,可以这样做。 + First, define the `Weather` `enum`: +首先定义 `Weather` `enum`: + ```swift enum Weather: String { case rainy, sunny @@ -90,6 +133,9 @@ Use GCD to create a background `DispatchQueue` to send the work to the pool of threads, and then dispatch back to the main thread to update the `result`. +接下来定义 view model,标记为 [`@Observable`][],发布类型为 `Weather?` 的 `result`。 +使用 GCD 创建后台 `DispatchQueue` 将工作发送到线程池,再派回主线程更新 `result`。 + ```swift @Observable class ContentViewModel { private(set) var result: Weather? @@ -108,6 +154,8 @@ back to the main thread to update the `result`. Finally, display the results: +最后显示结果: + ```swift struct ContentView: View { @State var viewModel = ContentViewModel() @@ -127,6 +175,9 @@ define a view model class that is marked as a `@MainActor`, with a `load()` function that internally calls an asynchronous function using `Task`. +近年来 Swift 引入 **actors** 以支持共享可变状态的同步。 +要确保工作在主线程执行,可定义标记为 `@MainActor` 的 view model 类,其 `load()` 内部使用 `Task` 调用异步函数。 + ```swift @MainActor @Observable class ContentViewModel { private(set) var result: Weather? @@ -142,6 +193,8 @@ asynchronous function using `Task`. Next, define the view model as a state using `@State`, with a `load()` function that can be called by the view model: +接下来使用 `@State` 定义 view model,由视图调用 `load()`: + ```swift struct ContentView: View { @State var viewModel = ContentViewModel() @@ -158,6 +211,9 @@ In Dart, all work runs on the main isolate by default. To implement the same example in Dart, first, create the `Weather` `enum`: +在 Dart 中,默认所有工作在主 isolate 上运行。 +要在 Dart 中实现相同示例,首先创建 `Weather` `enum`: + ```dart enum Weather { rainy, windy, sunny } @@ -169,6 +225,10 @@ provided in the future. A `Future` is similar to Swift's `@Observable`. In this example, a function within the view model returns a `Future` object: +然后定义简单的 view model(类似 SwiftUI 中的做法)以获取天气。 +在 Dart 中,`Future` 对象表示将来提供的值,与 Swift 的 `@Observable` 类似。 +本例中 view model 内的函数返回 `Future`: + ```dart @immutable @@ -186,18 +246,29 @@ similarities with the Swift code. The Dart function is marked as `async` because it uses the `await` keyword. +本例中的 `load()` 与 Swift 代码类似。 +Dart 函数标记为 `async` 是因为使用了 `await`。 + Additionally, a Dart function marked as `async` automatically returns a `Future`. In other words, you don't have to create a `Future` instance manually inside functions marked as `async`. +此外,标记为 `async` 的 Dart 函数会自动返回 `Future`, +即在 `async` 函数内无需手动创建 `Future` 实例。 + For the last step, display the weather value. In Flutter, [`FutureBuilder`]({{site.api}}/flutter/widgets/FutureBuilder-class.html) and [`StreamBuilder`]({{site.api}}/flutter/widgets/StreamBuilder-class.html) widgets are used to display the results of a Future in the UI. The following example uses a `FutureBuilder`: +最后一步是显示天气值。在 Flutter 中, +[`FutureBuilder`]({{site.api}}/flutter/widgets/FutureBuilder-class.html) 和 +[`StreamBuilder`]({{site.api}}/flutter/widgets/StreamBuilder-class.html) widget 用于在 UI 中显示 Future 的结果。 +以下示例使用 `FutureBuilder`: + ```dart class HomePage extends StatelessWidget { @@ -232,10 +303,14 @@ class HomePage extends StatelessWidget { For the complete example, check out the [async_weather][] file on GitHub. +完整示例请参阅 GitHub 上的 [async_weather][] 文件。 + [async_weather]: {{site.repo.this}}/blob/main/examples/resources/dart_swift_concurrency/lib/async_weather.dart ### Leveraging a background thread/isolate +### 利用后台线程 / isolate + Flutter apps can run on a variety of multi-core hardware, including devices running macOS and iOS. To improve the performance of these applications, @@ -243,10 +318,15 @@ you must sometimes run tasks on different cores concurrently. This is especially important to avoid blocking UI rendering with long-running operations. +Flutter 应用可在多种多核硬件上运行,包括 macOS 和 iOS 设备。为提升性能, +有时必须在不同核心上并发运行任务,这对避免长时间操作阻塞 UI 渲染尤为重要。 + In Swift, you can leverage GCD to run tasks on global queues with different quality of service class (qos) properties. This indicates the task's priority. +在 Swift 中,可利用 GCD 在不同服务质量 (qos) 的全局队列上运行任务,以表示任务优先级。 + ```swift func parse(string: String, completion: @escaping ([String:Any]) -> Void) { // Mimic 1 sec delay. @@ -265,6 +345,10 @@ A common scenario spawns a simple worker isolate and returns the results in a message when the worker exits. You can use `Isolate.run()` to spawn an isolate and run computations: +在 Dart 中,可将计算卸载到 worker isolate(常称为后台 worker)。 +常见场景是 spawn 一个简单的 worker isolate,在 worker 退出时通过消息返回结果。 +可使用 `Isolate.run()` spawn isolate 并运行计算: + ```dart void main() async { // Read some data. @@ -278,6 +362,8 @@ void main() async { In Flutter, you can also use the `compute` function to spin up an isolate to run a callback function: +在 Flutter 中,也可使用 `compute` 函数启动 isolate 运行回调: + ```dart final jsonData = await compute(getNumberOfKeys, jsonString); ``` @@ -285,6 +371,8 @@ final jsonData = await compute(getNumberOfKeys, jsonString); In this case, the callback function is a top-level function as shown below: +此时回调为如下所示的顶层函数: + ```dart Map getNumberOfKeys(String jsonString) { return jsonDecode(jsonString); @@ -297,6 +385,10 @@ and more information on Flutter at [Flutter for SwiftUI developers][] or [Flutter for UIKit developers][]. +有关 Dart 的更多信息请参阅 [Swift 开发者学习 Dart 指南][Learning Dart as a Swift developer], +有关 Flutter 请参阅 [给 SwiftUI 开发者的 Flutter 指南][Flutter for SwiftUI developers] +或 [给 UIKit 开发者的 Flutter 指南][Flutter for UIKit developers]。 + [Learning Dart as a Swift developer]: {{site.dart-site}}/guides/language/coming-from/swift-to-dart [Flutter for SwiftUI developers]: /flutter-for/swiftui-devs [Flutter for UIKit developers]: /flutter-for/uikit-devs diff --git a/sites/docs/src/content/flutter-for/index.md b/sites/docs/src/content/flutter-for/index.md index c7d8539fc0..7552a7e1f6 100644 --- a/sites/docs/src/content/flutter-for/index.md +++ b/sites/docs/src/content/flutter-for/index.md @@ -1,10 +1,16 @@ --- -title: Learn Flutter when coming from another platform +# title: Learn Flutter when coming from another platform +title: 从其他平台入门 Flutter shortTitle: Flutter for sitemap: false +# description: >- +# Utilize your background developing for another platform +# to learn the basics of Flutter! description: >- - Utilize your background developing for another platform - to learn the basics of Flutter! + 利用你在其他平台的开发背景,学习 Flutter 基础知识! # This is a placeholder page (Firebase redirects this page's URL to another); # it is necessary to allow breadcrumbs to work. +# 这是占位页面(Firebase 会将此页 URL 重定向到其他页面); +# 需要此页面才能让面包屑导航正常工作。 +ai-translated: true --- diff --git a/sites/docs/src/content/flutter-for/swiftui-devs.md b/sites/docs/src/content/flutter-for/swiftui-devs.md index 7625dc12a3..863f703594 100644 --- a/sites/docs/src/content/flutter-for/swiftui-devs.md +++ b/sites/docs/src/content/flutter-for/swiftui-devs.md @@ -1,6 +1,9 @@ --- -title: Flutter for SwiftUI Developers -description: Learn how to apply SwiftUI developer knowledge when building Flutter apps. +# title: Flutter for SwiftUI Developers +title: 给 SwiftUI 开发者的 Flutter 指南 +# description: Learn how to apply SwiftUI developer knowledge when building Flutter apps. +description: 学习在构建 Flutter 应用时运用 SwiftUI 开发经验。 +ai-translated: true --- @@ -9,9 +12,14 @@ SwiftUI developers who want to write mobile apps using Flutter should review this guide. It explains how to apply existing SwiftUI knowledge to Flutter. +想用 Flutter 编写移动应用的 SwiftUI 开发者应阅读本指南, +说明如何将现有 SwiftUI 知识应用于 Flutter。 + :::note If you instead have experience building apps for iOS with UIKit, see [Flutter for UIKit developers][]. + +若你使用 UIKit 为 iOS 构建应用,请参阅 [给 UIKit 开发者的 Flutter 指南][Flutter for UIKit developers]。 ::: Flutter is a framework for building cross-platform applications @@ -20,8 +28,18 @@ To understand some differences between programming with Dart and programming with Swift, see [Learning Dart as a Swift Developer][] and [Flutter concurrency for Swift developers][]. +Flutter 是一个用于构建跨平台应用的框架。 +它使用 Dart 编程语言。 +如果要了解 Dart 编程与 Swift 编程之间的某些差异, +请参阅 [Swift 开发者学习 Dart 指南][Learning Dart as a Swift Developer] +和 [给 Swift 开发者的 Flutter 并发指南][Flutter concurrency for Swift developers]。 + Your SwiftUI knowledge and experience are highly valuable when building with Flutter. + +在使用 Flutter 进行开发时, +你的 SwiftUI 知识和经验非常宝贵。 + {% comment %} TODO: Add talk about plugin system for interacting with OS and hardware when [iOS and Apple hardware interactions with Flutter][] is released. @@ -31,9 +49,15 @@ Flutter also makes a number of adaptations to app behavior when running on iOS and macOS. To learn how, see [Platform adaptations][]. +Flutter 还针对在 iOS 和 macOS 上运行的应用行为进行了一系列调整。 +如果需要了解具体方法,请参阅 [平台适配][Platform adaptations]。 + :::tip To integrate Flutter code into an **existing** iOS app, check out [Add Flutter to existing app][]. + +若要将 Flutter 代码集成到 **现有** iOS 应用, +请参阅 [将 Flutter 添加到现有应用][Add Flutter to existing app]。 ::: This document can be used as a cookbook by jumping around @@ -42,21 +66,39 @@ This guide embeds sample code. By using the "Open in DartPad" button that appears on hover or focus, you can open and run some of the examples on DartPad. +你可以在当前文档随意浏览并查找最符合你需要的内容。 +当前指南嵌入了示例代码。 +你可以通过悬停或聚焦时出现的 "Open in DartPad" 按钮, +在 DartPad 上打开并运行部分示例。 + ## Overview +## 概览 + As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps. +你可以观看以下视频来进行了解。 +该视频概述了 Flutter 在 iOS 上的工作原理, +以及如何使用 Flutter 构建 iOS 应用。 + Flutter and SwiftUI code describes how the UI looks and works. Developers call this type of code a _declarative framework_. +Flutter 和 SwiftUI 的代码描述了 UI 的外观和行为。 +开发者将此类代码称为 **声明式框架**。 + ### Views vs. Widgets +### View 与 Widget + **SwiftUI** represents UI components as _views_. You configure views using _modifiers_. +**SwiftUI** 将 UI 组件表示为 **view**,通过 **modifier** 配置 view。 + ```swift Text("Hello, World!") // <-- This is a View .padding(10) // <-- This is a modifier of that View @@ -64,12 +106,18 @@ Text("Hello, World!") // <-- This is a View **Flutter** represents UI components as _widgets_. +**Flutter** 将 UI 组件表示为 **widget**。 + Both views and widgets only exist until they need to be changed. These languages call this property _immutability_. SwiftUI represents a UI component property as a View modifier. By contrast, Flutter uses widgets for both UI components and their properties. +view 与 widget 仅在需要变更前存在,称为 **immutability(不可变性)**。 +SwiftUI 用 View modifier 表示 UI 组件属性; +Flutter 则用 widget 同时表示 UI 组件及其属性。 + ```dart Padding( // <-- This is a Widget padding: EdgeInsets.all(10.0), // <-- So is this @@ -81,49 +129,100 @@ To compose layouts, both SwiftUI and Flutter nest UI components within one another. SwiftUI nests Views while Flutter nests Widgets. +组合布局时,SwiftUI 与 Flutter 都嵌套 UI 组件:SwiftUI 嵌套 View,Flutter 嵌套 Widget。 + ### Layout process +### 布局过程 + **SwiftUI** lays out views using the following process: +**SwiftUI** 按以下过程布局 view: + 1. The parent view proposes a size to its child view. + + 父 view 向子 view 提议尺寸。 + 1. All subsequent child views: + + 所有后续子 view: + - propose a size to _their_ child's view + + 向 **其** 子 view 提议尺寸 + - ask that child what size it wants + + 询问子 view 期望尺寸 + 1. Each parent view renders its child view at the returned size. + 每个父 view 按返回的尺寸渲染子 view。 + **Flutter** differs somewhat with its process: +**Flutter** 的过程略有不同: + 1. The parent widget passes constraints down to its children. Constraints include minimum and maximum values for height and width. + + 父 widget 向子级传递约束,包括高度与宽度的最小值和最大值。 + 1. The child tries to decide its size. It repeats the same process with its own list of children: + + 子 widget 会尝试决定自身的尺寸,并对它自己的子级列表重复相同的过程: + - It informs its child of the child's constraints. + + 告知其子级所受的约束。 + - It asks its child what size it wishes to be. + 询问其子级希望的尺寸。 + 1. The parent lays out the child. + + 父级对子级进行布局。 + - If the requested size fits in the constraints, the parent uses that size. + + 若请求的尺寸符合约束,父级就采用该尺寸。 + - If the requested size doesn't fit in the constraints, the parent limits the height, width, or both to fit in its constraints. + 若请求的尺寸不符合约束,父级会限制高度、宽度或两者,使其符合约束。 + Flutter differs from SwiftUI because the parent component can override the child's desired size. The widget cannot have any size it wants. It also cannot know or decide its position on screen as its parent makes that decision. +Flutter 与 SwiftUI 不同在于父组件可覆盖子组件期望尺寸; +widget 不能任意尺寸,也无法知晓或决定屏幕位置,由父组件决定。 + To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value. +要强制子 widget 以特定尺寸渲染,父级须设置紧约束;最小尺寸等于最大尺寸时为紧约束。 + In **SwiftUI**, views might expand to the available space or limit their size to that of its content. **Flutter** widgets behave in similar manner. +在 **SwiftUI** 中,view 可扩展到可用空间或限制为内容尺寸。 +**Flutter** widget 行为类似。 + However, in Flutter parent widgets can offer unbounded constraints. Unbounded constraints set their maximum values to infinity. +但 Flutter 父 widget 可提供无界约束,最大值设为无穷。 + ```dart UnboundedBox( child: Container( @@ -134,6 +233,8 @@ UnboundedBox( If the child expands and it has unbounded constraints, Flutter returns an overflow warning: +若子级扩展且有无界约束,Flutter 会返回溢出警告: + ```dart UnconstrainedBox( child: Container(color: red, width: 4000, height: 50), @@ -145,34 +246,59 @@ UnconstrainedBox( To learn how constraints work in Flutter, see [Understanding constraints][]. +要了解 Flutter 中约束的工作原理,请参阅 [理解布局约束][Understanding constraints]。 + ### Design system +### 设计系统 + Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. Though this guide features [Material][] widgets, your Flutter app can use many different design systems: +Flutter 面向多平台,应用不必遵循特定设计系统。 +本指南使用 [Material][] widget,但可采用多种设计系统: + - Custom Material widgets + + 自定义 Material widget - Community built widgets + + 社区构建的 widget - Your own custom widgets + + 你自己的自定义 widget - [Cupertino widgets][] that follow Apple's Human Interface Guidelines + 遵循 Apple 人机界面指南的 [Cupertino widgets][] + If you're looking for a great reference app that features a custom design system, check out [Wonderous][]. +参考自定义设计系统的优秀应用请参阅 [Wonderous][]。 + ## UI Basics +## UI 基础 + This section covers the basics of UI development in Flutter and how it compares to SwiftUI. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more. +本节涵盖 Flutter UI 基础及与 SwiftUI 的对比,包括入门、静态文本、按钮、点击响应、列表与网格等。 + ### Getting started +### 入门 + In **SwiftUI**, you use `App` to start your app. +在 **SwiftUI** 中,用 `App` 启动应用。 + ```swift @main struct MyApp: App { @@ -187,6 +313,8 @@ struct MyApp: App { Another common SwiftUI practice places the app body within a `struct` that conforms to the `View` protocol as follows: +另一常见做法将应用 body 放在符合 `View` 协议的 `struct` 中,如下: + ```swift struct HomePage: View { var body: some View { @@ -198,6 +326,8 @@ struct HomePage: View { To start your **Flutter** app, pass in an instance of your app to the `runApp` function. +启动 **Flutter** 应用时,将应用实例传给 `runApp`。 + ```dart dartpad="42cf3026e1460ef618257684ee5af6a2" void main() { @@ -210,6 +340,9 @@ user interface it represents. It's common to begin your app with a [`WidgetApp`][] class, like [`CupertinoApp`][]. +`App` 是 widget,build 方法描述所代表的用户界面。 +通常以 [`WidgetApp`][] 类(如 [`CupertinoApp`][])开始。 + ```dart dartpad="42cf3026e1460ef618257684ee5af6a2" class MyApp extends StatelessWidget { @@ -227,6 +360,8 @@ class MyApp extends StatelessWidget { The widget used in `HomePage` might begin with the `Scaffold` class. `Scaffold` implements a basic layout structure for an app. +`HomePage` 中的 widget 可能以 `Scaffold` 开始,实现应用基本布局结构。 + ```dart dartpad="42cf3026e1460ef618257684ee5af6a2" class HomePage extends StatelessWidget { @@ -247,10 +382,18 @@ To center the text, wrap it in a `Center` widget. To learn about different widgets and their default behaviors, check out the [Widget catalog][]. +注意 Flutter 使用 [`Center`][]。 +SwiftUI 默认将 view 内容居中渲染,Flutter 并非总是如此;`Scaffold` 不会将 `body` 居中。 +要居中文本请用 `Center` 包裹,详见 [核心 Widget 目录][Widget catalog]。 + ### Adding Buttons +### 添加按钮 + In **SwiftUI**, you use the `Button` struct to create a button. +在 **SwiftUI** 中,用 `Button` 结构体创建按钮。 + ```swift Button("Do something") { // this closure gets called when your @@ -261,6 +404,8 @@ Button("Do something") { To achieve the same result in **Flutter**, use the `CupertinoButton` class: +在 **Flutter** 中,用 `CupertinoButton` 类达到相同效果: + ```dart dartpad="3c9b9a4de431b86725197a7fc2c84158" CupertinoButton( @@ -275,18 +420,31 @@ CupertinoButton( The [`CupertinoButton`][] class comes from the Cupertino library. Widgets in the Cupertino library use Apple's design system. +**Flutter** 提供多种预定义样式按钮。 +[`CupertinoButton`][] 来自 Cupertino 库,其 widget 使用 Apple 设计系统。 + ### Aligning components horizontally +### 水平对齐组件 + In **SwiftUI**, stack views play a big part in designing your layouts. Two separate structures allow you to create stacks: +在 **SwiftUI** 中,stack view 在布局中很重要,有两种结构: + 1. `HStack` for horizontal stack views + `HStack` 用于水平 stack view + 2. `VStack` for vertical stack views + `VStack` 用于垂直 stack view + The following SwiftUI view adds a globe image and text to a horizontal stack view: +以下 SwiftUI view 在水平 stack 中添加地球图标与文本: + ```swift HStack { Image(systemName: "globe") @@ -296,6 +454,8 @@ HStack { **Flutter** uses [`Row`][] rather than `HStack`: +**Flutter** 使用 [`Row`][] 而非 `HStack`: + ```dart dartpad="0365338f938427b01d72e37cea554f75" Row( @@ -310,13 +470,23 @@ with extra space. `MainAxisAlignment.center` positions children in the center of the main axis. For `Row`, the main axis is the horizontal axis. +`Row` 的 `children` 需 `List`; +`mainAxisAlignment` 控制额外空间中的子项位置,`MainAxisAlignment.center` 将子项放在主轴中心; +`Row` 的主轴为水平轴。 + ### Aligning components vertically +### 垂直对齐组件 + The following examples build on those in the previous section. +以下示例建立在上一节基础上。 + In **SwiftUI**, you use `VStack` to arrange the components into a vertical pillar. +在 **SwiftUI** 中,用 `VStack` 将组件垂直排列。 + ```swift VStack { Image(systemName: "globe") @@ -327,6 +497,8 @@ VStack { **Flutter** uses the same Dart code from the previous example, except it swaps [`Column`][] for `Row`: +**Flutter** 使用与上一示例相同的 Dart 代码,但将 [`Row`][] 换为 [`Column`][]: + ```dart dartpad="d9a288be0c2a353296fc8825680b84b8" Column( @@ -337,12 +509,17 @@ Column( ### Displaying a list view +### 显示列表视图 + In **SwiftUI**, you use the `List` base component to display sequences of items. To display a sequence of model objects, make sure that the user can identify your model objects. To make an object identifiable, use the `Identifiable` protocol. +在 **SwiftUI** 中,用 `List` 显示项序列; +要显示模型对象序列,须使用户能识别模型对象,对象需符合 `Identifiable` 协议。 + ```swift struct Person: Identifiable { var name: String @@ -370,6 +547,9 @@ This resembles how **Flutter** prefers to build its list widgets. Flutter doesn't need the list items to be identifiable. You set the number of items to display then build a widget for each item. +这与 **Flutter** 构建列表 widget 的方式类似; +Flutter 不要求列表项可识别,你设置项数并为每项构建 widget。 + ```dart dartpad="67426fd4f9c38c0c1db96b1af65598f2" class Person { @@ -402,25 +582,41 @@ class HomePage extends StatelessWidget { Flutter has some caveats for lists: +Flutter 列表有一些注意事项: + - The [`ListView`] widget has a builder method. This works like the `ForEach` within SwiftUI's `List` struct. + [`ListView`] widget 提供了一个 builder 方法,其作用类似于 SwiftUI `List` 结构体中的 `ForEach`。 + - The `itemCount` parameter of the `ListView` sets how many items the `ListView` displays. + `ListView` 的 `itemCount` 参数用于设置 `ListView` 显示的项目数量。 + - The `itemBuilder` has an index parameter that will be between zero and one less than itemCount. + `itemBuilder` 带有一个 index 参数,其取值范围在 0 到 itemCount 减 1 之间。 + The previous example returned a [`ListTile`][] widget for each item. The `ListTile` widget includes properties like `height` and `font-size`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data. +上面的示例为每个项目返回了一个 [`ListTile`][] widget。 +`ListTile` widget 包含 `height`、`font-size` 等属性,这些属性有助于构建列表。 +不过,Flutter 允许你返回几乎任何能表示你数据的 widget。 + ### Displaying a grid +### 显示网格 + When constructing non-conditional grids in **SwiftUI**, you use `Grid` with `GridRow`. +在 **SwiftUI** 中构建非条件网格时,使用 `Grid` 与 `GridRow`。 + ```swift Grid { GridRow { @@ -441,6 +637,8 @@ This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the `.builder()` initializer: +在 **Flutter** 中用 [`GridView`] widget 显示网格,有多种构造函数,以下使用 `.builder()` 初始化: + ```dart dartpad="d6b9174f33db94164e457b3da80da933" const widgets = [ @@ -476,17 +674,27 @@ various parameters that the grid uses to lay out its components. This includes `crossAxisCount` that dictates the number of items displayed on each row. +`SliverGridDelegateWithFixedCrossAxisCount` delegate 决定了网格用来布局其组件的各种参数, +其中包括决定每行显示项目数量的 `crossAxisCount`。 + How SwiftUI's `Grid` and Flutter's `GridView` differ in that `Grid` requires `GridRow`. `GridView` uses the delegate to decide how the grid should lay out its components. +SwiftUI 的 `Grid` 与 Flutter 的 `GridView` 的区别在于 `Grid` 需要 `GridRow`; +`GridView` 用 delegate 决定布局。 + ### Creating a scroll view +### 创建滚动视图 + In **SwiftUI**, you use `ScrollView` to create custom scrolling components. The following example displays a series of `PersonView` instances in a scrollable fashion. +在 **SwiftUI** 中,用 `ScrollView` 创建自定义滚动组件,以下示例以可滚动方式显示一系列 `PersonView`。 + ```swift ScrollView { VStack(alignment: .leading) { @@ -501,6 +709,9 @@ To create a scrolling view, **Flutter** uses [`SingleChildScrollView`][]. In the following example, the function `mockPerson` mocks instances of the `Person` class to create the custom `PersonView` widget. +**Flutter** 用 [`SingleChildScrollView`][] 创建滚动视图, +以下 `mockPerson` 模拟 `Person` 实例创建 `PersonView`。 + ```dart dartpad="a75740320989ed04020d95502a0de34e" SingleChildScrollView( @@ -514,29 +725,55 @@ SingleChildScrollView( ### Responsive and adaptive design +### 响应式与自适应设计 + In **SwiftUI**, you use `GeometryReader` to create relative view sizes. +在 **SwiftUI** 中,用 `GeometryReader` 创建相对 view 尺寸。 + For example, you could: +例如,你可以: + - Multiply `geometry.size.width` by some factor to set the _width_. + + 将 `geometry.size.width` 乘以某个因子来设置 **width**。 + - Use `GeometryReader` as a breakpoint to change the design of your app. + 将 `GeometryReader` 用作断点以更改应用设计。 + You can also see if the size class has `.regular` or `.compact` using `horizontalSizeClass`. +还可用 `horizontalSizeClass` 查看 size class 为 `.regular` 或 `.compact`。 + To create relative views in **Flutter**, you can use one of two options: +在 **Flutter** 中创建相对视图有两种方式: + - Get the `BoxConstraints` object in the [`LayoutBuilder`][] class. + + 在 [`LayoutBuilder`][] 类中获取 `BoxConstraints` 对象。 + - Use the [`MediaQuery.of()`][] in your build functions to get the size and orientation of your current app. + 在 build 函数中使用 [`MediaQuery.of()`][] 获取当前应用的尺寸和方向。 + To learn more, check out [Creating responsive and adaptive apps][]. +要了解更多内容,请参阅 [创建响应式与自适应应用][Creating responsive and adaptive apps]。 + ### Managing state +### 管理状态 + In **SwiftUI**, you use the `@State` property wrapper to represent the internal state of a SwiftUI view. +在 **SwiftUI** 中,用 `@State` 属性包装器表示 SwiftUI view 的内部状态。 + ```swift struct ContentView: View { @State private var counter = 0; @@ -551,18 +788,33 @@ struct ContentView: View { **SwiftUI** also includes several options for more complex state management such as the `ObservableObject` protocol. +**SwiftUI** 还有 `ObservableObject` 等更复杂状态管理选项。 + **Flutter** manages local state using a [`StatefulWidget`][]. Implement a stateful widget with the following two classes: +**Flutter** 使用 [`StatefulWidget`][] 管理局部状态。 +通过以下两个类来实现一个 stateful widget: + - a subclass of `StatefulWidget` + + `StatefulWidget` 的一个子类 + - a subclass of `State` + `State` 的一个子类 + The `State` object stores the widget's state. To change a widget's state, call `setState()` from the `State` subclass to tell the framework to redraw the widget. +`State` 对象存储着 widget 的状态。 +要改变 widget 的状态,可在 `State` 子类中调用 `setState()`,以通知框架重绘该 widget。 + The following example shows a part of a counter app: +以下示例展示了一个计数器应用的部分代码: + ```dart dartpad="34815ab7d6ee0c5a45c82597df444450" class MyHomePage extends StatefulWidget { @@ -598,21 +850,37 @@ class _MyHomePageState extends State { To learn more ways to manage state, check out [State management][]. +要了解更多管理状态的方式,请参阅 [状态管理][State management]。 + ### Animations +### 动画 + Two main types of UI animations exist. +UI 动画主要有两类。 + - Implicit that animated from a current value to a new target. + + 隐式:从当前值动画到新目标。 - Explicit that animates when asked. + 显式:按需动画。 + #### Implicit Animation +#### 隐式动画 + SwiftUI and Flutter take a similar approach to animation. In both frameworks, you specify parameters like `duration`, and `curve`. +SwiftUI 与 Flutter 动画方式相似,都指定 `duration`、`curve` 等参数。 + In **SwiftUI**, you use the `animate()` modifier to handle implicit animation. +在 **SwiftUI** 中,用 `animate()` modifier 处理隐式动画。 + ```swift Button("Tap me!"){ angle += 45 @@ -625,9 +893,13 @@ Button("Tap me!"){ This simplifies animating common widgets. Flutter names these widgets with the following format: `AnimatedFoo`. +**Flutter** 有隐式动画 widget,简化常见 widget 动画,命名格式为 `AnimatedFoo`。 + For example: To rotate a button, use the [`AnimatedRotation`][] class. This animates the `Transform.rotate` widget. +例如旋转按钮用 [`AnimatedRotation`][],为 `Transform.rotate` widget 添加动画。 + ```dart dartpad="0ad0572cbf98ead2e5d31a2a94430f19" AnimatedRotation( @@ -648,29 +920,49 @@ AnimatedRotation( Flutter allows you to create custom implicit animations. To compose a new animated widget, use the [`TweenAnimationBuilder`][]. +Flutter 可创建自定义隐式动画,用 [`TweenAnimationBuilder`][] 组合新动画 widget。 + #### Explicit Animation +#### 显式动画 + For explicit animations, **SwiftUI** uses the `withAnimation()` function. +显式动画方面,**SwiftUI** 用 `withAnimation()`。 + **Flutter** includes explicitly animated widgets with names formatted like `FooTransition`. One example would be the [`RotationTransition`][] class. +**Flutter** 有显式动画 widget,命名如 `FooTransition`,例如 [`RotationTransition`][]。 + Flutter also allows you to create a custom explicit animation using `AnimatedWidget` or `AnimatedBuilder`. +Flutter 还可用 `AnimatedWidget` 或 `AnimatedBuilder` 创建自定义显式动画。 + To learn more about animations in Flutter, see [Animations overview][]. +更多动画信息请参阅 [动画概览][Animations overview]。 + ### Drawing on the Screen +### 在屏幕上绘制 + In **SwiftUI**, you use `CoreGraphics` to draw lines and shapes to the screen. +在 **SwiftUI** 中,用 `CoreGraphics` 在屏幕上绘制线条与形状。 + **Flutter** has an API based on the `Canvas` class, with two classes that help you draw: +**Flutter** 基于 `Canvas` 类提供 API,有两个辅助类: + 1. [`CustomPaint`][] that requires a painter: + 需要一个 painter 的 [`CustomPaint`][]: + ```dart dartpad="978d64ee66d54177fb639f8a9f801039" CustomPaint( @@ -681,6 +973,8 @@ with two classes that help you draw: 2. [`CustomPainter`][] that implements your algorithm to draw to the canvas. + 实现你的算法、负责将内容绘制到画布上的 [`CustomPainter`][]。 + ```dart dartpad="978d64ee66d54177fb639f8a9f801039" class SignaturePainter extends CustomPainter { @@ -709,20 +1003,32 @@ with two classes that help you draw: ## Navigation +## 导航 + This section explains how to navigate between pages of an app, the push and pop mechanism, and more. +本节说明应用页面间导航、push/pop 机制等。 + ### Navigating between pages +### 在页面间导航 + Developers build iOS and macOS apps with different pages called _navigation routes_. +开发者使用称为 **navigation routes**(导航路由)的不同页面来构建 iOS 与 macOS 应用。 + In **SwiftUI**, the `NavigationStack` represents this stack of pages. +在 **SwiftUI** 中,`NavigationStack` 表示该页面栈。 + The following example creates an app that displays a list of persons. To display a person's details in a new navigation link, tap on that person. +以下示例创建显示人员列表的应用,点击人员在新的导航链接中显示详情。 + ```swift NavigationStack(path: $path) { List { @@ -744,9 +1050,13 @@ use [`Navigator`][] with named routes. After defining your navigation routes, call your navigation routes using their names. +若无复杂链接的小型 **Flutter** 应用,可用 [`Navigator`][] 命名路由;定义路由后按名称调用。 + 1. Name each route in the class passed to the `runApp()` function. The following example uses `App`: + 在传给 `runApp()` 函数的类中为每条路由命名,以下示例使用 `App`: + ```dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" // Defines the route name as a constant @@ -772,6 +1082,9 @@ call your navigation routes using their names. `mockPersons()`. Tapping a person pushes the person's detail page to the `Navigator` using `pushNamed()`. + 下面的示例使用 `mockPersons()` 生成一个人员列表。 + 点击某个人员时,会使用 `pushNamed()` 将该人员的详情页推入 `Navigator`。 + ```dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" ListView.builder( @@ -802,6 +1115,9 @@ call your navigation routes using their names. widget when navigating to the new route. Extract the arguments using `ModalRoute.of()`: + 定义用于显示每个人员详情的 `DetailsPage` widget。 + 在 Flutter 中,导航到新路由时可以向 widget 传入参数,并使用 `ModalRoute.of()` 提取这些参数。 + ```dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" class DetailsPage extends StatelessWidget { @@ -824,13 +1140,21 @@ call your navigation routes using their names. To create more advanced navigation and routing requirements, use a routing package such as [go_router][]. +更高级导航需求可使用 [go_router][] 等路由 package。 + To learn more, check out [Navigation and routing][]. +更多内容请参阅 [导航与路由][Navigation and routing]。 + ### Manually pop back +### 手动返回 + In **SwiftUI**, you use the `dismiss` environment value to pop-back to the previous screen. +在 **SwiftUI** 中,用 `dismiss` 环境值返回上一屏。 + ```swift Button("Pop back") { dismiss() @@ -839,6 +1163,8 @@ Button("Pop back") { In **Flutter**, use the `pop()` function of the `Navigator` class: +在 **Flutter** 中,用 `Navigator` 类的 `pop()`: + ```dart dartpad="3c125ab2dfba9f4178aeaeb8619c5bea" TextButton( @@ -853,9 +1179,13 @@ TextButton( ### Navigating to another app +### 导航到其他应用 + In **SwiftUI**, you use the `openURL` environment variable to open a URL to another application. +在 **SwiftUI** 中,用 `openURL` 环境变量打开其他应用的 URL。 + ```swift @Environment(\.openURL) private var openUrl @@ -872,6 +1202,8 @@ Button("Open website") { In **Flutter**, use the [`url_launcher`][] plugin. +在 **Flutter** 中,使用 [`url_launcher`][] 插件。 + ```dart dartpad="695beba25fa8120d89c9960cb222e276" CupertinoButton( @@ -884,20 +1216,30 @@ CupertinoButton( ## Themes, styles, and media +## 主题、样式与媒体 + You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps. +可轻松设置 Flutter 应用样式,包括主题切换、文本与 UI 组件设计等。 + ### Using dark mode +### 使用深色模式 + In **SwiftUI**, you call the `preferredColorScheme()` function on a `View` to use dark mode. +在 **SwiftUI** 中,在 `View` 上调用 `preferredColorScheme()` 使用深色模式。 + In **Flutter**, you can control light and dark mode at the app-level. To control the brightness mode, use the `theme` property of the `App` class: +在 **Flutter** 中,可在应用级用 `App` 的 `theme` 控制亮度模式。 + ```dart dartpad="18790cfaa8441085994373a4bc4f46b0" const CupertinoApp( @@ -908,10 +1250,14 @@ const CupertinoApp( ### Styling text +### 设置文本样式 + In **SwiftUI**, you use modifier functions to style text. For example, to change the font of a `Text` string, use the `font()` modifier: +在 **SwiftUI** 中,用 modifier 设置文本样式,例如用 `font()` 修改 `Text` 字体。 + ```swift Text("Hello, world!") .font(.system(size: 30, weight: .heavy)) @@ -921,6 +1267,8 @@ Text("Hello, world!") To style text in **Flutter**, add a `TextStyle` widget as the value of the `style` parameter of the `Text` widget. +在 **Flutter** 中,将 `TextStyle` 作为 `Text` 的 `style` 参数。 + ```dart dartpad="18790cfaa8441085994373a4bc4f46b0" Text( @@ -935,8 +1283,12 @@ Text( ### Styling buttons +### 设置按钮样式 + In **SwiftUI**, you use modifier functions to style buttons. +在 **SwiftUI** 中,用 modifier 设置按钮样式。 + ```swift Button("Do something") { // Do something when the button is tapped. @@ -949,12 +1301,21 @@ Button("Do something") { To style button widgets in **Flutter**, set the style of its child, or modify properties on the button itself. +在 **Flutter** 中,设置子项样式或修改按钮属性。 + In the following example: +在以下示例中: + - The `color` property of `CupertinoButton` sets its `color`. + + `CupertinoButton` 的 `color` 设置其颜色。 + - The `color` property of the child `Text` widget sets the button text color. + 子 `Text` widget 的 `color` 设置按钮文字颜色。 + ```dart dartpad="f8b6622f526fc5c7d5adadf1e071c28f" child: CupertinoButton( @@ -973,10 +1334,15 @@ child: CupertinoButton( ### Using custom fonts +### 使用自定义字体 + In **SwiftUI**, you can use a custom font in your app in two steps. First, add the font file to your SwiftUI project. After adding the file, use the `.font()` modifier to apply it to your UI components. +在 **SwiftUI** 中,两步使用自定义字体: +将字体文件加入项目,再用 `.font()` modifier 应用到 UI 组件。 + ```swift Text("Hello") .font( @@ -991,13 +1357,29 @@ In **Flutter**, you control your resources with a file named `pubspec.yaml`. This file is platform agnostic. To add a custom font to your project, follow these steps: +在 **Flutter** 中,用 `pubspec.yaml` 管理平台无关的资源。添加自定义字体步骤: + 1. Create a folder called `fonts` in the project's root directory. This optional step helps to organize your fonts. + + 在项目的根目录创建一个名为 `fonts` 的文件夹。此步骤可选,有助于组织你的字体。 + 1. Add your `.ttf`, `.otf`, or `.ttc` font file into the `fonts` folder. + + 将你的 `.ttf`、`.otf` 或 `.ttc` 字体文件放入 `fonts` 文件夹。 + 1. Open the `pubspec.yaml` file within the project. + + 打开项目中的 `pubspec.yaml` 文件。 + 1. Find the `flutter` section. + + 找到 `flutter` 部分。 + 1. Add your custom font(s) under the `fonts` section. + 在 `fonts` 部分下添加你的自定义字体。 + ```yaml flutter: fonts: @@ -1009,6 +1391,8 @@ To add a custom font to your project, follow these steps: After you add the font to your project, you can use it as in the following example: +添加字体后,可如下使用: + ```dart Text( @@ -1020,19 +1404,32 @@ Text( :::note To download custom fonts to use in your apps, check out [Google Fonts](https://fonts.google.com). + +要为应用下载自定义字体,请参阅 [Google Fonts](https://fonts.google.com)。 ::: ### Bundling images in apps +### 在应用中打包图片 + In **SwiftUI**, you first add the image files to `Assets.xcassets`, then use the `Image` view to display the images. +在 **SwiftUI** 中,先将图像加入 `Assets.xcassets`,再用 `Image` view 显示。 + To add images in **Flutter**, follow a method similar to how you added custom fonts. +在 **Flutter** 中添加图像的方式类似自定义字体。 + 1. Add an `images` folder to the root directory. + + 在根目录添加一个 `images` 文件夹。 + 1. Add this asset to the `pubspec.yaml` file. + 在 `pubspec.yaml` 文件中添加该资源。 + ```yaml flutter: assets: @@ -1042,30 +1439,58 @@ custom fonts. After adding your image, display it using the `Image` widget's `.asset()` constructor. This constructor: +添加图像后,用 `Image` widget 的 `.asset()` 构造函数显示。该构造函数: + 1. Instantiates the given image using the provided path. + + 使用提供的路径实例化给定的图像。 + 1. Reads the image from the assets bundled with your app. + + 从随应用捆绑的资源中读取该图像。 + 1. Displays the image on the screen. + 在屏幕上显示该图像。 + To review a complete example, check out the [`Image`][] docs. +完整示例请参阅 [`Image`][] 文档。 + ### Bundling videos in apps +### 在应用中打包视频 + In **SwiftUI**, you bundle a local video file with your app in two steps. First, you import the `AVKit` framework, then you instantiate a `VideoPlayer` view. +在 **SwiftUI** 中,两步捆绑本地视频:导入 `AVKit`,再实例化 `VideoPlayer` view。 + In **Flutter**, add the [video_player][] plugin to your project. This plugin allows you to create a video player that works on Android, iOS, and on the web from the same codebase. +在 **Flutter** 中,添加 [video_player][] 插件,可从同一代码库在 Android、iOS 与 Web 上播放视频。 + 1. Add the plugin to your app and add the video file to your project. + + 将该插件添加到你的应用,并将视频文件添加到项目中。 + 1. Add the asset to your `pubspec.yaml` file. + + 在 `pubspec.yaml` 文件中添加该资源。 + 1. Use the `VideoPlayerController` class to load and play your video file. + 使用 `VideoPlayerController` 类加载并播放你的视频文件。 + To review a complete walkthrough, check out the [video_player example][]. +完整教程请参阅 [video_player 示例][video_player example]。 + [Flutter for UIKit developers]: /flutter-for/uikit-devs [Add Flutter to existing app]: /add-to-app [Animations overview]: /ui/animations diff --git a/sites/docs/src/content/flutter-for/web-devs.md b/sites/docs/src/content/flutter-for/web-devs.md index 2e4aad5c40..6f18718b6a 100644 --- a/sites/docs/src/content/flutter-for/web-devs.md +++ b/sites/docs/src/content/flutter-for/web-devs.md @@ -1021,7 +1021,7 @@ use a [`RichText`][] widget instead. Its `text` property can specify one or more [`TextSpan`][] objects that can be individually styled. -一个 [`Text`][] widget 允许你展示同一类样式的文本。 +一个 [`Text`][] widget 让你展示同一类样式的文本。 为了展现具有多种样式(本例中,是一个带重音的单词)的文本, 你需要改用 [`RichText`][] widget。 它的 `text` 属性可以设置一个或多个可单独设置样式的 [`TextSpan`][]。 diff --git a/sites/docs/src/content/flutter-for/xamarin-forms-devs.md b/sites/docs/src/content/flutter-for/xamarin-forms-devs.md index 77c75ef8ce..40d92644f3 100644 --- a/sites/docs/src/content/flutter-for/xamarin-forms-devs.md +++ b/sites/docs/src/content/flutter-for/xamarin-forms-devs.md @@ -2819,7 +2819,7 @@ Xamarin.Forms `element`s allow you to directly query the `element` to determine the state of its properties, or whether it's bound to a property in a `ViewModel`. -Xamarin.Forms 的 `element` 允许你直接查询 `element` 来确定它的任何属性的状态, +Xamarin.Forms 的 `element` 让你直接查询 `element` 来确定它的任何属性的状态, 或者它被绑定到 `ViewModel` 中的属性。 Retrieving information in Flutter is handled by specialized widgets diff --git a/sites/docs/src/content/install/add-to-path.md b/sites/docs/src/content/install/add-to-path.md index b7916491a0..4655bd739b 100644 --- a/sites/docs/src/content/install/add-to-path.md +++ b/sites/docs/src/content/install/add-to-path.md @@ -1,8 +1,12 @@ --- -title: Add Flutter to your PATH -shortTitle: Add to PATH -description: >- - Learn how to add Flutter to your PATH after downloading the Flutter SDK. +# title: Add Flutter to your PATH +title: 将 Flutter 添加到 PATH +# shortTitle: Add to PATH +shortTitle: 添加到 PATH +# description: >- +# Learn how to add Flutter to your PATH after downloading the Flutter SDK. +description: 了解在下载 Flutter SDK 后如何将 Flutter 添加到你的 PATH。 +ai-translated: true --- Learn how to add Flutter to your `PATH` environment variable @@ -10,11 +14,20 @@ after downloading the SDK. Adding Flutter to your `PATH` allows you to use the `flutter` and `dart` command-line tools in terminals and IDEs. +了解如何在下载 SDK 后将 Flutter 添加到你的 `PATH` 环境变量。 +将 Flutter 添加到你的 `PATH` 后,你就可以在终端和 IDE 中使用 +`flutter` 和 `dart` 命令行工具。 + :::tip If you haven't downloaded Flutter yet, follow [Set up and test drive Flutter][] instead. + +如果你尚未下载 Flutter, +请先按照 [设置并试跑 Flutter][Set up and test drive Flutter] 进行操作。 ::: +[Set up and test drive Flutter]: /install/quick +

Add Flutter to your path on macOS.

+

在 macOS 上将 Flutter 添加到你的 `PATH`。

@@ -38,6 +53,7 @@ follow [Set up and test drive Flutter][] instead.

Add Flutter to your path on Linux.

+

在 Linux 上将 Flutter 添加到你的 `PATH`。

@@ -46,17 +62,19 @@ follow [Set up and test drive Flutter][] instead.

Add Flutter to your path on ChromeOS.

+

在 ChromeOS 上将 Flutter 添加到你的 `PATH`。

-[Set up and test drive Flutter]: /install/quick - ## Windows To run `flutter` and `dart` commands in a terminal on Windows, add the Flutter SDK's `bin` directory to the `Path` environment variable. +要在 Windows 终端中运行 `flutter` 和 `dart` 命令, +请将 Flutter SDK 的 `bin` 目录添加到 `Path` 环境变量。 + {% render "docs/install/path/windows.md" %} ## macOS @@ -64,13 +82,21 @@ add the Flutter SDK's `bin` directory to the `Path` environment variable. To run `flutter` and `dart` commands in a terminal on macOS, add the Flutter SDK's `bin` directory to the `PATH` environment variable. +要在 macOS 终端中运行 `flutter` 和 `dart` 命令, +请将 Flutter SDK 的 `bin` 目录添加到 `PATH` 环境变量。 + {% render "docs/install/path/macos.md" %} ## Linux +## Linux + To run `flutter` and `dart` commands in a terminal on Linux, add the Flutter SDK's `bin` directory to the `PATH` environment variable. +要在 Linux 终端中运行 `flutter` 和 `dart` 命令, +请将 Flutter SDK 的 `bin` 目录添加到 `PATH` 环境变量。 + {% render "docs/install/path/linux.md" %} ## ChromeOS @@ -78,4 +104,7 @@ add the Flutter SDK's `bin` directory to the `PATH` environment variable. To run `flutter` and `dart` commands in a terminal on chromeOS, add the Flutter SDK's `bin` directory to the `PATH` environment variable. +要在 ChromeOS 终端中运行 `flutter` 和 `dart` 命令, +请将 Flutter SDK 的 `bin` 目录添加到 `PATH` 环境变量。 + {% render "docs/install/path/chromeos.md" %} diff --git a/sites/docs/src/content/install/archive.md b/sites/docs/src/content/install/archive.md index 5ca2795839..aff74834dd 100644 --- a/sites/docs/src/content/install/archive.md +++ b/sites/docs/src/content/install/archive.md @@ -108,6 +108,8 @@ SDK 归档列表中的每个 Flutter 版本都有以下信息: ## Public release windows +## 公开发布窗口 + Predictability is key to landing complex features safely. We use public release windows to provide the community with the visibility needed to plan ahead. @@ -115,18 +117,36 @@ By explicitly calling out branch cutoff dates and release targets, we enable everyone to align their development cycles and coordinate feature landing effectively. +可预测性是安全合入复杂特性的关键。 +我们通过公开发布窗口,为社区提供提前规划所需的可见性。 +通过明确给出分支截止日期和发布目标, +我们让每个人都能对齐各自的开发周期, +并高效地协调特性的合入。 + ### What is a branch cutoff date? +### 什么是分支截止日期? + This date is the deadline for pull requests to land in the default branches (`main` for Dart and `master` for Flutter) to guarantee inclusion in the next stable release. +该日期是 pull request 合入默认分支(Dart 为 `main`,Flutter 为 `master`)的最后期限, +只有在此期限前合入,才能保证被纳入下一个稳定版发布。 + * **Before cutoff:** Your PR will ship in the next stable version. + + **截止日期之前:** 你的 PR 将随下一个稳定版本一起发布。 + * **After cutoff:** Your PR will wait for the following cycle. + **截止日期之后:** 你的 PR 将等到下一个发布周期。 + ### 2026 schedule -| Flutter version | Release target | Branch cutoff date | +### 2026 年发布计划 + +| Flutter versionFlutter 版本 | Release target发布目标 | Branch cutoff date分支截止日期 | |-----------------|----------------|--------------------| | Flutter 3.41 | February, 2026 | 2026-01-06 | | Flutter 3.44 | May, 2026 | 2026-04-07 | @@ -139,6 +159,8 @@ guarantee inclusion in the next stable release. ## Stable channel +## Stable 渠道 + @@ -153,6 +175,8 @@ guarantee inclusion in the next stable release. ## Beta channel +## Beta 渠道 + @@ -168,18 +192,35 @@ guarantee inclusion in the next stable release. :::tip To see what's changed in a beta release, compare the version tags on GitHub. +若要查看某个 beta 版本有哪些变更,可在 GitHub 上比较版本标签 (tag)。 + 1. Find the version number (tag) you want to see (for example, `3.38.0-0.2.pre`). + + 找到你想查看的版本号(tag,例如 `3.38.0-0.2.pre`)。 + 2. Find the previous version number (for example, `3.38.0-0.1.pre`). + + 找到上一个版本号(例如 `3.38.0-0.1.pre`)。 + 3. Go to the [GitHub compare page](https://github.com/flutter/flutter/compare). + + 打开 [GitHub 比较页面](https://github.com/flutter/flutter/compare)。 + 4. Select the older tag for the `base` field and the newer tag for the `compare` field. + 将较旧的 tag 选为 `base` 字段,将较新的 tag 选为 `compare` 字段。 + For example: [`flutter/flutter@3.38.0-0.1.pre...3.38.0-0.2.pre`](https://github.com/flutter/flutter/compare/3.38.0-0.1.pre...3.38.0-0.2.pre) + +例如:[`flutter/flutter@3.38.0-0.1.pre...3.38.0-0.2.pre`](https://github.com/flutter/flutter/compare/3.38.0-0.1.pre...3.38.0-0.2.pre) ::: ## Main channel +## Main 渠道 + [Installation bundles][] are not available for the `main` channel (which was previously known as the `master` channel). However, you can get the SDK directly from diff --git a/sites/docs/src/content/install/custom.md b/sites/docs/src/content/install/custom.md index 90fc3462fb..8b4c84b8cb 100644 --- a/sites/docs/src/content/install/custom.md +++ b/sites/docs/src/content/install/custom.md @@ -1,56 +1,81 @@ --- -title: Set up Flutter for your particular needs -shortTitle: Custom setup -description: >- - Install and set up Flutter for your preferred - development environment and target platforms. +# title: Set up Flutter for your particular needs +title: 根据你的需求配置 Flutter +# shortTitle: Custom setup +shortTitle: 自定义配置 +# description: >- +# Install and set up Flutter for your preferred +# development environment and target platforms. +description: 为你的首选开发环境和目标平台安装并配置 Flutter。 showBanner: false sitemap: false +ai-translated: true --- To get started developing with Flutter, follow these steps to install and set up Flutter for your preferred development environment and target platform. +要开始用 Flutter 进行开发, +请按照以下步骤,为你的首选开发环境和目标平台安装并配置 Flutter。 + If you plan to use VS Code or another Code - OSS derived editor like Antigravity, consider following the [Flutter quick start][] instead. +如果你计划使用 VS Code 或其他基于 Code OSS 的编辑器(例如 Antigravity), +建议改为按照 [Flutter 快速开始][Flutter quick start] 进行操作。 + [Flutter quick start]: /install/quick ## Install and set up Flutter {: #install} +## 安装并配置 Flutter + To get started developing apps with Flutter, install the Flutter SDK to your development device. Choose one of the following installation methods: +要开始用 Flutter 开发应用, +请在你的开发设备上安装 Flutter SDK。 +请选择以下任一安装方式: + ## Set up an IDE or editor {: #editor} +## 配置 IDE 或编辑器 + For the best development experience with Flutter, install an IDE or editor with support for Dart and Flutter. Some popular options include VS Code, Antigravity, Android Studio, and other Code OSS-based editors. +要获得最佳的 Flutter 开发体验, +请安装支持 Dart 和 Flutter 的 IDE 或编辑器。 +常见选择包括 VS Code、Antigravity、 +Android Studio 以及其他基于 Code OSS 的编辑器。 +

Set up Flutter support in Android Studio.

+

在 Android Studio 中配置 Flutter 支持。

@@ -74,73 +101,90 @@ Android Studio, and other Code OSS-based editors.

Set up Flutter support in an IntelliJ-based IDE.

+

在基于 IntelliJ 的 IDE 中配置 Flutter 支持。

## Set up a target platform {: #target-platform} +## 配置目标平台 + Once you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. +成功安装 Flutter 后, +请至少为一个目标平台配置开发环境, +以继续你的 Flutter 学习之旅。 + We recommend [developing for the web][web-setup] first as it requires no additional setup besides an appropriate browser. You can always set up development for additional target platforms later. +我们建议先 [面向 Web 开发][web-setup], +因为除合适的浏览器外无需额外配置。 +你随时可以在之后为更多目标平台配置开发环境。 + @@ -149,15 +193,26 @@ You can always set up development for additional target platforms later. ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 + **Congratulations!** Now that you've installed Flutter, set up an IDE or editor, and set up development for a target platform, you can continue your Flutter learning journey. +**恭喜!** +现在你已经安装了 Flutter、配置了 IDE 或编辑器, +并为某个目标平台配置好了开发环境, +可以继续你的 Flutter 学习之旅了。 + Follow the [Flutter learning pathway][], set up development for an [additional target platform][], or explore some of these other learning resources. +请按照 [Flutter 学习路径][Flutter learning pathway] 继续学习, +为 [其他目标平台][additional target platform] 配置开发环境, +或探索以下其他学习资源。 + {% render "docs/get-started/setup-next-steps.html", site: site %} [Flutter learning pathway]: /learn/pathway diff --git a/sites/docs/src/content/install/manual.md b/sites/docs/src/content/install/manual.md index 4b3b24ed70..5653eb7e6c 100644 --- a/sites/docs/src/content/install/manual.md +++ b/sites/docs/src/content/install/manual.md @@ -1,59 +1,102 @@ --- -title: Install Flutter manually -shortTitle: Install manually -breadcrumb: Manually -description: >- - Learn how to install and set up the Flutter SDK manually. +# title: Install Flutter manually +title: 手动安装 Flutter +# shortTitle: Install manually +shortTitle: 手动安装 +# breadcrumb: Manually +breadcrumb: 手动安装 +# description: >- +# Learn how to install and set up the Flutter SDK manually. +description: 了解如何手动安装并配置 Flutter SDK。 +ai-translated: true --- Learn how to install and manually set up your Flutter development environment. +了解如何手动安装并配置 +你的 Flutter 开发环境。 + :::tip If you're just looking to quickly install Flutter, consider [installing Flutter with VS Code][with-vs-code] for a streamlined setup experience. + +若你只想快速安装 Flutter, +可考虑 [使用 VS Code 安装 Flutter][with-vs-code], +获得更简化的配置体验。 ::: [with-vs-code]: /install/with-vs-code ## Choose your development platform {: #dev-platform} +## 选择你的开发平台 + The instructions on this page are configured to cover installing Flutter on a **Windows**{:.selected-os-text} device. +本页说明默认针对在 **Windows**{:.selected-os-text} 设备上安装 Flutter。 + If you'd like to follow the instructions for a different OS, please select one of the following. +若要查看其他操作系统的说明, +请选择以下选项之一。 + ## Download prerequisite software {: #download-prerequisites} +## 下载必备软件 + Before installing the Flutter SDK, first complete the following setup. +在安装 Flutter SDK 之前, +请先完成以下配置。 + 1.

Install Git for Windows

+

为 Windows 安装 Git

+ Download and install the latest version of [Git for Windows][]. + 下载并安装最新版本的 [Git for Windows][]。 + For help installing or troubleshooting Git, reference the [Git documentation][git-install]. + 有关安装或排查 Git 问题的帮助, + 请参阅 [Git 文档][git-install]。 + 1.

Set up an editor or IDE

+

配置编辑器或 IDE

+ For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support][editors]{: target="_blank"}. + 为获得最佳的 Flutter 应用开发体验, + 请考虑安装并配置 + [支持 Flutter 的编辑器或 IDE][editors]{: target="_blank"}。 + {: .steps .windows-only} 1.

Install the Xcode command-line tools

+

安装 Xcode 命令行工具

+ Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git. + 下载 Xcode 命令行工具以获取 Flutter 依赖的命令行工具(包括 Git)。 + To download the tools, run the following command in your preferred terminal: + 要下载这些工具,请在你偏好的终端中运行以下命令: + ```console $ xcode-select --install ``` @@ -62,19 +105,34 @@ first complete the following setup. a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**. + 若你尚未安装这些工具, + 应会弹出对话框确认你是否要安装。 + 点击 **Install**,安装完成后点击 **Done**。 + 1.

Set up an editor or IDE

+

配置编辑器或 IDE

+ For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support][editors]{: target="_blank"}. + 为获得最佳的 Flutter 应用开发体验, + 请考虑安装并配置 + [支持 Flutter 的编辑器或 IDE][editors]{: target="_blank"}。 + {: .steps .macos-only} 1.

Download and install prerequisite packages

+

下载并安装必备 package

+ Using your preferred package manager or mechanism, install the latest versions of the following packages: + 使用你偏好的 package 管理器或安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -85,6 +143,9 @@ first complete the following setup. On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: + 在基于 Debian 且使用 `apt-get` 的发行版(例如 Ubuntu)上, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -92,26 +153,45 @@ first complete the following setup. 1.

Set up an editor or IDE

+

配置编辑器或 IDE

+ For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support][editors]{: target="_blank"}. + 为获得最佳的 Flutter 应用开发体验, + 请考虑安装并配置 + [支持 Flutter 的编辑器或 IDE][editors]{: target="_blank"}。 + {: .steps .linux-only} 1.

Set up Linux support

+

配置 Linux 支持

+ If you haven't set up Linux support on your Chromebook before, [Turn on Linux support][chromeos-linux]. + 若你此前未在 Chromebook 上配置 Linux 支持, + 请参阅 [开启 Linux 支持][chromeos-linux]。 + If you've already turned on Linux support, ensure it's up to date following the [Fix problems with Linux][chromeos-linux-update] instructions. + 若你已开启 Linux 支持, + 请按照 [修复 Linux 相关问题][chromeos-linux-update] 的说明确保其为最新状态。 + 1.

Download and install prerequisite packages

+

下载并安装必备 package

+ Using `apt-get` or your preferred installation mechanism, install the latest versions of the following packages: + 使用 `apt-get` 或你偏好的安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -122,6 +202,9 @@ first complete the following setup. If you want to use `apt-get`, install these packages using the following commands: + 若要使用 `apt-get`, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -129,10 +212,16 @@ first complete the following setup. 1.

Set up an editor or IDE

+

配置编辑器或 IDE

+ For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support][editors]{: target="_blank"}. + 为获得最佳的 Flutter 应用开发体验, + 请考虑安装并配置 + [支持 Flutter 的编辑器或 IDE][editors]{: target="_blank"}。 + {: .steps .chromeos-only} [editors]: /tools/editors @@ -143,40 +232,76 @@ first complete the following setup. ## Install and set up Flutter {: #install-flutter} +## 安装并配置 Flutter + To install the Flutter SDK, download the latest bundle from the SDK archive, then extract the SDK to where you want it stored. +要安装 Flutter SDK, +请从 SDK 归档下载最新 bundle, +然后将其解压到你希望存放的位置。 + 1.

Download the Flutter SDK bundle

+

下载 Flutter SDK bundle

+ Download the following installation bundle to get the latest stable release of the Flutter SDK. + 下载以下安装 bundle 以获取 + Flutter SDK 的最新稳定版。 + 1.

Create a folder to store the SDK

+

创建用于存放 SDK 的文件夹

+ Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `%USERPROFILE%\develop` (`C:\Users\{username}\develop`). + 创建或找到一个用于存放解压后 SDK 的文件夹。 + 可考虑在 `%USERPROFILE%\develop`(`C:\Users\{username}\develop`)创建并使用该目录。 + :::note Select a location that doesn't have special characters or spaces in its path and doesn't require elevated privileges. + + 选择的路径中不要包含特殊字符或空格, + 且不需要提升权限。 ::: 1.

Extract the SDK

+

解压 SDK

+ Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. + 将你下载的 SDK bundle 解压到 + 你希望存放 Flutter SDK 的目录。 + 1. Copy the following command. + + 复制以下命令。 + 1. Replace `` with the path to the bundle you downloaded. + + 将 `` 替换为你下载的 bundle 路径。 + 1. Replace `` with the path to the folder you want the extracted SDK to be in. + + 将 `` 替换为 + 你希望存放解压后 SDK 的文件夹路径。 + 1. Run the edited command in your preferred terminal. + 在你偏好的终端中运行编辑后的命令。 + ```console $ Expand-Archive –Path -Destination ``` @@ -185,6 +310,10 @@ then extract the SDK to where you want it stored. the `%USERPROFILE%\Downloads` directory and want to store the extracted SDK in the `%USERPROFILE%\develop` directory: + 例如,若你将 Flutter 3.29.3 的 bundle 下载到 + `%USERPROFILE%\Downloads` 目录,并希望将解压后的 SDK 存放在 + `%USERPROFILE%\develop` 目录: + ```console $ Expand-Archive ` -Path $env:USERPROFILE\Downloads\flutter_windows_3.29.3-stable.zip ` @@ -196,36 +325,69 @@ then extract the SDK to where you want it stored. after extraction, your antivirus software might have quarantined it. If this happens, configure your antivirus to trust the Flutter SDK directory, then extract the bundle again. + + 若解压后 `bin` 目录中缺少 `flutter.bat` 文件, + 可能是杀毒软件将其隔离了。 + 若出现此情况,请将 Flutter SDK 目录加入杀毒软件信任列表, + 然后重新解压 bundle。 ::: {: .steps .windows-only} 1.

Download the Flutter SDK bundle

+

下载 Flutter SDK bundle

+ Depending on your macOS device's cpu architecture, download one of the following installation bundles to get the latest stable release of the Flutter SDK. + 根据你的 macOS 设备的 CPU 架构, + 下载以下任一安装 bundle 以获取 + Flutter SDK 的最新稳定版。 + | Apple Silicon (ARM64) | Intel | |--------------------------------------------------|------------------------------------------------| | | | 1.

Create a folder to store the SDK

+

创建用于存放 SDK 的文件夹

+ Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `~/develop/`. + 创建或找到一个用于存放解压后 SDK 的文件夹。 + 可考虑在 `~/develop/` 创建并使用该目录。 + 1.

Extract the SDK

+

解压 SDK

+ Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. + 将你下载的 SDK bundle 解压到 + 你希望存放 Flutter SDK 的目录。 + 1. Copy the following command. + + 复制以下命令。 + 1. Replace `` with the path to the bundle you downloaded. + + 将 `` 替换为你下载的 bundle 路径。 + 1. Replace `` with the path to the folder you want the extracted SDK to be in. + + 将 `` 替换为 + 你希望存放解压后 SDK 的文件夹路径。 + 1. Run the edited command in your preferred terminal. + 在你偏好的终端中运行编辑后的命令。 + ```console $ unzip -d ``` @@ -234,6 +396,9 @@ then extract the SDK to where you want it stored. the `~/Downloads` directory and want to store the extracted SDK in the `~/develop` directory: + 例如,若你将 Flutter 3.29.3 的 bundle 下载到 + `~/Downloads` 目录,并希望将解压后的 SDK 存放在 `~/develop` 目录: + ```console $ unzip ~/Downloads/flutter_macos_3.29.3-stable.zip -d ~/develop/ ``` @@ -242,27 +407,53 @@ then extract the SDK to where you want it stored. 1.

Download the Flutter SDK bundle

+

下载 Flutter SDK bundle

+ Download the following installation bundle to get the latest stable release of the Flutter SDK. + 下载以下安装 bundle 以获取 + Flutter SDK 的最新稳定版。 + 1.

Create a folder to store the SDK

+

创建用于存放 SDK 的文件夹

Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `~/develop/`. + 创建或找到一个用于存放解压后 SDK 的文件夹。 + 可考虑在 `~/develop/` 创建并使用该目录。 + 1.

Extract the SDK

+

解压 SDK

+ Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. + 将你下载的 SDK bundle 解压到 + 你希望存放 Flutter SDK 的目录。 + 1. Copy the following command. + + 复制以下命令。 + 1. Replace `` with the path to the bundle you downloaded. + + 将 `` 替换为你下载的 bundle 路径。 + 1. Replace `` with the path to the folder you want the extracted SDK to be in. + + 将 `` 替换为 + 你希望存放解压后 SDK 的文件夹路径。 + 1. Run the edited command in your preferred terminal. + 在你偏好的终端中运行编辑后的命令。 + ```console $ tar -xf -C ``` @@ -271,6 +462,9 @@ then extract the SDK to where you want it stored. the `~/Downloads` directory and want to store the extracted SDK in the `~/develop` directory: + 例如,若你将 Flutter 3.29.3 的 bundle 下载到 + `~/Downloads` 目录,并希望将解压后的 SDK 存放在 `~/develop` 目录: + ```console $ tar -xf ~/Downloads/flutter_linux_3.29.3-stable.tar.xz -C ~/develop/ ``` @@ -279,11 +473,18 @@ then extract the SDK to where you want it stored. ## Add Flutter to your PATH {: #add-to-path} +## 将 Flutter 添加到 PATH + Now that you've downloaded the SDK, add the Flutter SDK's `bin` directory to your `PATH` environment variable. Adding Flutter to your `PATH` allows you to use the `flutter` and `dart` command-line tools in terminals and IDEs. +现在你已经下载了 SDK, +请将 Flutter SDK 的 `bin` 目录添加到你的 `PATH` 环境变量。 +将 Flutter 添加到你的 `PATH` 后,你就可以在终端和 IDE 中使用 +`flutter` 和 `dart` 命令行工具。 +
{% render "docs/install/path/windows.md" %} @@ -310,15 +511,25 @@ Adding Flutter to your `PATH` allows you to use the ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 + Now that you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. +现在你已经成功安装了 Flutter, +请至少为一个目标平台配置开发环境, +以继续你的 Flutter 学习之旅。 + :::recommend If you don't yet have a preferred platform to target during development, the Flutter team recommends you first try out [developing on the web][web-setup]! + +若你尚未确定开发时要面向的平台, +Flutter 团队建议你首先尝试 +[在 Web 上开发][web-setup]! ::: [web-setup]: /platform-integration/web/setup @@ -331,27 +542,27 @@ the Flutter team recommends you first try out alt="A representation of Flutter on multiple devices.">
- Set up a target platform + Set up a target platform配置目标平台
@@ -364,15 +575,15 @@ the Flutter team recommends you first try out alt="Dash helping you explore Flutter learning resources.">
- Learn Flutter development + Learn Flutter development学习 Flutter 开发
@@ -384,18 +595,18 @@ the Flutter team recommends you first try out aria-hidden="true" alt="Keep up to date with Flutter">
- Stay up to date with Flutter + Stay up to date with Flutter跟进 Flutter 最新动态
diff --git a/sites/docs/src/content/install/quick.md b/sites/docs/src/content/install/quick.md index 5e20a65173..f4d56fc71f 100644 --- a/sites/docs/src/content/install/quick.md +++ b/sites/docs/src/content/install/quick.md @@ -1,113 +1,214 @@ --- -title: Set up and test drive Flutter -shortTitle: Quick start -description: >- - Set up Flutter on your device with a Code OSS-based editor, such as VS Code, and - get started developing your first multi-platform app with Flutter! +# title: Set up and test drive Flutter +title: 设置并试跑 Flutter +# shortTitle: Quick start +shortTitle: 快速开始 +# description: >- +# Set up Flutter on your device with a Code OSS-based editor, such as VS Code, and +# get started developing your first multi-platform app with Flutter! +description: 在你的设备上使用基于 Code OSS 的编辑器(例如 VS Code)配置 Flutter,并开始开发你的第一个多平台应用! showBanner: false sitemap: false +ai-translated: true --- {% render "docs/install/quick.md" site: site %} ## Test drive Flutter {: #test-drive} +## 试跑 Flutter + Now that you've set up VS Code and Flutter, it's time to create an app and try out Flutter development! +现在你已经配置好了 VS Code 和 Flutter, +是时候创建一个应用并体验 Flutter 开发了! + 1.

Create a new Flutter app

+

创建新的 Flutter 应用

+ 1. Open the command palette in VS Code. + 在 VS Code 中打开命令面板。 + Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. + 依次选择 **View** > **Command Palette**, + 或按 Cmd/Ctrl + + Shift + P。 + 1. In the command palette, start typing `flutter:`. + 在命令面板中,开始输入 `flutter:`。 + VS Code should surface commands from the Flutter plugin. + VS Code 应会显示来自 Flutter 插件的命令。 + 1. Select the **Flutter: New Project** command. + 选择 **Flutter: New Project** 命令。 + Your OS or VS Code might ask for access to your documents, agree to continue to the next step. + 你的操作系统或 VS Code 可能会请求访问你的文档, + 请同意以继续下一步。 + 1. Choose the **Application** template. + 选择 **Application** 模板。 + VS Code should prompt you with **Which Flutter template?**. Choose **Application** to bootstrap a simple counter app. + VS Code 应会提示 **Which Flutter template?**。 + 选择 **Application** 以引导创建一个简单的计数器应用。 + 1. Create or select the parent directory for your new app's folder. + 创建或选择新应用文件夹的父目录。 + A file dialog should appear. + 应会出现文件对话框。 + 1. Select or create the parent directory where you want the project to be created. + + 选择或创建你希望创建项目的父目录。 + 1. To confirm your selection, click **Select a folder to create the project in**. + 要确认你的选择,请点击 **Select a folder to create the project in**。 + 1. Enter a name for your app. + 为你的应用输入名称。 + VS Code should prompt you to enter a name for your new app. Enter `trying_flutter` or a similar `lowercase_with_underscores` name. To confirm your selection, press Enter. + VS Code 应会提示你输入新应用的名称。 + 输入 `trying_flutter` 或类似的 `lowercase_with_underscores` 名称。 + 要确认你的选择,请按 Enter。 + 1. Wait for project initialization to complete. + 等待项目初始化完成。 + Task progress is often surfaced as a notification in the bottom right and can also be accessed from the **Output** panel. + 任务进度通常会在右下角以通知形式显示, + 也可从 **Output** 面板查看。 + 1. Open the `lib` directory, then the `main.dart` file. + 打开 `lib` 目录,然后打开 `main.dart` 文件。 + If you're curious about what each portion of the code does, check out the preceding comments throughout the file. + 若你想了解代码各部分的作用, + 可查看文件中前面的注释。 + 1.

Run your app on the web

+

在 Web 上运行你的应用

+ While Flutter apps can run on many platforms, try running your new app on the web. + Flutter 应用可在许多平台上运行, + 请尝试在 Web 上运行你的新应用。 + 1. Open the command palette in VS Code. + 在 VS Code 中打开命令面板。 + Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. + 依次选择 **View** > **Command Palette**, + 或按 Cmd/Ctrl + + Shift + P。 + 1. In the command palette, start typing `flutter:`. + 在命令面板中,开始输入 `flutter:`。 + VS Code should surface commands from the Flutter plugin. + VS Code 应会显示来自 Flutter 插件的命令。 + 1. Select the **Flutter: Select Device** command. + 选择 **Flutter: Select Device** 命令。 + 1. From the **Select Device** prompt, select **Chrome**. + 在 **Select Device** 提示中选择 **Chrome**。 + 1. Run or start debugging your app. + 运行或开始调试你的应用。 + Go to **Run** > **Start Debugging** or press F5. + 依次选择 **Run** > + **Start Debugging**,或按 F5。 + `flutter run` is used to build and start your app, then a new instance of Chrome should open and start running your newly created app. + 会使用 `flutter run` 构建并启动你的应用, + 随后应会打开新的 Chrome 实例并开始运行你刚创建的应用。 + 1.

Try hot reload

+

尝试热重载

+ Flutter offers a fast development cycle with **stateful hot reload**, the ability to reload the code of a live running app without restarting or losing app state. + Flutter 提供带有 **带状态的热重载** 的快速开发周期, + 可在不重启应用、不丢失应用状态的情况下 + 重新加载正在运行的应用的代码。 + You can change your app's source code, run the hot reload command in VS Code, then see the change in your running app. + 你可以修改应用的源代码, + 在 VS Code 中运行热重载命令, + 然后在正在运行的应用中看到变化。 + 1. In the running app, try adding to the counter a few times by clicking the ![increment (+)][increment-button]{: .text-icon} button. + 在正在运行的应用中,尝试多次点击 + ![increment (+)][increment-button]{: .text-icon} 按钮以增加计数。 + 1. With your app still running, make a change in the `lib/main.dart` file. + 在应用仍在运行时,修改 `lib/main.dart` 文件。 + Change the `_counter++` line in the `_incrementCounter` method to instead decrement the `_counter` field. + 将 `_incrementCounter` 方法中的 `_counter++` 行 + 改为递减 `_counter` 字段。 + ```dart diff setState(() { // ... @@ -120,48 +221,91 @@ it's time to create an app and try out Flutter development! (**File** > **Save All**) or click the **Hot Reload** ![hot reload icon][]{: .text-icon} button. + 保存你的更改 + (**File** > **Save All**),或 + 点击 **Hot Reload** ![hot reload icon][]{: .text-icon} 按钮。 + Flutter updates the running app without losing any existing state. Notice the existing value stayed the same. + Flutter 会更新正在运行的应用且不会丢失任何现有状态。 + 请注意现有数值保持不变。 + 1. Try clicking the ![increment (+)][increment-button]{: .text-icon} button again. Notice the value decreases instead of increases. + 再次尝试点击 + ![increment (+)][increment-button]{: .text-icon} 按钮。 + 请注意数值会减少而不是增加。 + 1.

Explore the Flutter sidebar

+

探索 Flutter 侧边栏

+ The Flutter plugin adds a dedicated sidebar to VS Code for managing Flutter debug sessions and devices, viewing an outline of your code and widgets, as well as accessing the Dart and Flutter DevTools. + Flutter 插件会在 VS Code 中添加专用侧边栏, + 用于管理 Flutter 调试会话和设备、 + 查看代码和 widget 大纲, + 以及访问 Dart 和 Flutter DevTools。 + 1. If your app isn't running, start debugging it again. + 若你的应用未在运行,请再次开始调试。 + Go to **Run** > **Start Debugging** or press F5. + 依次选择 **Run** > + **Start Debugging**,或按 F5。 + 1. Open the Flutter sidebar in VS Code. + 在 VS Code 中打开 Flutter 侧边栏。 + Either open it with the Flutter ![Flutter logo][]{: .text-icon} button in the VS Code sidebar or open it from the command palette by running the **Flutter: Focus on Flutter Sidebar View** command. + 可通过 VS Code 侧边栏中的 Flutter ![Flutter logo][]{: .text-icon} 按钮打开, + 或在命令面板中运行 **Flutter: Focus on Flutter Sidebar View** 命令打开。 + 1. In the Flutter sidebar, under **DevTools**, click the **Flutter Inspector** button. + 在 Flutter 侧边栏的 **DevTools** 下, + 点击 **Flutter Inspector** 按钮。 + A separate **Widget Inspector** panel should open in VS Code. + VS Code 中应会打开单独的 **Widget Inspector** 面板。 + In the widget inspector, you can view your app's widget tree, view the properties and layout of each widget, and more. + 在 widget inspector 中,你可以查看应用的 widget 树、 + 查看每个 widget 的属性和布局等。 + 1. In the widget inspector, try clicking the top-level `MyHomePage` widget. + 在 widget inspector 中,尝试点击顶层的 `MyHomePage` widget。 + A view of its properties and layout should open, and the VS Code editor should navigate to and focus the line where the widget was included. + 应会打开其属性和布局视图, + VS Code 编辑器应会导航并聚焦到包含该 widget 的行。 + 1. Explore and try out other features in the widget inspector and Flutter sidebar. + 在 widget inspector 和 Flutter 侧边栏中探索并尝试其他功能。 + {:.steps} [increment-button]: /assets/images/docs/get-started/increment-button.png @@ -170,12 +314,20 @@ it's time to create an app and try out Flutter development! ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 + **Congratulations!** Now that you've installed and tried out Flutter, follow the [Flutter learning pathway][], set up development for an [additional target platform][], or explore some of these resources to continue your Flutter learning journey. +**恭喜!** +现在你已经安装并试跑了 Flutter, +请按照 [Flutter 学习路径][Flutter learning pathway] 继续学习, +为 [其他目标平台][additional target platform] 配置开发环境, +或探索以下资源以继续你的 Flutter 学习之旅。 + {% render "docs/get-started/setup-next-steps.html", site: site %} [Flutter learning pathway]: /learn/pathway diff --git a/sites/docs/src/content/install/troubleshoot.md b/sites/docs/src/content/install/troubleshoot.md index 6bc6472dd8..d2cd63e02f 100644 --- a/sites/docs/src/content/install/troubleshoot.md +++ b/sites/docs/src/content/install/troubleshoot.md @@ -1,15 +1,22 @@ --- -title: Troubleshooting installation -shortTitle: Troubleshoot -description: >- - Get help with common installation issues that new - Flutter developers might have run into. +# title: Troubleshooting installation +title: 安装问题排查 +# shortTitle: Troubleshoot +shortTitle: 排查 +# description: >- +# Get help with common installation issues that new +# Flutter developers might have run into. +description: 获取常见安装问题的帮助,这些问题可能是新 Flutter 开发者会遇到的。 +ai-translated: true --- This page describes some common installation issues that new Flutter users have encountered and offers suggestions on how to resolve them. +本页介绍一些新 Flutter 用户遇到的常见安装问题, +并提供解决建议。 + If you are still experiencing problems after using this page, consider reaching out to any of the resources listed under [community support channels][]. @@ -17,20 +24,36 @@ To add a topic to this page or make a correction, you can [file an issue][] or submit a [pull request][] on GitHub. +若使用本页后问题仍未解决, +可考虑通过 [社区支持渠道][community support channels] 中列出的资源寻求帮助。 +要为本页添加主题或提交更正, +你可以在 GitHub 上 [提交 issue][file an issue] 或 +发起 [pull request][pull request]。 + [community support channels]: #community-support [file an issue]: {{site.github}}/flutter/website/issues/new [pull request]: {{site.github}}/flutter/website/pulls ## Get the Flutter SDK +## 获取 Flutter SDK + ### Unable to find the `flutter` command +### 找不到 `flutter` 命令 + __What does this issue look like?__ +**这个问题看起来是怎样的?** + When you try to run the `flutter` command, the console fails to find it. The error usually looks as follows: +当你尝试运行 `flutter` 命令时, +终端找不到该命令。 +错误通常如下所示: + ```plaintext 'flutter' is not recognized as an internal or external command operable program or batch file ``` @@ -38,29 +61,47 @@ The error usually looks as follows: Error messages on macOS and Linux could look slightly different from the one on Windows. +macOS 和 Linux 上的错误信息可能与 Windows 上的略有不同。 + __Explanation and suggestions__ +**说明与建议** + Did you add Flutter to the `PATH` environment variable for your platform? On Windows, follow these [instructions for adding a command to your path][windows path]. +你是否已将 Flutter 添加到你所在平台的 `PATH` 环境变量? +在 Windows 上,请按照这些 [将命令添加到你的 PATH 的说明][windows path] 操作。 + If you've already [set up VS Code][] for Flutter development, you can use the Flutter extension's **Locate SDK** prompt to identify the location of your `flutter` folder. +若你已为 Flutter 开发 [配置 VS Code][set up VS Code], +可以使用 Flutter 扩展的 **Locate SDK** 提示 +来定位你的 `flutter` 文件夹位置。 + See also: [Configuring PATH and Environment Variables - Dart Code][config path] +另请参阅:[Configuring PATH and Environment Variables - Dart Code][config path] + [windows path]: https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows [set up VS Code]: /tools/vs-code#setup [config path]: https://dartcode.org/docs/configuring-path-and-environment-variables/ ### Flutter in special folders +### Flutter 位于特殊文件夹中 __What does this issue look like?__ +**这个问题看起来是怎样的?** + Running your Flutter project produces an error like the following: +运行 Flutter 项目时会出现类似以下的错误: + ```plaintext The Flutter SDK is installed in a protected folder and may not function correctly. Please move the SDK to a location that is user-writable without Administration permissions and restart. @@ -68,18 +109,29 @@ Please move the SDK to a location that is user-writable without Administration p __Explanation and suggestions__ +**说明与建议** + On Windows, this usually happens when Flutter is installed in a directory like `C:\Program Files\` that requires elevated privileges. Try relocating Flutter to a different folder, such as `C:\src\flutter`. +在 Windows 上,这通常发生在将 Flutter 安装在 +`C:\Program Files\` 等需要提升权限的目录时。 +请尝试将 Flutter 移到其他文件夹, +例如 `C:\src\flutter`。 + ### Invoke-Expression: You cannot call a method on a null-valued expression __What does this issue look like?__ +**这个问题看起来是怎样的?** + When running `flutter doctor` on Windows, you might see an error like: +在 Windows 上运行 `flutter doctor` 时,你可能会看到类似以下的错误: + ```plaintext Invoke-Expression : You cannot call a method on a null-valued expression. At ...\update_engine_version.ps1:60 char:20 @@ -87,35 +139,61 @@ At ...\update_engine_version.ps1:60 char:20 __Explanation and suggestions__ +**说明与建议** + This error typically occurs when the `SystemRoot` environment variable is missing or when the PowerShell execution policy prevents the script from running correctly. +此错误通常发生在缺少 `SystemRoot` 环境变量时, +或 PowerShell 执行策略阻止脚本正常运行时。 + To resolve this: +要解决此问题: + 1. **Run as Administrator**: Open your PowerShell terminal as an Administrator. + **以管理员身份运行**: + 以管理员身份打开 PowerShell 终端。 + 2. **Check Environment Variables**: Ensure the `SystemRoot` environment variable is set (usually to `C:\Windows`). You can check its value by running `echo $env:SystemRoot` in your PowerShell terminal. + **检查环境变量**: + 确保已设置 `SystemRoot` 环境变量(通常为 `C:\Windows`)。你可以在 PowerShell 终端中运行 `echo $env:SystemRoot` 来检查其值。 + 3. **Check Execution Policy**: If the issue persists, you might need to adjust your execution policy. Run the following command in an Administrator PowerShell window: + **检查执行策略**: + 若问题仍然存在,你可能需要调整执行策略。 + 在管理员 PowerShell 窗口中运行以下命令: + ```powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser ``` ## Android setup +## Android 配置 + ### Having multiple versions of Java installed +### 安装了多个 Java 版本 + __What does this issue look like?__ +**这个问题看起来是怎样的?** + The command `flutter doctor --android-licenses` fails. Running `flutter doctor --verbose` gives an error message like the following: +命令 `flutter doctor --android-licenses` 失败。 +运行 `flutter doctor --verbose` 会显示类似以下的错误信息: + ```plaintext java.lang.UnsupportedClassVersionError: com/android/prefs/AndroidLocationsProvider has been compiled by a more recent version of the Java Runtime (class file version 55.0), @@ -124,14 +202,23 @@ this version of the Java Runtime only recognizes class file versions up to 52.0 __Explanation and suggestions__ +**说明与建议** + The error occurs when an older version of the Java Development Kit (JDK) is installed on your computer. +当你的计算机上安装了较旧版本的 +Java Development Kit (JDK) 时会出现此错误。 + If you don't need multiple versions of Java, uninstall existing JDKs from your computer. Flutter automatically uses the JDK included in Android Studio. +若你不需要多个 Java 版本, +请从计算机上卸载现有的 JDK。 +Flutter 会自动使用 Android Studio 自带的 JDK。 + If you do need another version of Java, try the workaround described in [this GitHub issue][java binary path] @@ -141,18 +228,33 @@ check out the [Android Java Gradle migration guide][] or [flutter doctor --android-licenses not working due to java.lang.UnsupportedClassVersionError - Stack Overflow][so java version]. +若你确实需要其他 Java 版本, +在长期方案实施之前,可尝试 +[this GitHub issue][java binary path] 中描述的变通方法。 +更多信息请参阅 [Android Java Gradle 迁移指南][Android Java Gradle migration guide] +或 [flutter doctor --android-licenses not working due to + java.lang.UnsupportedClassVersionError - Stack Overflow][so java version]。 + [java binary path]: {{site.repo.flutter}}/issues/106416#issuecomment-1522198064 [Android Java Gradle migration guide]: /release/breaking-changes/android-java-gradle-migration-guide [so java version]: {{site.so}}/questions/75328050/ ### `cmdline-tools` component is missing +### 缺少 `cmdline-tools` 组件 + __What does this issue look like?__ +**这个问题看起来是怎样的?** + The `flutter doctor` command complains that the `cmdline-tools` are missing from the Android toolchain. For example: +`flutter doctor` 命令会提示 Android 工具链中 +缺少 `cmdline-tools`。 +例如: + ```plaintext noHighlight [!] Android toolchain - develop for Android devices (Android SDK version 33.0.2) • Android SDK at C:\Users\My PC\AppData\Local\Android\sdk @@ -161,33 +263,60 @@ For example: __Explanation and suggestions__ +**说明与建议** + The easiest way to get the cmdline-tools is through the SDK Manager in Android Studio. To do this, use the following instructions: +获取 cmdline-tools 的最简单方式是通过 Android Studio 中的 +SDK Manager。 +请按以下步骤操作: + 1. Open the SDK Manager from Android Studio by selecting **Tools > SDK Manager** from the menu bar. + + 在 Android Studio 中,从菜单栏选择 **Tools > SDK Manager** 打开 SDK Manager。 + 2. Select the latest Android SDK (or a specific version that your app requires), Android SDK Command-line Tools, and Android SDK Build-Tools. + + 选择最新的 Android SDK + (或你的应用所需的特定版本)、 + Android SDK Command-line Tools 以及 Android SDK Build-Tools。 + 3. Click **Apply** to install the selected artifacts. + 点击 **Apply** 安装所选组件。 + ![Android Studio SDK Manager](/assets/images/docs/get-started/install_android_tools.png) If you're not using Android Studio, you can download the tools using the [sdkmanager][] command-line tool. +若你未使用 Android Studio, +可以使用 [sdkmanager][] 命令行工具下载这些工具。 + [sdkmanager]: {{site.android-dev}}/studio/command-line/sdkmanager ## macOS setup +## macOS 配置 + ### SocketException: Send failed, OS Error: No route to host, errno = 65 +### SocketException:发送失败,OS 错误:No route to host,errno = 65 + __What does this issue look like?__ +**这个问题看起来是怎样的?** + On macOS, the `flutter run` command produces an error like: +在 macOS 上,`flutter run` 命令会产生类似以下的错误: + ```plaintext $ flutter run Launching lib/main.dart in debug mode... @@ -198,26 +327,47 @@ Oops; flutter has exited unexpectedly: "SocketException: Send failed (OS Error: __Explanation and suggestions__ +**说明与建议** + This issue is related to macOS permissions. +此问题与 macOS 权限有关。 + To fix this: +要解决此问题: + 1. Upgrade your Flutter SDK to the latest version. + 将 Flutter SDK 升级到最新版本。 + 2. Open **System Settings** > **Privacy & Security** > **Local Network**. Toggle on the permission for all the code editors and terminals you use to launch Flutter apps. You might need to restart your code editor, terminal, and physical device. + 打开 **System Settings** > **Privacy & Security** > **Local Network**。 + 为你用于启动 Flutter 应用的所有代码编辑器和终端开启权限。 + 你可能需要重启代码编辑器、终端和实体设备。 + ## Other problems +## 其他问题 + ### Exit code 69 +### 退出代码 69 + __What does this issue look like?__ +**这个问题看起来是怎样的?** + Running a `flutter` command produces an "exit code: 69" error, as shown in the following example: +运行 `flutter` 命令会出现「exit code: 69」错误, +如下例所示: + ```plaintext Running "flutter pub get" in flutter_tools... Resolving dependencies in .../flutter/packages/flutter_tools... (28.0s) @@ -237,38 +387,76 @@ exit code: 69 __Explanation and suggestions__ +**说明与建议** + This issue is related to networking. Try the following instructions to troubleshoot: +此问题与网络有关。 +请尝试以下步骤进行排查: + * Check your internet connection. Make sure that you're connected to the internet and that your connection is stable. + + 检查你的网络连接。 + 确保你已连接到互联网且连接稳定。 + * Restart your devices, including your computer and networking equipment. + + 重启你的设备,包括计算机和网络设备。 + * Use a VPN to help to bypass any restrictions that might prevent you from connecting to the network. + + 使用 VPN 以帮助绕过可能阻止你连接网络的限制。 + * If you have tried all of these steps and are still getting the error, print out verbose logs with the `flutter doctor -v` command and ask for help in one of the [community support channels][]. + 若你已尝试以上所有步骤仍出现错误, + 请使用 `flutter doctor -v` 命令输出详细日志, + 并在 [社区支持渠道][community support channels] 之一寻求帮助。 + [community support channels]: #community-support ## Community support +## 社区支持 + The Flutter community is helpful and welcoming. If none of the above suggestions solves your installation issue, consider asking for support from one of the following channels: +Flutter 社区乐于助人、氛围友好。 +若以上建议都无法解决你的安装问题, +可考虑通过以下渠道之一寻求帮助: + * [/r/flutterhelp](https://www.reddit.com/r/flutterhelp/) on Reddit + + Reddit 上的 [/r/flutterhelp](https://www.reddit.com/r/flutterhelp/) + * [/r/flutterdev](https://discord.gg/rflutterdev) on Discord, particularly the `install-and-setup` channel on this server. + + Discord 上的 [/r/flutterdev](https://discord.gg/rflutterdev), + 尤其是该服务器上的 `install-and-setup` 频道。 + * [StackOverflow][], in particular, questions tagged with [#flutter][] or [#dart][]. + [StackOverflow][], + 尤其是带有 [#flutter][] 或 [#dart][] 标签的问题。 + To be respectful of everyone's time, search the archive for a similar issue before posting a new one. +为尊重每个人的时间, +在发布新问题前请先搜索归档中是否有类似问题。 + [StackOverflow]: {{site.so}} [#dart]: {{site.so}}/questions/tagged/dart [#flutter]: {{site.so}}/questions/tagged/flutter diff --git a/sites/docs/src/content/install/uninstall.md b/sites/docs/src/content/install/uninstall.md index 8450d54fe7..638d771bdc 100644 --- a/sites/docs/src/content/install/uninstall.md +++ b/sites/docs/src/content/install/uninstall.md @@ -1,40 +1,69 @@ --- -title: Uninstall Flutter -shortTitle: Uninstall -description: >- - How to remove the Flutter SDK and clean up its configuration files. +# title: Uninstall Flutter +title: 卸载 Flutter +# shortTitle: Uninstall +shortTitle: 卸载 +# description: >- +# How to remove the Flutter SDK and clean up its configuration files. +description: 如何移除 Flutter SDK 并清理其配置文件。 showToc: false +ai-translated: true --- To remove the Flutter SDK from your development machine, delete the directories that store Flutter and its configuration files. +要从你的开发机上移除 Flutter SDK, +请删除存放 Flutter 及其配置文件的目录。 + ## Choose your development platform {: #dev-platform } +## 选择你的开发平台 + The instructions on this page are configured to cover uninstall Flutter on a **Windows**{:.selected-os-text} device. +本页说明默认针对在 **Windows**{:.selected-os-text} 设备上卸载 Flutter。 + If you'd like to follow the instructions for a different OS, please select one of the following. +若要查看其他操作系统的说明, +请选择以下选项之一。 + ## Uninstall the Flutter SDK {: #uninstall } +## 卸载 Flutter SDK + 1.

Determine your Flutter SDK installation location

+

确定 Flutter SDK 的安装位置

+ Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

Remove the installation directory

+

删除安装目录

+ To uninstall the Flutter SDK, delete the `flutter` directory you installed Flutter to. + 要卸载 Flutter SDK, + 请删除你安装 Flutter 时使用的 `flutter` 目录。 + For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, run the following command to delete the SDK: + 例如,若你将 Flutter 下载到用户目录下的 + `develop\flutter` 文件夹中, + 可运行以下命令删除 SDK: + ```ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:USERPROFILE "develop\flutter") ``` @@ -43,18 +72,31 @@ please select one of the following. 1.

Determine your Flutter SDK installation location

+

确定 Flutter SDK 的安装位置

+ Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. + 复制你下载并解压 Flutter SDK 所在目录的绝对路径。 + 1.

Remove the installation directory

+

删除安装目录

+ To uninstall the Flutter SDK, delete the `flutter` directory you installed Flutter to. + 要卸载 Flutter SDK, + 请删除你安装 Flutter 时使用的 `flutter` 目录。 + For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, run the following command to delete the SDK: + 例如,若你将 Flutter 下载到用户目录下的 + `develop/flutter` 文件夹中, + 可运行以下命令删除 SDK: + ```console $ rm -rf ~/develop/flutter ``` @@ -63,21 +105,34 @@ please select one of the following. ## Clean up installation and configuration files {: #cleanup } +## 清理安装与配置文件 + Flutter and Dart add to additional directories in your home directory. These contain configuration files and package downloads. The following cleanup is optional. +Flutter 和 Dart 还会在你的主目录下使用其他目录。 +这些目录存放配置文件和 package 下载内容。 +以下清理步骤为可选操作。 + 1.

Remove Flutter configuration directories

+

删除 Flutter 配置目录

+ If you don't want to preserve your Flutter tooling configuration, remove the following directories from your device. + 若你不需要保留 Flutter 工具链配置, + 请从设备上删除以下目录。 +
- `%APPDATA%\.flutter-devtools` To remove these directories, run the following command: + 要删除这些目录,请运行以下命令: + ```ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".flutter-devtools") ``` @@ -92,6 +147,8 @@ The following cleanup is optional. To remove these directories, run the following command: + 要删除这些目录,请运行以下命令: + ```ps $ rm -rf ~/.flutter ~/.flutter-devtools ~/.flutter_settings ``` @@ -100,9 +157,14 @@ The following cleanup is optional. 1.

Remove Dart configuration directories

+

删除 Dart 配置目录

+ If you don't want to preserve your Dart tooling configuration, remove the following directories from your device. + 若你不需要保留 Dart 工具链配置, + 请从设备上删除以下目录。 +
- `%APPDATA%\.dart` @@ -111,6 +173,8 @@ The following cleanup is optional. To remove these directories, run the following command: + 要删除这些目录,请运行以下命令: + ```console $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".dart"), (Join-Path $env:APPDATA ".dart-tool"), (Join-Path $env:LOCALAPPDATA ".dartServer") ``` @@ -125,6 +189,8 @@ The following cleanup is optional. To remove these directories, run the following command: + 要删除这些目录,请运行以下命令: + ```console $ rm -rf ~/.dart ~/.dart-tool ~/.dartServer ``` @@ -133,15 +199,23 @@ The following cleanup is optional. 1.

Remove pub package directories

+

删除 pub package 目录

+ If you don't want to preserve your locally installed pub packages, remove the [pub system cache][] directory from your device. + 若你不需要保留本地已安装的 pub packages, + 请从设备上删除 [pub 系统缓存][pub system cache] 目录。 +
If you didn't change the location of the pub system cache, run the following command to delete the `%LOCALAPPDATA%\Pub\Cache` directory: + 若你未更改 pub 系统缓存的位置, + 可运行以下命令删除 `%LOCALAPPDATA%\Pub\Cache` 目录: + ```ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:LOCALAPPDATA "Pub\Cache") ``` @@ -153,6 +227,9 @@ The following cleanup is optional. If you didn't change the location of the pub system cache, run the following command to delete the `~/.pub-cache` directory: + 若你未更改 pub 系统缓存的位置, + 可运行以下命令删除 `~/.pub-cache` 目录: + ```console $ rm -rf ~/.pub-cache ``` @@ -165,10 +242,17 @@ The following cleanup is optional. ## Reinstall Flutter {: #reinstall } +## 重新安装 Flutter + You can [reinstall Flutter][flutter-install] or [just Dart][dart-install] at any time. If you removed any configuration directories, reinstalling Flutter restores them to default settings. +你可以随时 [重新安装 Flutter][flutter-install] 或 +[仅安装 Dart][dart-install]。 +若你删除了任何配置目录, +重新安装 Flutter 会将它们恢复为默认设置。 + [flutter-install]: /install [dart-install]: {{site.dart-site}}/get-dart diff --git a/sites/docs/src/content/install/upgrade.md b/sites/docs/src/content/install/upgrade.md index 139fbb5ef2..45d51f715a 100644 --- a/sites/docs/src/content/install/upgrade.md +++ b/sites/docs/src/content/install/upgrade.md @@ -280,11 +280,18 @@ $ flutter pub outdated ## Troubleshooting +## 问题排查 + ### Windows: "Filename too long" error +### Windows:「Filename too long」错误 + When running `flutter upgrade` on Windows, you might encounter an error like the following: +在 Windows 上运行 `flutter upgrade` 时, +你可能会遇到类似以下的错误: + ```text error: unable to create file ...: Filename too long ``` @@ -292,15 +299,25 @@ error: unable to create file ...: Filename too long This occurs because the path to a file in the Flutter SDK exceeds the default maximum path length limit on Windows. +这是因为 Flutter SDK 中某个文件的路径超出了 Windows 默认的最大路径长度限制。 + To resolve this issue, consider installing the Flutter SDK at a shorter path. For example, install Flutter at `C:\Flutter` instead of something longer like `C:\Users\\Documents\flutter`. +要解决此问题,可考虑将 Flutter SDK 安装到更短的路径。 +例如,将 Flutter 安装到 `C:\Flutter`, +而不是 `C:\Users\\Documents\flutter` 这样更长的路径。 + Otherwise, do the following: +否则,请执行以下操作: + 1. Enable long paths support in Git: + 在 Git 中启用长路径支持: + ```console $ git config --system core.longpaths true ``` @@ -308,7 +325,12 @@ Otherwise, do the following: If the command fails with a permission error, try running your terminal as an administrator. + 若该命令因权限错误而失败, + 请尝试以管理员身份运行终端。 + 1. Enable long paths in Windows: + + 在 Windows 中启用长路径: ```console New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force @@ -316,6 +338,8 @@ Otherwise, do the following: This command requires administrator privileges. + 此命令需要管理员权限。 + [Flutter SDK archive]: /install/archive [flutter-announce]: {{site.groups}}/forum/#!forum/flutter-announce [pubspec.yaml]: {{site.dart-site}}/tools/pub/pubspec diff --git a/sites/docs/src/content/install/with-vs-code.md b/sites/docs/src/content/install/with-vs-code.md index 4a7d855d9f..acead39aa4 100644 --- a/sites/docs/src/content/install/with-vs-code.md +++ b/sites/docs/src/content/install/with-vs-code.md @@ -1,15 +1,24 @@ --- -title: Install Flutter using VS Code -shortTitle: Install with VS Code -breadcrumb: With VS Code -description: >- - Learn how to use VS Code to quickly install and set up the Flutter SDK. +# title: Install Flutter using VS Code +title: 使用 VS Code 安装 Flutter +# shortTitle: Install with VS Code +shortTitle: 使用 VS Code 安装 +# breadcrumb: With VS Code +breadcrumb: 使用 VS Code +# description: >- +# Learn how to use VS Code to quickly install and set up the Flutter SDK. +description: 了解如何使用 VS Code 快速安装并配置 Flutter SDK。 +ai-translated: true --- Learn how to install and set up Flutter in a Code OSS-based editor. This includes (but is not limited to), [VS Code][], [Antigravity][], [Cursor][], and [Windsurf][]. +了解如何在基于 Code OSS 的编辑器中安装并配置 Flutter。 +包括但不限于 +[VS Code][]、[Antigravity][]、[Cursor][] 和 [Windsurf][]。 + [VS Code]: https://code.visualstudio.com [Antigravity]: https://antigravity.google/ [Cursor]: https://cursor.com/ @@ -18,39 +27,67 @@ This includes (but is not limited to), :::tip If you've never set up or developed an app with Flutter before, follow [Set up and test drive Flutter][] instead. + +如果你从未使用 Flutter 配置或开发过应用, +请先按照 [设置并试跑 Flutter][Set up and test drive Flutter] 进行操作。 ::: [Set up and test drive Flutter]: /install/quick ## Choose your development platform {: #dev-platform} +## 选择你的开发平台 + The instructions on this page are configured to cover installing Flutter on a **Windows**{:.selected-os-text} device. +本页说明默认针对在 **Windows**{:.selected-os-text} 设备上安装 Flutter。 + If you'd like to follow the instructions for a different OS, please select one of the following. +若要查看其他操作系统的说明, +请选择以下选项之一。 + ## Download prerequisite software {: #download-prerequisites} +## 下载必备软件 + For the smoothest Flutter setup, first install the following tools. +为了最顺畅地完成 Flutter 配置, +请先安装以下工具。 + 1.

Set up Linux support

+

配置 Linux 支持

+ If you haven't set up Linux support on your Chromebook before, [Turn on Linux support][chromeos-linux]. + 若你此前未在 Chromebook 上配置 Linux 支持, + 请参阅 [开启 Linux 支持][chromeos-linux]。 + If you've already turned on Linux support, ensure it's up to date following the [Fix problems with Linux][chromeos-linux-update] instructions. + 若你已开启 Linux 支持, + 请按照 [修复 Linux 相关问题][chromeos-linux-update] 的说明确保其为最新状态。 + 1.

Download and install prerequisite packages

+

下载并安装必备 package

+ Using `apt-get` or your preferred installation mechanism, install the latest versions of the following packages: + 使用 `apt-get` 或你偏好的安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -61,6 +98,9 @@ first install the following tools. If you want to use `apt-get`, install these packages using the following commands: + 若要使用 `apt-get`, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -68,18 +108,29 @@ first install the following tools. 1.

Download and install Visual Studio Code

+

下载并安装 Visual Studio Code

+ To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + {: .steps .chromeos-only} 1.

Install the Xcode command-line tools

+

安装 Xcode 命令行工具

+ Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git. + 下载 Xcode 命令行工具以获取 Flutter 依赖的命令行工具(包括 Git)。 + To download the tools, run the following command in your preferred terminal: + 要下载这些工具,请在你偏好的终端中运行以下命令: + ```console $ xcode-select --install ``` @@ -88,32 +139,58 @@ first install the following tools. a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**. + 若你尚未安装这些工具, + 应会弹出对话框确认你是否要安装。 + 点击 **Install**,安装完成后点击 **Done**。 + 1.

Download and install Visual Studio Code

+

下载并安装 Visual Studio Code

+ To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + {: .steps .macos-only} 1.

Install Git for Windows

+

为 Windows 安装 Git

+ Download and install the latest version of [Git for Windows][]. + 下载并安装最新版本的 [Git for Windows][]。 + For help installing or troubleshooting Git, reference the [Git documentation][git-install]. + 有关安装或排查 Git 问题的帮助, + 请参阅 [Git 文档][git-install]。 + 1.

Download and install Visual Studio Code

+

下载并安装 Visual Studio Code

+ To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + {: .steps .windows-only} 1.

Download and install prerequisite packages

+

下载并安装必备 package

+ Using your preferred package manager or mechanism, install the latest versions of the following packages: + 使用你偏好的 package 管理器或安装方式, + 安装以下 package 的最新版本: + - `curl` - `git` - `unzip` @@ -124,6 +201,9 @@ first install the following tools. On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: + 在基于 Debian 且使用 `apt-get` 的发行版(例如 Ubuntu)上, + 请使用以下命令安装这些 package: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa @@ -131,9 +211,14 @@ first install the following tools. 1.

Download and install Visual Studio Code

+

下载并安装 Visual Studio Code

+ To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. + 要快速安装 Flutter 并编辑、调试你的应用, + 请 [安装并配置 Visual Studio Code][vscode-install]。 + {: .steps .linux-only} [chromeos-linux]: https://support.google.com/chromebook/answer/9145439 @@ -144,43 +229,81 @@ first install the following tools. ## Install and set up Flutter {: #install-flutter} +## 安装并配置 Flutter + Now that you've installed Git and VS Code, follow these steps to use VS Code to install and set up Flutter. +现在你已经安装了 Git 和 VS Code, +请按照以下步骤使用 VS Code 安装并配置 Flutter。 + 1.

Launch VS Code

+

启动 VS Code

+ If not already open, open VS Code by searching for it with Spotlight or opening it manually from the directory where it's installed. + 若尚未打开,可通过 Spotlight 搜索或从安装目录手动打开 VS Code。 + 1.

Add the Flutter extension to VS Code

+

向 VS Code 添加 Flutter 扩展

+ To add the Dart and Flutter extensions to VS Code, visit the [Flutter extension's marketplace page][flutter-vscode], and click **Install**. If prompted by your browser, allow it to open VS Code. + 要向 VS Code 添加 Dart 和 Flutter 扩展, + 请访问 [Flutter 扩展的市场页面][flutter-vscode], + 并点击 **Install**。 + 若浏览器提示,请允许其打开 VS Code。 + 1.

Install Flutter with VS Code

+

使用 VS Code 安装 Flutter

+ 1. Open the command palette in VS Code. + 在 VS Code 中打开命令面板。 + Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. + 依次选择 **View** > **Command Palette**, + 或按 Cmd/Ctrl + + Shift + P。 + 1. In the command palette, type `flutter`. + 在命令面板中输入 `flutter`。 + 1. Select **Flutter: New Project**. + 选择 **Flutter: New Project**。 + 1. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**. + VS Code 会提示你在计算机上定位 Flutter SDK。 + 选择 **Download SDK**。 + 1. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter. + 当 **Select Folder for Flutter SDK** 对话框显示时, + 选择你希望安装 Flutter 的位置。 + 1. Click **Clone Flutter**. + 点击 **Clone Flutter**。 + While downloading Flutter, VS Code displays this pop-up notification: + 下载 Flutter 时,VS Code 会显示以下弹出通知: + ```console Downloading the Flutter SDK. This may take a few minutes. ``` @@ -189,33 +312,60 @@ follow these steps to use VS Code to install and set up Flutter. If you suspect that the download has hung, click **Cancel** then start the installation again. + 此下载可能需要几分钟。 + 若你怀疑下载已卡住,请点击 **Cancel**, + 然后重新开始安装。 + 1. Click **Add SDK to PATH**. + 点击 **Add SDK to PATH**。 + When successful, a notification displays: + 成功时,会显示以下通知: + ```console The Flutter SDK was added to your PATH ``` 1. VS Code might display a Google Analytics notice. + VS Code 可能会显示 Google Analytics 通知。 + If you agree, click **OK**. + 若你同意,请点击 **OK**。 + 1. To ensure that Flutter is available in all terminals: + 要确保 Flutter 在所有终端中可用: + 1. Close, then reopen all terminal windows. + + 关闭并重新打开所有终端窗口。 + 1. Restart VS Code. + 重启 VS Code。 + {:type="a"} 1.

Validate your setup

+

验证你的配置

+ To ensure you installed Flutter correctly, run `flutter doctor -v` in your preferred terminal. + 要确保你正确安装了 Flutter, + 请在你偏好的终端中运行 `flutter doctor -v`。 + If the command isn't found or there's an error, check out [Flutter installation troubleshooting][troubleshoot]. + 若找不到该命令或出现错误, + 请参阅 [Flutter 安装问题排查][troubleshoot]。 + {:.steps} [Install Flutter manually]: /install/manual @@ -224,15 +374,25 @@ follow these steps to use VS Code to install and set up Flutter. ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 + Now that you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. +现在你已经成功安装了 Flutter, +请至少为一个目标平台配置开发环境, +以继续你的 Flutter 学习之旅。 + :::recommend If you don't yet have a preferred platform to target during development, the Flutter team recommends you first try out [developing for the web][web-setup]! + +若你尚未确定开发时要面向的平台, +Flutter 团队建议你首先尝试 +[面向 Web 开发][web-setup]! ::: [web-setup]: /platform-integration/web/setup @@ -243,27 +403,27 @@ the Flutter team recommends you first try out
- Set up a target platform + Set up a target platform配置目标平台
@@ -274,15 +434,15 @@ the Flutter team recommends you first try out
- Learn Flutter development + Learn Flutter development学习 Flutter 开发
@@ -293,18 +453,18 @@ the Flutter team recommends you first try out
- Stay up to date with Flutter + Stay up to date with Flutter跟进 Flutter 最新动态
diff --git a/sites/docs/src/content/learn/index.md b/sites/docs/src/content/learn/index.md index b867c0b251..4f5e128687 100644 --- a/sites/docs/src/content/learn/index.md +++ b/sites/docs/src/content/learn/index.md @@ -1,8 +1,12 @@ --- -title: Learn Flutter -breadcrumb: Learn -description: Find everything you need to start building Flutter apps. +# title: Learn Flutter +title: 学习 Flutter +# breadcrumb: Learn +breadcrumb: 学习 +# description: Find everything you need to start building Flutter apps. +description: 找到你开始构建 Flutter 应用所需的一切。 showToc: false +ai-translated: true --- +从这里开始,了解如何完成环境配置、构建 Flutter 应用、编写 Dart 代码,以及理解 Flutter 的工作原理。 + + +

This is the **recommended** set of resources to start learning Flutter! Learn how to write Dart code in the first tutorial, then build a series of apps using Flutter in the second. You'll learn the essential best practices for Flutter app development, app architecture, fetching data from the network, and more. Great for beginners with minimal programming experience.

+

+ + 这是开始学习 Flutter 的**推荐**资源集!在第一个教程中学习如何编写 Dart 代码,然后在第二个教程中使用 Flutter 构建一系列应用。你将学习 Flutter 应用开发、应用架构、从网络获取数据等方面的核心最佳实践。非常适合编程经验较少的初学者。 +

Start learning + 开始学习
## For experienced developers +## 面向有经验的开发者 + Dive deeper into more specific topics in Flutter development with sample code, YouTube videos, codelabs, and more. - +通过示例代码、YouTube 视频、codelab 等资源,深入探索 Flutter 开发中更具体的主题。 + + +

Find sample code, YouTube videos, codelabs, and more written by the experts on the Flutter team at Google. This index contains the resources you need to learn any aspect of Flutter.

+

+ 查找由 Google Flutter 团队专家撰写的示例代码、YouTube 视频、codelab 等资源。本索引包含你学习 Flutter 任意方面所需的资源。 +

Explore all resources + 浏览全部资源
- + +

Learn the recommended app architecture to build a Flutter app. This guide contains everything you need to know to understand and implement MVVM architecture in a Flutter app.

+

+ 学习构建 Flutter 应用的推荐应用架构。本指南包含理解并在 Flutter 应用中实现 MVVM 架构所需了解的全部内容。 +

Start learning + 开始学习
- diff --git a/sites/docs/src/content/learn/pathway/how-flutter-works.md b/sites/docs/src/content/learn/pathway/how-flutter-works.md index 5c0ac48ee3..6e32509368 100644 --- a/sites/docs/src/content/learn/pathway/how-flutter-works.md +++ b/sites/docs/src/content/learn/pathway/how-flutter-works.md @@ -1,14 +1,20 @@ --- -title: How Flutter works +# title: How Flutter works +title: Flutter 工作原理 +# description: >- +# Dive deeper into how Flutter works through a six-part video series. description: >- - Dive deeper into how Flutter works through a six-part video series. + 通过六集视频系列深入了解 Flutter 的工作原理。 layout: tutorial +ai-translated: true --- ## Flutter's architecture {:#architecture} +## Flutter 架构 {:#architecture} +
@@ -23,8 +29,19 @@ the role of Dart. This video is perfect for anyone beginning to research Flutter who wants to understand the big picture. +欢迎观看「Flutter 工作原理」第一集, +本系列共六集,旨在探索你将 +Dart 代码交给 Flutter 框架之后会发生什么。 +本集从宏观角度介绍 Flutter 架构, +涵盖声明式代码、多平台框架以及 +Dart 的作用。 +本视频非常适合刚开始 +了解 Flutter、希望把握整体图景的任何人。 + ## Widgets and the three trees {:#three-trees} +## widget 与三棵树 {:#three-trees} +
@@ -36,8 +53,16 @@ see how elements glue widgets to the rendering layer. You'll also learn about the role of render objects in translating widget values into painting calls. +通过探索 Flutter 的三棵主要树——`Widget`、`Element` 和 `RenderObject`——深入了解其架构。 +了解 widget 如何为 Flutter 开发者提供声明式 API, +以及 element 如何将 widget 与渲染层连接起来。 +你还将了解 render object 在 +将 widget 值转换为绘制调用方面的作用。 + ## State objects and their lifecycle {:#state} +## State 对象及其生命周期 {:#state} +
@@ -52,6 +77,16 @@ By the end of the episode, you'll understand how `State` objects track, respond to, and manage changes in your Flutter apps—and how the `State` lifecycle enables efficient UI updates. +「Flutter 工作原理」第三集深入讲解 `State` 类, +它是每个 `StatefulWidget` 背后的关键组成部分。 +跟随 `State` 对象的完整生命周期——从用于初始化资源的 `initState`, +到用于清理资源的 `dispose`。 +途中还将探索重要方法,包括 `didChangeDependencies`、 +`didUpdateWidget` 以及至关重要的 `build` 方法。 +本集结束时,你将理解 `State` 对象如何 +跟踪、响应并管理 Flutter 应用中的变化——以及 +`State` 生命周期如何实现高效的 UI 更新。 + This episode also peels back the curtain on how Flutter recurses down the widget tree after a `setState` call, building out only the parts of your app that need to change. @@ -62,8 +97,20 @@ If you're curious how Flutter keeps apps fast and responsive, or you just want to really understand what happens behind the scenes, this episode is packed with the essential foundations. +本集还会揭示 Flutter 在调用 `setState` 后 +如何沿 widget 树向下递归, +仅构建应用中需要变更的部分。 +你将了解为何 `const` 构造函数对性能很重要、 +为何 `setState` 闭包必须同步,以及 +element(而非 widget 本身)如何管理实际的重建过程。 +如果你好奇 Flutter 如何保持应用快速且响应灵敏,或者 +你只想真正理解幕后发生了什么, +本集汇集了这些必备基础。 + ## The widgets that actually render {:#render-object-widgets} +## 真正负责渲染的 widget {:#render-object-widgets} +
@@ -74,14 +121,28 @@ widget in Flutter that creates something visual. While stateless and stateful widgets help structure your app, it's render object widgets that turn your UI code into real pixels. +你是否想过 Flutter 应用究竟是如何渲染到屏幕上的? +本视频深入讲解 `RenderObjectWidget`——Flutter 中唯一会 +创建可视内容的 widget 类型。 +Stateless widget 和 Stateful widget 帮助组织应用结构, +而 render object widget 才将你的 UI 代码变成真正的像素。 + You'll learn how Flutter builds the `Widget`, `Element`, and `RenderObject` trees, why many common widgets don't directly render anything, and how Flutter uses `RenderObjectWidget` to create and update render objects that power your UI. +你将了解 Flutter 如何构建 +`Widget`、`Element` 和 `RenderObject` 三棵树, +为何许多常见 widget 并不直接渲染任何内容,以及 +Flutter 如何使用 `RenderObjectWidget` 创建并 +更新驱动 UI 的 render object。 + ## A day in the life of a render object {:#render-objects} +## Render object 的一天 {:#render-objects} +
@@ -97,8 +158,21 @@ He also breaks down key methods like `layout`, `paint`, and `describeSemanticsConfiguration`, showing how they fit together to keep your UI responsive and accurate. +在「Flutter 工作原理」第五集中,Craig 带你了解 +`RenderObject` 完整一天的工作流程。 +在第四集概念的基础上, +本视频讲解 render object 的核心职责: +layout、绘制、命中测试和无障碍。Craig 阐明了 +约束如何沿渲染树向下传递、尺寸如何向上回传,以及 +父级 render object 如何设置子级的位置。 +他还拆解了 +`layout`、`paint` 和 `describeSemanticsConfiguration` 等关键方法, +说明它们如何协同工作,使你的 UI 保持响应灵敏且准确。 + ## The Flutter engine and embedders {:#engine} +## Flutter 引擎与 embedder {:#engine} +
@@ -113,6 +187,16 @@ Craig also highlights the structure of a newly generated Flutter project, dives into how threads are managed in a Flutter app, and explains the roles of platform channels and the [Pigeon][] package. +在「Flutter 工作原理」第六集中,Craig 带我们 +深入 Dart 代码之下,探索 Flutter 引擎与 embedder。 +本集说明 Flutter 移动应用如何依赖 +原生 Android 与 iOS 代码来启动和运行, +Flutter 引擎如何将你的 Dart 代码连接到宿主平台,以及 +embedder 如何促进两者之间的通信。 +Craig 还介绍了新生成 Flutter 项目的结构, +深入讲解 Flutter 应用中的线程管理方式,并 +说明 platform channel 与 [Pigeon][] package 的作用。 + You'll also learn why the Flutter engine is written in C++ rather than Dart, how it evolved from a fork of Chrome, and @@ -123,6 +207,16 @@ which aim to simplify native interop even further. If you want a clear mental model of how Flutter apps work under the hood, this is the perfect way to connect all the layers together. +你还将了解 Flutter 引擎为何用 +C++ 而非 Dart 编写, +它如何从 Chrome 分支演化而来,以及 +如何使用 Skia 或 Impeller 渲染每一帧。 +本集最后展望 +Flutter 未来的架构改进, +这些改进旨在进一步简化原生互操作。 +如果你想建立清晰的思维模型来理解 Flutter 应用在底层的运作方式, +这是将所有层次串联起来的绝佳方式。 + [Pigeon]: {{site.pub-pkg}}/pigeon diff --git a/sites/docs/src/content/learn/pathway/index.md b/sites/docs/src/content/learn/pathway/index.md index 7469d90789..1034200d40 100644 --- a/sites/docs/src/content/learn/pathway/index.md +++ b/sites/docs/src/content/learn/pathway/index.md @@ -1,9 +1,14 @@ --- -title: Flutter learning pathway -shortTitle: Learning pathway +# title: Flutter learning pathway +title: Flutter 学习路径 +# shortTitle: Learning pathway +shortTitle: 学习路径 +# description: >- +# This learning pathway walks you through the basics of both Dart and Flutter. description: >- - This learning pathway walks you through the basics of both Dart and Flutter. + 本学习路径将带你了解 Dart 与 Flutter 的基础知识。 layout: tutorial +ai-translated: true ---
@@ -18,64 +23,121 @@ This learning pathway spans the Dart and Flutter websites, YouTube, and your IDE. By the end, you'll have a solid foundation in both Dart and Flutter. +欢迎学习 Dart 与 Flutter 入门路径。 +在这条路径中,你将配置开发环境、 +学习如何编写 Dart 代码,并 +分步构建三个小型 Flutter 应用。 +本学习路径涵盖 +Dart 与 Flutter 官网、YouTube 以及你的 IDE。 +学完之后,你将对 Dart 和 Flutter 打下扎实基础。 + ### Set up your development environment +### 配置开发环境 + Before you can start building Flutter apps, you'll need to set up your development environment. Follow the installation guide to get Flutter and all required dependencies installed on your machine. +在开始构建 Flutter 应用之前, +你需要先配置开发环境。 +请按照安装指南,在你的机器上安装 Flutter 以及 +所有必需的依赖项。 + [Get Started →](/learn/pathway/quick-install) +[开始 →](/learn/pathway/quick-install) + ### Complete the Dart Getting Started tutorial +### 完成 Dart 入门教程 + Flutter uses the Dart programming language. If you're new to Dart, complete this interactive tutorial to learn the fundamentals—variables, functions, classes, and more. +Flutter 使用 Dart 编程语言。 +如果你是 Dart 新手,请完成此交互式教程, +学习基础知识——变量、函数、类等。 + If you're in a hurry and already familiar with modern, object-oriented programming languages, you can safely skip this step. +如果你时间紧迫,且已熟悉 +现代面向对象编程语言, +可以安全跳过此步骤。 + Dart Getting Started tutorial → + +Dart 入门教程 → + + ### Complete the Flutter Getting Started tutorial +### 完成 Flutter 入门教程 + Now that you have Dart skills under your belt, dive into Flutter! This hands-on tutorial walks you through building three small Flutter apps step-by-step. +现在你已经掌握了 Dart 技能,可以深入学习 Flutter 了! +本动手教程将带你 +分步构建三个小型 Flutter 应用。 + [Flutter Getting Started tutorial →](/learn/pathway/tutorial) +[Flutter 入门教程 →](/learn/pathway/tutorial) + ### Understand how Flutter works +### 理解 Flutter 的工作原理 + Deepen your understanding of how Flutter works under the hood by watching this video series. Learn about the widget tree, rendering pipeline, and what makes Flutter unique. This series provides the knowledge you need to know to take full advantage of the framework. +通过观看本视频系列,深入了解 Flutter +底层的工作原理。 +了解 widget 树、渲染管线,以及 Flutter 的独特之处。 +本系列提供你充分运用该框架所需的 +必备知识。 [Watch: How Flutter Works →](/learn/pathway/how-flutter-works) +[观看:Flutter 工作原理 →](/learn/pathway/how-flutter-works) + Dash with question marks around her; looking curious. - + + We're always looking for ways to improve the Getting Started experience. Moving forward, we plan to add more resources to this section of the website. We'd love to know what you think and what you'd like to see. +我们一直在寻找改进入门体验的方法。 +接下来,我们计划在本网站这一部分添加更多资源。 +我们很想了解你的想法以及你希望看到的内容。 + Please take a moment to share your feedback with us. +请花一点时间与我们分享你的反馈。 + Leave feedback +提交反馈 + \ No newline at end of file diff --git a/sites/docs/src/content/learn/pathway/quick-install.md b/sites/docs/src/content/learn/pathway/quick-install.md index 91f7870764..9a2cb8e9a4 100644 --- a/sites/docs/src/content/learn/pathway/quick-install.md +++ b/sites/docs/src/content/learn/pathway/quick-install.md @@ -1,8 +1,12 @@ --- -title: Quick install -breadcrumb: Install -description: This pages walks you through the quick install process for Flutter. +# title: Quick install +title: 快速安装 +# breadcrumb: Install +breadcrumb: 安装 +# description: This page walks you through the quick install process for Flutter. +description: 本页将引导你完成 Flutter 的快速安装流程。 layout: tutorial +ai-translated: true --- @@ -10,15 +14,28 @@ layout: tutorial ## Start learning! +## 开始学习! + Installing the Flutter SDK automatically installs Dart as well. You can now start building Flutter apps! - +安装 Flutter SDK 时会自动安装 Dart。现在你可以开始构建 Flutter 应用了! + + +

If you're new to programming, start with the Dart Getting Started tutorial to learn the fundamentals of the Dart programming language. This tutorial was designed to lead into the Flutter Getting Started tutorial. +

+

+ +如果你是编程新手,请从 Dart 入门教程开始学习 Dart 编程语言的基础知识。本教程旨在衔接 Flutter 入门教程。 +

Start learning + 开始学习
If you're already familiar with programming, you can skip the Dart tutorial and jump straight into the first Flutter lesson. + +如果你已熟悉编程,可以跳过 Dart 教程,直接进入第一个 Flutter 课程。 \ No newline at end of file diff --git a/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md b/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md index cbe0b31def..51ae7a903f 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md +++ b/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md @@ -1,21 +1,26 @@ --- -title: LayoutBuilder and adaptive layouts -description: Learn how to use the LayoutBuilder widget. +# title: LayoutBuilder and adaptive layouts +title: LayoutBuilder 与自适应布局 +# description: Learn how to use the LayoutBuilder widget. +description: 学习如何使用 LayoutBuilder widget。 layout: tutorial +ai-translated: true --- Learn how to create layouts that adapt to different screen widths. +学习如何创建能够适应不同屏幕宽度的布局。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Create responsive layouts with LayoutBuilder + - title: 使用 LayoutBuilder 创建响应式布局 icon: fit_screen - - title: Detect screen size to choose different layouts + - title: 检测屏幕尺寸以选择不同布局 icon: devices - - title: Build a sidebar and detail layout for large screens + - title: 为大屏幕构建侧边栏与详情布局 icon: view_sidebar @@ -23,6 +28,8 @@ items: ### Introduction +### 简介 + Modern apps need to work well on screens of all sizes. On this page, you'll learn how to create layouts that adapt to different screen widths. @@ -30,18 +37,36 @@ This app shows a sidebar on large screens and a navigation-based UI on small screens. Specifically, this app handles two screen sizes: +现代应用需要在各种尺寸的屏幕上都能良好运行。 +在本页中,你将学习如何创建能够 +适应不同屏幕宽度的布局。 +此应用在大屏幕上显示侧边栏, +在小屏幕上使用基于导航的 UI。 +具体而言,此应用处理两种屏幕尺寸: + - **Large screens (tablets, desktop)**: Shows contact groups and contact details side-by-side. - **Small screens (phones)**: Uses navigation to move between contact groups and details. + + **大屏幕(平板、桌面)**: + 并排显示联系人分组和联系人详情。 + 使用导航在联系人分组和详情之间切换。 + ### Create the contact groups page +### 创建联系人分组页面 + First, create the basic structure of the `ContactGroupsPage` widget for your contact groups screen. Create `lib/screens/contact_groups.dart` and add the following basic structure: +首先,为联系人分组屏幕创建 `ContactGroupsPage` widget 的基本结构。 +创建 `lib/screens/contact_groups.dart` 并添加 +以下基本结构: + ```dart import 'package:flutter/cupertino.dart'; @@ -61,9 +86,14 @@ class ContactGroupsPage extends StatelessWidget { ### Create the contacts page +### 创建联系人页面 + Similarly, create `lib/screens/contacts.dart` to eventually display individual contacts: +同样,创建 `lib/screens/contacts.dart` 以最终 +显示各个联系人: + ```dart import 'package:flutter/cupertino.dart'; @@ -87,11 +117,20 @@ The `ContaactsListPage` widget and `ContactGroupsPage` widget are placeholder pages that are needed to implement the adaptive layout widget, which you'll do next. +`ContaactsListPage` widget 和 `ContactGroupsPage` widget 是 +实现自适应布局 widget 所需的占位页面, +你将在下一步完成该布局。 + ### Build the adaptive layout foundation +### 构建自适应布局基础 + Create `lib/screens/adaptive_layout.dart` and start with the following basic structure: +创建 `lib/screens/adaptive_layout.dart`, +并以以下基本结构开始: + ```dart import 'package:flutter/cupertino.dart'; @@ -116,8 +155,13 @@ class _AdaptiveLayoutState extends State { This is a `StatefulWidget` because the adaptive layout eventually manages which contact group is currently selected. +这是一个 `StatefulWidget`,因为自适应布局最终需要 +管理当前选中的联系人分组。 + Next, add the screen size detection logic to `lib/screens/adaptive_layout.dart`: +接下来,在 `lib/screens/adaptive_layout.dart` 中添加屏幕尺寸检测逻辑: + ```dart import 'package:flutter/cupertino.dart'; @@ -156,16 +200,31 @@ the parent's size constraints. In the `builder` callback, you receive a`BoxConstraints` object that tells you the maximum available width and height. +`LayoutBuilder` widget 提供有关 +父级尺寸约束的信息。 +在 `builder` 回调中,你会收到一个 `BoxConstraints` 对象, +它会告知你最大可用宽度和高度。 + By checking if `constraints.maxWidth > largeScreenMinWidth`, you can decide which layout to show. The 600-pixel threshold is a common breakpoint that separates phone-sized screens from tablet-sized screens. +通过检查 `constraints.maxWidth > largeScreenMinWidth`, +你可以决定显示哪种布局。 +600 像素的阈值是常用的断点, +用于区分手机尺寸屏幕与平板尺寸屏幕。 + ### Update the main app +### 更新主应用 + Update `main.dart` to use the adaptive layout, so you can see your changes: +更新 `main.dart` 以使用自适应布局, +这样你就能看到更改效果: + ```dart import 'package:flutter/cupertino.dart'; @@ -201,11 +260,19 @@ class RolodexApp extends StatelessWidget { If you're running in Chrome, you can resize the browser window to see layout changes. +如果你在 Chrome 中运行,可以调整浏览器窗口大小以 +查看布局变化。 + ### Add list selection functionality +### 添加列表选择功能 + The large screen layout needs to track which contact group is selected. Update the state object in `lib/screens/adaptive_layout.dart` with the following code: +大屏幕布局需要跟踪选中的联系人分组。 +在 `lib/screens/adaptive_layout.dart` 中使用以下代码更新状态对象: + ```dart @@ -251,12 +318,21 @@ class _AdaptiveLayoutState extends State { The `selectedListId` variable tracks the currently selected contact group, and `_onContactListSelected` updates this value when the user makes a choice. +`selectedListId` 变量跟踪当前选中的联系人分组, +`_onContactListSelected` 在用户做出选择时更新此值。 + ### Build the large screen layout +### 构建大屏幕布局 + Now, implement the side-by-side layout for large screens in `lib/screens/adaptive_layout.dart`. First, replace the temporary text with a widget that contains the proper layout. +现在,在 `lib/screens/adaptive_layout.dart` 中实现大屏幕的并排布局。 +首先,将临时文本替换为包含 +正确布局的 widget。 + ```dart @@ -312,9 +388,17 @@ place the sidebar and details side-by-side. `SafeArea` ensures that the content doesn't overlap with system UI elements like the status bar. +大屏幕布局使用 `Row` 将 +侧边栏和详情并排放置。 +`SafeArea` 确保内容不会与 +状态栏等系统 UI 元素重叠。 + Now, set the sizes of the two panels and add a visual divider in `lib/screens/adaptive_layout.dart`: +现在,在 `lib/screens/adaptive_layout.dart` 中 +设置两个面板的尺寸并添加视觉分隔线: + ```dart Widget _buildLargeScreenLayout() { @@ -335,83 +419,109 @@ Widget _buildLargeScreenLayout() { This layout creates the following: +此布局创建以下内容: + - A fixed-width sidebar (320 pixels) for contact groups. - A 1-pixel divider between the panels. - A details panel that uses an `Expanded` widget to take the remaining space. + 用于联系人分组的固定宽度侧边栏(320 像素)。 +- 面板之间的 1 像素分隔线。 +- 使用 `Expanded` widget 占据剩余空间的详情面板。 + ### Test the adaptive layout +### 测试自适应布局 + Hot reload your app and test the responsive behavior. If you're running in Chrome, you can resize the browser window to see the layout change: +热重载应用并测试响应式行为。 +如果你在 Chrome 中运行,可以调整浏览器窗口大小以 +查看布局变化: + - **Wide window (> 600px)**: Shows placeholder text for the sidebar and details side-by-side. - **Narrow window (< 600px)**: Shows only the contact groups page. + + **宽窗口(> 600px)**: + 并排显示侧边栏和详情的占位文本。 + 仅显示联系人分组页面。 + Both the sidebar and main content area show placeholder text for now. +目前侧边栏和主内容区域都显示占位文本。 + In the next lesson, you'll implement slivers to fill in the contact list content. +在下一课中,你将实现 sliver 以填充 +联系人列表内容。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Created responsive layouts with LayoutBuilder + - title: 使用 LayoutBuilder 创建了响应式布局 icon: fit_screen details: >- - `LayoutBuilder` provides the parent's size constraints in - its builder callback. By checking `constraints.maxWidth`, - you can decide which layout to show based on available space. - - title: Detected screen size to choose different layouts + `LayoutBuilder` 在其 builder 回调中提供父级的尺寸约束。 + 通过检查 `constraints.maxWidth`, + 你可以根据可用空间决定显示哪种布局。 + - title: 检测屏幕尺寸以选择不同布局 icon: devices details: >- - You used a 600-pixel breakpoint to - distinguish phone-sized screens from tablet-sized screens. - This common threshold helps your app adapt its UI to - provide the best experience on each device. - - title: Built a sidebar and detail layout for large screens + 你使用 600 像素断点来 + 区分手机尺寸屏幕与平板尺寸屏幕。 + 这一常用阈值帮助你的应用调整 UI, + 以便在每种设备上提供最佳体验。 + - title: 为大屏幕构建了侧边栏与详情布局 icon: view_sidebar details: >- - On large screens, you displayed a fixed-width sidebar and - an `Expanded` detail panel side-by-side using a `Row`. - This classic pattern maximizes screen real estate on tablets and desktops. + 在大屏幕上,你使用 `Row` 并排显示固定宽度侧边栏和 + `Expanded` 详情面板。 + 这一经典模式可以最大化平板和桌面上的屏幕空间。 ### Test yourself - -- question: What information does LayoutBuilder provide to its builder callback? +### 自测 + + +- question: LayoutBuilder 向其 builder 回调提供哪些信息? options: - - text: The device's operating system and screen orientation. + - text: 设备的操作系统和屏幕方向。 correct: false - explanation: LayoutBuilder provides size constraints, not OS or orientation info. - - text: The parent's size constraints, including maximum width and height. + explanation: LayoutBuilder 提供尺寸约束,而非操作系统或方向信息。 + - text: 父级的尺寸约束,包括最大宽度和高度。 correct: true - explanation: LayoutBuilder's builder receives BoxConstraints that tell you the available space from the parent. - - text: The current theme colors and typography. + explanation: LayoutBuilder 的 builder 会收到 BoxConstraints,告知你来自父级的可用空间。 + - text: 当前的主题颜色和排版。 correct: false - explanation: Theme data comes from Theme.of(context), not LayoutBuilder. - - text: The number of child widgets in the tree. + explanation: 主题数据来自 Theme.of(context),而非 LayoutBuilder。 + - text: widget 树中子 widget 的数量。 correct: false - explanation: LayoutBuilder provides layout constraints, not widget tree information. -- question: In a large screen layout, which widget can be used to place a sidebar and details panel side-by-side? + explanation: LayoutBuilder 提供布局约束,而非 widget 树信息。 +- question: 在大屏幕布局中,可以使用哪个 widget 将侧边栏和详情面板并排放置? options: - text: Column correct: false - explanation: Column arranges widgets vertically, not side-by-side. + explanation: Column 垂直排列 widget,而非并排。 - text: Row correct: true - explanation: Row arranges its children horizontally, making it ideal for placing a sidebar and details panel side-by-side. + explanation: Row 水平排列其子级,非常适合将侧边栏和详情面板并排放置。 - text: Stack correct: false - explanation: Stack overlaps widgets on top of each other, not side-by-side. + explanation: Stack 将 widget 层叠在一起,而非并排。 - text: ListView correct: false - explanation: ListView is for scrollable lists, not for side-by-side layout. + explanation: ListView 用于可滚动列表,而非并排布局。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md b/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md index 482152b8ee..5ff8be565d 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md +++ b/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md @@ -1,21 +1,28 @@ --- -title: Advanced UI features +# title: Advanced UI features +title: 高级 UI 特性 +# description: >- +# A gentle introduction into advanced UI features: +# adaptive layouts, slivers, scrolling, navigation. description: >- - A gentle introduction into advanced UI features: - adaptive layouts, slivers, scrolling, navigation. + 高级 UI 特性的温和入门: + 自适应布局、sliver、滚动、导航。 layout: tutorial +ai-translated: true --- Preview the Rolodex app you'll build and set up a Cupertino-based project with data models. +预览你将构建的 Rolodex 应用,并搭建基于 Cupertino 的项目与数据模型。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Preview the Rolodex app you'll build + - title: 预览你将构建的 Rolodex 应用 icon: preview - - title: Set up a project with Cupertino widgets + - title: 使用 Cupertino widget 搭建项目 icon: phone_iphone - - title: Create data models for contacts and groups + - title: 为联系人和分组创建数据模型 icon: data_object @@ -23,10 +30,16 @@ items: ### Introduction +### 简介 + In this third installment of the Flutter tutorial series, you'll use Flutter's Cupertino library to build a partial clone of the iOS Contacts app. +在 Flutter 教程系列的第三部中, +你将使用 Flutter 的 Cupertino 库构建 +iOS 通讯录应用的部分克隆版。 + A screenshot of the completed Rolodex contact
 management app showing a list of contacts organized alphabetically. @@ -35,10 +48,18 @@ By the end of this tutorial, you'll have learned how to create adaptive layouts, implement comprehensive theming, build navigation patterns, and use advanced scrolling techniques. +学完本教程后,你将学会如何创建 +自适应布局、实现全面的主题、构建导航 +模式,以及使用高级滚动技术。 + #### What you'll learn +#### 你将学习的内容 + This tutorial explores the following topics: +本教程探讨以下主题: + * Building responsive layouts with `LayoutBuilder`. * Using advanced scrolling with slivers and search. * Implementing stack-based navigation patterns. @@ -46,20 +67,40 @@ This tutorial explores the following topics: * Supporting both light and dark themes. * Creating an iOS-style UI using Cupertino widgets. + 使用 `LayoutBuilder` 构建响应式布局。 +* 使用 sliver 和搜索实现高级滚动。 +* 实现基于堆栈的导航模式。 +* 使用 `CupertinoThemeData` 创建全面的主题。 +* 支持浅色和深色主题。 +* 使用 Cupertino widget 创建 iOS 风格 UI。 + This tutorial assumes that you've completed the previous Flutter tutorials and are comfortable with basic widget composition, state management, and the Flutter project structure. +本教程假定你已完成之前的 Flutter 教程, +并熟悉基本的 widget 组合、状态管理 +以及 Flutter 项目结构。 + ### Create a new Flutter project +### 创建新的 Flutter 项目 + To build a Flutter app, you first need a Flutter project. You can create a new app with the [Flutter CLI tool][], which is installed as part of the Flutter SDK. +要构建 Flutter 应用,你首先需要一个 Flutter 项目。 +你可以使用随 Flutter SDK 一起安装的 [Flutter CLI 工具][Flutter CLI tool] +创建新应用。 + Open your preferred terminal and run the following command to create a new Flutter project: +打开你偏好的终端并运行 +以下命令以创建新的 Flutter 项目: + ```console $ flutter create rolodex --empty $ cd rolodex @@ -68,23 +109,37 @@ $ cd rolodex This command creates a new Flutter project that uses the minimal "empty" template. +此命令会创建一个使用精简「empty」模板的 +新 Flutter 项目。 + [Flutter CLI tool]: /reference/flutter-cli ### Add the Cupertino Icons dependency +### 添加 Cupertino Icons 依赖 + This project uses the [`cupertino_icons` package][], an official Flutter package. Add it as a dependency by running the following command: +此项目使用 [`cupertino_icons` package][], +这是一个官方 Flutter package。 +通过运行以下命令将其添加为依赖: + ```console $ flutter pub add cupertino_icons ``` ### Set up the project structure +### 搭建项目结构 + First, create the basic directory structure for your app. In your project's `lib` directory, create the following folders: +首先,为应用创建基本目录结构。 +在项目的 `lib` 目录中,创建以下文件夹: + ```console $ mkdir lib/data lib/screens lib/theme ``` @@ -92,11 +147,19 @@ $ mkdir lib/data lib/screens lib/theme This command creates folders to organize your code into logical sections: data models, screen widgets, and theme configuration. +此命令会创建文件夹,将代码组织为逻辑分区: +数据模型、屏幕 widget 和主题配置。 + ### Replace the starter code +### 替换入门代码 + In your IDE, open the `lib/main.dart` file, and replace its entire contents with the following starter code: +在 IDE 中打开 `lib/main.dart` 文件,并将其全部 +内容替换为以下入门代码: + ```dart import 'package:flutter/cupertino.dart'; @@ -129,10 +192,19 @@ this app uses `CupertinoApp` instead of `MaterialApp`. The Cupertino design system provides iOS-style widgets and styling, which is perfect for building apps that feel native on Apple devices. +与之前两个教程不同, +此应用使用 `CupertinoApp` 而非 `MaterialApp`。 +Cupertino 设计系统提供 iOS 风格的 widget 和样式, +非常适合构建在 Apple 设备上具有原生体验的应用。 + ### Run your app +### 运行应用 + In your terminal at the root of your Flutter app, run the following command: +在 Flutter 应用根目录的终端中,运行以下命令: + ```console $ flutter run -d chrome ``` @@ -140,16 +212,29 @@ $ flutter run -d chrome The app builds and launches in a new instance of Chrome. It displays "Hello Rolodex!" in the center of the screen. +应用会在新的 Chrome 实例中构建并启动。 +它会在屏幕中央显示「Hello Rolodex!」。 + ### Create the data models +### 创建数据模型 + Before building the UI, create the data structures and sample data that the app will use. This section is lightly explained because it's not the focus of this tutorial. +在构建 UI 之前, +创建应用将使用的数据结构和示例数据。 +本节仅作简要说明,因为不是本教程的重点。 + #### Contact data +#### 联系人数据 + Create a new file, `lib/data/contact.dart`, and add the basic `Contact` class: +创建新文件 `lib/data/contact.dart`,并添加基本的 `Contact` 类: + ```dart foldable class Contact { @@ -346,12 +431,21 @@ final Set allContacts = { This sample data includes contacts with and without middle names and suffixes. This gives you a variety of data to work with as you build the UI. +此示例数据包含带和不带中间名、后缀的联系人。 +这为你构建 UI 时提供了多种数据可供使用。 + #### ContactGroup data +#### ContactGroup 数据 + Now, create the contact groups that organize your contacts into lists. Create a new `lib/data/contact_group.dart` file and add the `ContactGroup` class: +现在,创建将联系人组织成列表的分组。 +创建新的 `lib/data/contact_group.dart` 文件并 +添加 `ContactGroup` 类: + ```dart import 'dart:collection'; @@ -414,8 +508,13 @@ class ContactGroup { A `ContactGroup` represents a collection of contacts, such as "All Contacts" or "Favorites". +`ContactGroup` 表示一组联系人, +例如「All Contacts」或「Favorites」。 + Add the following helper code and sample data to `lib/data/contact_group.dart`: +将以下辅助代码和示例数据添加到 `lib/data/contact_group.dart`: + ```dart typedef AlphabetizedContactMap = SplayTreeMap>; @@ -471,8 +570,13 @@ List generateSeedData() { This code creates three sample groups and a function to generate the initial data for the app. +此代码创建三个示例分组和一个函数,用于 +生成应用的初始数据。 + Finally, add a class that manages state changes to `lib/data/contact_group.dart`: +最后,在 `lib/data/contact_group.dart` 中添加管理状态变化的类: + ```dart class ContactGroupsModel { @@ -498,13 +602,22 @@ If you aren't familiar with `ValueNotifier`, you should complete the [previous tutorial covering state][] before continuing, which covers state management. +如果你不熟悉 `ValueNotifier`, +应先完成[涵盖状态的上一篇教程][previous tutorial covering state]再继续, +该教程涵盖状态管理。 + [previous tutorial covering state]: /learn/pathway/tutorial/set-up-state-project ### Connect the data to your app +### 将数据连接到应用 + Update your `main.dart` to include the global state and import the new data file: +更新 `main.dart` 以包含全局状态并 +导入新的数据文件: + ```dart import 'package:flutter/cupertino.dart'; @@ -539,65 +652,72 @@ class RolodexApp extends StatelessWidget { With all the extraneous code out of the way, in the next lesson, you'll start building the app in earnest. +清理完所有多余代码后,在下一课中, +你将正式开始构建应用。 + [`cupertino_icons` package]: {{site.pub-pkg}}/cupertino_icons ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Previewed the Rolodex app + - title: 预览了 Rolodex 应用 icon: preview details: >- - You're starting a new tutorial section focused on advanced UI features. - To make your app feel polished and native on any device, - you'll learn adaptive layouts, slivers, navigation, and theming. - - title: Set up a project with Cupertino widgets + 你正在开始一个专注于高级 UI 特性的新教程章节。 + 为了让应用在任何设备上都感觉精致且原生, + 你将学习自适应布局、sliver、导航和主题。 + - title: 使用 Cupertino widget 搭建了项目 icon: phone_iphone details: >- - Unlike the previous lessons, - this app uses `CupertinoApp` instead of `MaterialApp`. - The Cupertino design system provides iOS-style widgets that - feel native on Apple devices. - - title: Created data models for contacts and groups + 与之前的课程不同, + 此应用使用 `CupertinoApp` 而非 `MaterialApp`。 + Cupertino 设计系统提供在 Apple 设备上 + 具有原生体验的 iOS 风格 widget。 + - title: 为联系人和分组创建了数据模型 icon: data_object details: >- - You created `Contact` and `ContactGroup` classes with sample data, - plus a `ContactGroupsModel` for state management. - This foundation supports the UI you'll build in the coming lessons. + 你创建了带示例数据的 `Contact` 和 `ContactGroup` 类, + 以及用于状态管理的 `ContactGroupsModel`。 + 这一基础将支持你在后续课程中构建的 UI。 ### Test yourself - -- question: What is the main difference between CupertinoApp and MaterialApp? +### 自测 + + +- question: CupertinoApp 与 MaterialApp 的主要区别是什么? options: - - text: CupertinoApp only works on iOS devices. + - text: CupertinoApp 只能在 iOS 设备上运行。 correct: false - explanation: CupertinoApp can run on any platform; it just provides iOS-style widgets. - - text: CupertinoApp provides iOS-style widgets and styling, while MaterialApp provides Material Design widgets. + explanation: CupertinoApp 可在任何平台上运行;它只是提供 iOS 风格的 widget。 + - text: CupertinoApp 提供 iOS 风格的 widget 和样式,而 MaterialApp 提供 Material Design widget。 correct: true - explanation: CupertinoApp uses Cupertino design system widgets that match the iOS look and feel. - - text: CupertinoApp is lighter and has better performance. + explanation: CupertinoApp 使用与 iOS 外观和体验相匹配的 Cupertino 设计系统 widget。 + - text: CupertinoApp 更轻量且性能更好。 correct: false - explanation: Both have similar performance; they differ in visual style, not speed. - - text: MaterialApp requires more configuration to set up. + explanation: 两者性能相近;区别在于视觉风格,而非速度。 + - text: MaterialApp 需要更多配置才能搭建。 correct: false - explanation: Both have similar setup requirements; they just use different design systems. -- question: What is the purpose of a ValueNotifier in state management? + explanation: 两者搭建要求相近;只是使用不同的设计系统。 +- question: ValueNotifier 在状态管理中的作用是什么? options: - - text: To validate user input values. + - text: 验证用户输入值。 correct: false - explanation: ValueNotifier holds and notifies about value changes, not validation. - - text: To hold a single value and notify listeners when that value changes. + explanation: ValueNotifier 持有值并在值变化时通知,而非用于验证。 + - text: 持有一个值,并在该值变化时通知监听者。 correct: true - explanation: ValueNotifier is a simple ChangeNotifier that wraps a single value and notifies listeners on change. - - text: To convert values between different data types. + explanation: ValueNotifier 是一个简单的 ChangeNotifier,它包装单个值并在变化时通知监听者。 + - text: 在不同数据类型之间转换值。 correct: false - explanation: Type conversion is not the purpose of ValueNotifier. - - text: To store values permanently in local storage. + explanation: 类型转换不是 ValueNotifier 的用途。 + - text: 将值永久存储在本地存储中。 correct: false - explanation: ValueNotifier holds values in memory; persistence requires separate implementation. + explanation: ValueNotifier 在内存中持有值;持久化需要单独实现。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md b/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md index 8578d18623..9b66260e91 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md +++ b/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md @@ -1,19 +1,24 @@ --- -title: State management in Flutter -description: Instructions on how to manage state with ChangeNotifiers. +# title: State management in Flutter +title: Flutter 中的状态管理 +# description: Instructions on how to manage state with ChangeNotifiers. +description: 如何使用 ChangeNotifier 管理状态的说明。 layout: tutorial +ai-translated: true --- Learn to create a ViewModel with ChangeNotifier and manage loading, success, and error states. +学习使用 ChangeNotifier 创建 ViewModel,并管理加载、成功与错误状态。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Create a ViewModel with ChangeNotifier + - title: 使用 ChangeNotifier 创建 ViewModel icon: layers - - title: Manage loading, success, and error states + - title: 管理加载、成功与错误状态 icon: toggle_on - - title: Signal UI updates with notifyListeners + - title: 使用 notifyListeners 通知 UI 更新 icon: notifications_active @@ -21,27 +26,48 @@ items: ### Introduction +### 介绍 + When developers talk about state-management in Flutter, they're essentially referring to the pattern by which your app updates the data it needs to render correctly and then tells Flutter to re-render the UI with that new data. +当开发者在 Flutter 中谈论状态管理时, +他们本质上指的是应用 +更新正确渲染所需数据,然后 +通知 Flutter 用新数据重新渲染 UI 的模式。 + In MVVM, this responsibility falls to the ViewModel layer, which sits between and connects your UI to your Model layer. In Flutter, ViewModels use Flutter's `ChangeNotifier` class to notify the UI when data changes. +在 MVVM 中,这一职责落在 ViewModel 层, +它位于 UI 与 Model 层之间并连接两者。 +在 Flutter 中,ViewModel 使用 Flutter 的 `ChangeNotifier` 类在 +数据变化时通知 UI。 + To use [`ChangeNotifier`][], extend it in your state management class to gain access to the `notifyListeners()` method, which triggers UI rebuilds when called. +要使用 [`ChangeNotifier`][],在你的状态管理类中继承它以 +获得 `notifyListeners()` 方法的访问权限, +调用该方法时会触发 UI 重建。 + [`ChangeNotifier`]: {{site.api}}/flutter/foundation/ChangeNotifier-class.html ### Create the basic view model structure +### 创建基本 view model 结构 + Create the `ArticleViewModel` class with its basic structure and state properties: +创建 `ArticleViewModel` 类及其 +基本结构与状态属性: + ```dart class ArticleViewModel extends ChangeNotifier { @@ -56,15 +82,30 @@ class ArticleViewModel extends ChangeNotifier { The `ArticleViewModel` holds three pieces of state: +`ArticleViewModel` 保存三份状态: + - `summary`: The current Wikipedia article data. + + `summary`:当前的 Wikipedia 文章数据。 + - `error`: Any error that occurred during data fetching. + + `error`:数据获取过程中发生的任何错误。 + - `isLoading`: A flag to show progress indicators. + `isLoading`:用于显示进度指示器的标志。 + ### Add constructor initialization +### 添加构造函数初始化 + Update the constructor to automatically fetch content when the `ArticleViewModel` is created: +更新构造函数,以便在创建 +`ArticleViewModel` 时自动获取内容: + ```dart class ArticleViewModel extends ChangeNotifier { @@ -87,10 +128,19 @@ a `ArticleViewModel` object is created. Because constructors can't be asynchronous, it delegates initial content fetching to a separate method. +此构造函数初始化在创建 +`ArticleViewModel` 对象时即可提供内容。 +由于构造函数不能是异步的, +它将初始内容获取委托给单独的方法。 + ### Set up the `fetchArticle` method +### 设置 `fetchArticle` 方法 + Add the `fetchArticle` method that fetches data and manages state updates: +添加用于获取数据并管理状态更新的 `fetchArticle` 方法: + ```dart class ArticleViewModel extends ChangeNotifier { @@ -121,14 +171,28 @@ When the operation completes, it toggles the property back. When you build the UI, you'll use this `isLoading` property to show a loading indicator while fetching a new article. +ViewModel 会更新 `isLoading` 属性并 +调用 `notifyListeners()` 通知 UI 已更新。 +操作完成后,它会将该属性切换回来。 +构建 UI 时,你将使用此 `isLoading` 属性在 +获取新文章时显示加载指示器。 + ### Retrieve an article from the `ArticleModel` +### 从 `ArticleModel` 获取文章 + Complete the `fetchArticle` method to fetch an article summary. Use a [try-catch block][] to gracefully handle network errors and store error messages that the UI can display to users. The method clears previous errors on success and clears the previous article summary on error to maintain a consistent state. +完善 `fetchArticle` 方法以获取文章摘要。 +使用 [try-catch block][] 优雅地处理网络错误并 +保存 UI 可向用户显示的错误消息。 +该方法在成功时清除先前的错误,在 +出错时清除先前的文章摘要以保持一致的状态。 + ```dart class ArticleViewModel extends ChangeNotifier { @@ -161,11 +225,18 @@ class ArticleViewModel extends ChangeNotifier { ### Test the ViewModel +### 测试 ViewModel + Before building the full UI, test that your HTTP requests work by printing results to the console. First, update the `fetchArticle` method to print the results: +在构建完整 UI 之前,通过将结果打印到控制台 +来测试 HTTP 请求是否有效。 +首先,更新 `fetchArticle` 方法以 +打印结果: + ```dart Future fetchArticle() async { @@ -188,6 +259,9 @@ Future fetchArticle() async { Then, update the `MainApp` widget to create the `ArticleViewModel`, which calls the `fetchArticle` method on creation: +然后,更新 `MainApp` widget 以创建 `ArticleViewModel`, +它在创建时会调用 `fetchArticle` 方法: + ```dart class MainApp extends StatelessWidget { @@ -212,65 +286,73 @@ Hot reload your app and check your console output. You should see either an article title or an error message, which confirms that your Model and ViewModel are wired up correctly. +热重载应用并查看控制台输出。 +你应看到文章标题或错误消息, +这确认 Model 与 ViewModel 已正确连接。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Created the ArticleViewModel with ChangeNotifier + - title: 使用 ChangeNotifier 创建了 ArticleViewModel icon: layers details: >- - The ViewModel sits between your UI and Model, - managing state and connecting the two layers. - By extending `ChangeNotifier`, your ViewModel gains the ability to - notify listeners when data changes. - - title: Managed loading, success, and error states + ViewModel 位于 UI 与 Model 之间, + 管理状态并连接两层。 + 通过继承 `ChangeNotifier`,ViewModel 获得在 + 数据变化时通知监听者的能力。 + - title: 管理了加载、成功与错误状态 icon: toggle_on details: >- - Your ViewModel tracks three pieces of state: - `isLoading`, `summary`, and `error`. - Using `try` and `catch`, you handle network errors gracefully and - maintain consistent state for each possible outcome. - - title: Used notifyListeners to signal UI updates + ViewModel 跟踪三份状态: + `isLoading`、`summary` 和 `error`。 + 使用 `try` 和 `catch`,你优雅地处理网络错误并 + 为每种可能结果保持一致的状态。 + - title: 使用 notifyListeners 通知 UI 更新 icon: notifications_active details: >- - Calling `notifyListeners()` tells any listening widgets to rebuild. - You call it after setting `loading = true` and again - after the operation completes. - This is how you can implement reactive UI updates in Flutter. + 调用 `notifyListeners()` 会告诉所有监听的 widget 重建。 + 你在将 `loading = true` 之后调用一次,在 + 操作完成后再调用一次。 + 这就是在 Flutter 中实现响应式 UI 更新的方式。 ### Test yourself - -- question: What is a ChangeNotifier? +### 自测 + + +- question: ChangeNotifier 是什么? options: - - text: A widget that displays notifications to the user. + - text: 向用户显示通知的 widget。 correct: false - explanation: ChangeNotifier is not a widget; it's a class for managing state. - - text: A class that can notify listeners when its data changes, enabling reactive UI updates. + explanation: ChangeNotifier 不是 widget;它是用于管理状态的类。 + - text: 当数据变化时可通知监听者、从而实现响应式 UI 更新的类。 correct: true - explanation: ChangeNotifier provides the notifyListeners method to signal widgets to rebuild when state changes. - - text: A built-in Dart class for sending push notifications. + explanation: ChangeNotifier 提供 notifyListeners 方法,在状态变化时通知 widget 重建。 + - text: 用于发送推送通知的内置 Dart 类。 correct: false - explanation: ChangeNotifier is for in-app state management, not push notifications. - - text: A type of animation controller in Flutter. + explanation: ChangeNotifier 用于应用内状态管理,而非推送通知。 + - text: Flutter 中的一种动画控制器类型。 correct: false - explanation: Animation controllers are separate; ChangeNotifier is for state management. -- question: "What does calling `notifyListeners()` do in a ChangeNotifier?" + explanation: 动画控制器是独立的;ChangeNotifier 用于状态管理。 +- question: "在 ChangeNotifier 中调用 `notifyListeners()` 会做什么?" options: - - text: Saves the current state to local storage. + - text: 将当前状态保存到本地存储。 correct: false - explanation: "`notifyListeners()` signals UI updates; persistence requires separate implementation." - - text: Tells any listening widgets to rebuild and reflect the new state. + explanation: "`notifyListeners()` 通知 UI 更新;持久化需要单独实现。" + - text: 告诉所有监听的 widget 重建并反映新状态。 correct: true - explanation: "Calling `notifyListeners()` triggers a rebuild of all widgets listening to this ChangeNotifier." - - text: Logs the state change to the console for debugging. + explanation: "调用 `notifyListeners()` 会触发监听此 ChangeNotifier 的所有 widget 重建。" + - text: 将状态变化记录到控制台以便调试。 correct: false - explanation: It doesn't log anything; it signals listeners to rebuild. - - text: Resets all state properties to their default values. + explanation: 它不会记录任何内容;它通知监听者重建。 + - text: 将所有状态属性重置为默认值。 correct: false - explanation: "`notifyListeners()` doesn't modify state; it just signals that state has changed." + explanation: "`notifyListeners()` 不会修改状态;它只是表明状态已变化。" diff --git a/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md b/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md index 519807b6a7..69e1a3c0b2 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md +++ b/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md @@ -1,21 +1,26 @@ --- -title: Create an app -description: Instructions on how to create a new Flutter app. +# title: Create an app +title: 创建应用 +# description: Instructions on how to create a new Flutter app. +description: 如何创建新 Flutter 应用的说明。 layout: tutorial +ai-translated: true --- Learn the first steps to building a Flutter app, from creating a project to understanding widgets and hot reload. +学习构建 Flutter 应用的第一步:从创建项目到理解 widget 与热重载。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Create a new Flutter project using the CLI + - title: 使用 CLI 创建新的 Flutter 项目 icon: terminal - - title: Understand widgets and the widget tree + - title: 理解 widget 与 widget 树 icon: account_tree - - title: Run your app and use hot reload + - title: 运行应用并使用热重载 icon: bolt @@ -23,41 +28,65 @@ items: ### What you'll build +### 你将构建的内容 + In this first section of the Flutter tutorial, you'll build the core UI of an app called 'Birdle', a game similar to [Wordle, the popular New York Times game][]. +在 Flutter 教程的第一部分, +你将构建名为「Birdle」的应用的核心 UI, +这是一款类似于 [Wordle, the popular New York Times game][] 的游戏。 + A screenshot that resembles the popular game Wordle. By the end of this tutorial, you'll have learned the fundamentals of building Flutter UIs, and your app will look like the following screenshot (and it'll even mostly work 😀). +学完本教程后,你将掌握构建 Flutter UI 的基础知识,你的应用将 +与下面的截图相似(而且基本上还能正常运行 😀)。 + [Wordle, the popular New York Times game]: https://www.nytimes.com/games/wordle/index.html ### Create a new Flutter project +### 创建新的 Flutter 项目 + The first step to building Flutter apps is to create a new project. You create new apps with the [Flutter CLI tool][], installed as part of the Flutter SDK. +构建 Flutter 应用的第一步是创建新项目。 +你可以使用随 Flutter SDK 一起安装的 [Flutter CLI 工具][Flutter CLI tool] 创建新应用。 + Open your terminal or command prompt and run the following command to create a new Flutter project: +打开终端或命令提示符,运行 +以下命令以创建新的 Flutter 项目: + ```console $ flutter create birdle --empty ``` This creates a new Flutter project using the minimal "empty" template. +这将使用精简的「empty」模板创建一个新的 Flutter 项目。 + [Flutter CLI tool]: /reference/flutter-cli ### Examine the code +### 查看代码 + In your IDE, open the file at `lib/main.dart`. Starting from the top, you'll see this code. +在 IDE 中打开 `lib/main.dart` 文件。 +从文件顶部开始,你会看到以下代码。 + ```dart title="lib/main.dart" void main() { @@ -71,8 +100,16 @@ The `runApp` method is part of the Flutter SDK, and it takes a **widget** as an argument. In this case, an instance of the `MainApp` widget is being passed in. +`main` 函数是任何 Dart 程序的入口点, +而 Flutter 应用本质上就是一个 **Dart** 程序。 +`runApp` 方法是 Flutter SDK 的一部分, +它接受一个 **widget** 作为参数。 +此处传入的是 `MainApp` widget 的一个实例。 + Just below the `main` function, you'll find the `MainApp` class declaration. +在 `main` 函数正下方,你会找到 `MainApp` 类的声明。 + ```dart class MainApp extends StatelessWidget { @@ -99,41 +136,71 @@ Essentially, this is what a Flutter app is: a composition of widgets that make up a tree structure called the **widget tree.** +`MainApp` 是 **根 widget**, +因为它是传入 `runApp` 的 widget。 +在这个 widget 内部,有一个 `build` 方法, +它返回名为 `MaterialApp` 的另一个 widget。 +本质上,Flutter 应用就是这样: +由多个 widget 组合而成,构成称为 **widget 树** 的树形结构。 + Your job as a Flutter developer is to compose widgets from the SDK into larger, custom widgets that display a UI. +作为 Flutter 开发者,你的任务是将 +SDK 中的 widget 组合成更大的自定义 widget,以展示 UI。 + At the moment, the widget tree is quite simple: +目前,widget 树相当简单: + A screenshot that resembles the popular game Wordle. ### Run your app +### 运行应用 + 1. In your terminal, navigate to the root directory of your created Flutter app: +1. 在终端中, + 进入你创建的 Flutter 应用的根目录: + ```console $ cd birdle ``` 1. Run the app using the Flutter CLI tool. +1. 使用 Flutter CLI 工具运行应用。 + ```console $ flutter run -d chrome ``` The app will build and launch in a new instance of Chrome. + 应用将在新的 Chrome 实例中构建并启动。 + A screenshot that resembles the popular game Wordle. ### Use hot reload +### 使用热重载 + **Stateful hot reload**, if you haven't heard of it, allows a running Flutter app to re-render updated business logic or UI code in less than a second – all without losing your place in the app. +如果你还没听说过 **有状态热重载**, +它可以让正在运行的 Flutter 应用在不到一秒内 +重新渲染更新后的业务逻辑或 UI 代码——而且不会丢失你在应用中的当前位置。 + In your IDE, open the `main.dart` file and navigate to line ~15 and find this code: +在 IDE 中打开 `main.dart` 文件,定位到约第 15 行,找到以下 +代码: + ```dart child: Text('Hello World!'), @@ -144,67 +211,75 @@ Then, hot-reload your app by pressing `r` in the terminal where the app is running. The running app should instantly show your updated text. +将字符串中的文本改为你想要的任何内容。 +然后,在运行应用的终端中按 `r` 进行热重载。 +正在运行的应用应立即显示你更新后的文本。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Created your first Flutter project + - title: 创建了第一个 Flutter 项目 icon: terminal details: >- - You used `flutter create` with the `--empty` flag to - scaffold a minimal Flutter project. - The CLI generates the project structure and - boilerplate code needed to get started. - - title: Explored the widget tree + 你使用带 `--empty` 标志的 `flutter create` 搭建了 + 一个精简的 Flutter 项目。 + CLI 会生成项目结构和 + 入门所需的样板代码。 + - title: 探索了 widget 树 icon: account_tree details: >- - Flutter UIs are built by composing **widgets** into a tree structure. - The `runApp` function takes a root widget, and that widget's - `build` method returns other widgets, forming the **widget tree**. - Your job as a Flutter developer is to - compose these widgets into custom UIs. - - title: Ran your app with hot reload + Flutter UI 通过将 **widget** 组合成树形结构来构建。 + `runApp` 函数接受根 widget,该 widget 的 + `build` 方法返回其他 widget,形成 **widget 树**。 + 作为 Flutter 开发者,你的任务是 + 将这些 widget 组合成自定义 UI。 + - title: 使用热重载运行了应用 icon: bolt details: >- - You ran your app with `flutter run` and - experienced **stateful hot reload**, which lets you - see code changes reflected in under a second without losing app state. - Press `r` in the terminal to trigger a hot reload. + 你使用 `flutter run` 运行了应用,并 + 体验了 **有状态热重载**,它让你 + 在不到一秒内看到代码变更的反映,且不会丢失应用状态。 + 在终端中按 `r` 可触发热重载。 ### Test yourself - -- question: "What is the purpose of the `runApp` function in a Flutter application?" +### 自测 + + +- question: "Flutter 应用中 `runApp` 函数的作用是什么?" options: - - text: It compiles the Dart code into native machine code. + - text: 它将 Dart 代码编译为原生机器码。 correct: false - explanation: "Compilation happens before the app runs; `runApp` starts the Flutter framework with a root widget." - - text: It takes a widget as an argument and makes it the root of the widget tree. + explanation: "编译在应用运行之前完成;`runApp` 使用根 widget 启动 Flutter 框架。" + - text: 它接受一个 widget 作为参数,并将其设为 widget 树的根。 correct: true - explanation: "The `runApp` function inflates the given widget and attaches it to the screen, making it the root of the widget tree." - - text: "It creates the `main.dart` file for the project." + explanation: "`runApp` 函数会展开给定的 widget 并将其附加到屏幕,使其成为 widget 树的根。" + - text: "它为项目创建 `main.dart` 文件。" correct: false - explanation: "The file is created by `flutter create`; `runApp` is called at runtime." - - text: It downloads Flutter dependencies from the internet. + explanation: "该文件由 `flutter create` 创建;`runApp` 在运行时调用。" + - text: 它从互联网下载 Flutter 依赖。 correct: false - explanation: "Dependencies are managed by `flutter pub get`, not `runApp`." -- question: How do you trigger a hot reload while a Flutter app is running in the terminal? + explanation: "依赖由 `flutter pub get` 管理,而非 `runApp`。" +- question: 当 Flutter 应用在终端中运行时,如何触发热重载? options: - - text: "Press `h` in the terminal." + - text: "在终端中按 `h`。" correct: false - explanation: "Pressing `h` shows help options; `r` triggers hot reload." - - text: "Press `r` in the terminal." + explanation: "按 `h` 会显示帮助选项;按 `r` 触发热重载。" + - text: "在终端中按 `r`。" correct: true - explanation: "Pressing `r` in the terminal where the app is running triggers a hot reload." - - text: "Stop and restart the app with `flutter run`." + explanation: "在运行应用的终端中按 `r` 会触发热重载。" + - text: "使用 `flutter run` 停止并重启应用。" correct: false - explanation: A full restart is not needed; hot reload is faster. - - text: Save the file and wait for automatic reload. + explanation: 无需完全重启;热重载更快。 + - text: 保存文件并等待自动重载。 correct: false - explanation: "By default, you need to press `r` to trigger hot reload in the terminal." + explanation: "默认情况下,你需要在终端中按 `r` 才能触发热重载。" diff --git a/sites/docs/src/content/learn/pathway/tutorial/devtools.md b/sites/docs/src/content/learn/pathway/tutorial/devtools.md index f7f81c326c..89932e7e7f 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/devtools.md +++ b/sites/docs/src/content/learn/pathway/tutorial/devtools.md @@ -1,23 +1,29 @@ --- +# title: DevTools title: DevTools -description: Learn to use the Dart DevTools when developing Flutter apps. +# description: Learn to use the Dart DevTools when developing Flutter apps. +description: 学习在开发 Flutter 应用时使用 Dart DevTools。 layout: tutorial +ai-translated: true --- Learn to use the widget inspector and property editor to debug layout issues and experiment with properties in real-time. +学习使用 widget 检查器和属性编辑器来调试布局问题, +并实时试验属性。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Explore your app's widget tree with the widget inspector + - title: 使用 widget 检查器探索应用的 widget 树 icon: account_tree - - title: Learn to debug layout issues like unbounded constraints + - title: 学习调试无界约束等布局问题 icon: bug_report - - title: Experiment with properties in real-time + - title: 实时试验属性 icon: tune @@ -25,17 +31,28 @@ items: ### Introduction +### 简介 + As your Flutter app grows in complexity, it becomes more important to understand how each of the widget properties affects the UI. The [Dart and Flutter DevTools][] provide you with two particularly useful features: the **widget inspector** and the **property editor**. +随着 Flutter 应用日益复杂,理解各个 widget 属性如何影响 UI 变得越来越重要。 +[Dart 和 Flutter DevTools][Dart and Flutter DevTools] 为你提供了 +两个特别实用的功能: +**widget 检查器**和**属性编辑器**。 + First, launch DevTools by running the following commands while your app is running in debug mode. Run this command in a **separate terminal window** from where your app is running: +首先,在应用以调试模式运行时,通过运行以下命令启动 DevTools。 +请在**与应用运行位置不同的终端窗口**中 +运行此命令: + ```console $ dart devtools ``` @@ -43,14 +60,24 @@ $ dart devtools Running this command starts the DevTools server and opens the interface in a browser. +运行此命令会启动 DevTools 服务器, +并在浏览器中打开界面。 + To connect DevTools to your running app: +要将 DevTools 连接到你正在运行的应用: + 1. Find the DevTools URL printed in the terminal where your app is running (for example: `Serving DevTools at http://127.0.0.1:9101`). 2. Copy this URL. 3. Paste it into the connect bar in the DevTools browser page. +1. 在运行应用的终端中找到打印的 DevTools URL(例如: + `Serving DevTools at http://127.0.0.1:9101`)。 +2. 复制此 URL。 +3. 将其粘贴到 DevTools 浏览器页面的连接栏中。 + :::note Run in your IDE Provided you have the appropriate Flutter plugin installed, @@ -58,6 +85,13 @@ you can also run DevTools directly inside Code OSS-based editors such as [VS Code][] as well as [IntelliJ and Android Studio][]. The screenshots in this lesson are from VS Code. + +如果你已安装相应的 Flutter 插件, +你也可以直接在 +基于 Code OSS 的编辑器(例如 [VS Code][])以及 +[IntelliJ 和 Android Studio][IntelliJ and Android Studio] 中运行 DevTools。 +本课中的截图来自 VS Code。 + ::: [Dart and Flutter DevTools]: /tools/devtools @@ -66,16 +100,25 @@ The screenshots in this lesson are from VS Code. ### The widget inspector +### widget 检查器 + The widget inspector allows you to visualize and explore your widget tree. It helps you understand the layout of your UI and identifies which widgets are responsible for different parts of the screen. Running against the app you've built so far, the inspector looks like this: +widget 检查器让你可视化和探索 widget 树。 +它帮助你理解 UI 的布局, +并识别哪些 widget 负责屏幕的不同部分。 +针对你目前构建的应用运行后,检查器如下所示: + A screenshot of the Flutter widget inspector tool. Consider the `GamePage` widget you created in this section: +考虑你在本节中创建的 `GamePage` widget: + ```dart class GamePage extends StatelessWidget { GamePage({super.key}); @@ -105,6 +148,8 @@ class GamePage extends StatelessWidget { And how it's used in `MainApp`: +以及它在 `MainApp` 中的用法: + ```dart class MainApp extends StatelessWidget { const MainApp({super.key}); @@ -128,10 +173,22 @@ the `Row` widgets with `Tile` children. You can select any widget in the tree to see its properties and even jump to its source code in your IDE. +在 widget 检查器中,你应该能看到与代码中 +完全相同的 widget 树: +以 `MaterialApp` 为根,以 `Scaffold` 作为其 `home`, +以 `AppBar` 作为其 `appBar`,以此类推,一直到 +带有 `Tile` 子级的 `Row` widget。 +你可以在树中选择任意 widget 以查看其属性, +甚至跳转到 IDE 中的对应源代码。 + ### Debugging layout issues +### 调试布局问题 + The widget inspector is perhaps most useful for debugging layout issues. +widget 检查器或许在调试布局问题时最为实用。 + In certain situations, a widget's [constraints][] are unbounded, or infinite. This means that either @@ -139,11 +196,23 @@ the maximum width or the maximum height is set to [`double.infinity`][]. A widget that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception. +在某些情况下, +widget 的[约束][constraints]是无界的,即无限的。 +这意味着 +最大宽度或最大高度被设置为 [`double.infinity`][]。 +当给定无界约束时,试图尽可能大的 widget 无法正常工作, +并且在调试模式下会抛出异常。 + The most common case where a render box ends up with an unbounded constraint is within a flex box widget ([`Row`][] or [`Column`][]), and within a scrollable region, such as a [`ListView`][] or [`ScrollView`][] subclasses. +渲染 box 最终获得无界约束的最常见情况 +出现在 flex box widget([`Row`][] 或 [`Column`][])内, +以及可滚动区域内, +例如 [`ListView`][] 或 [`ScrollView`][] 子类。 + `ListView`, for example, tries to expand to fit the space available in its cross-direction. Such as if it's a vertically scrolling block that tries to be as wide as its parent. @@ -152,13 +221,28 @@ a horizontally scrolling `ListView`, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction. +例如,`ListView` 会尝试扩展以 +适应其交叉方向上的可用空间。比如 +它是一个垂直滚动的块,试图与其父级一样宽。 +如果你将垂直滚动的 `ListView` 嵌套在 +水平滚动的 `ListView` 内,内部列表会尝试 +尽可能宽,而由于 +外部列表在该方向上可滚动,因此宽度是无限的。 + Perhaps the most common error you'll run into while building a Flutter application is due to incorrectly using layout widgets. This error is referred to as the "unbounded constraints" error. +或许你在构建 Flutter 应用时 +最常遇到的错误是由于不正确地使用布局 widget。 +此错误被称为「无界约束」错误。 + Watch the following video to get an understanding of how to spot and resolve this issue. +观看以下视频,了解如何 +发现并解决此问题。 + @@ -171,16 +255,25 @@ spot and resolve this issue. ### The property editor +### 属性编辑器 + When you select a widget in the widget inspector, the property editor displays all the properties of that selected widget. This is a powerful tool for understanding why a widget looks the way it does and for experimenting with property value changes in real-time. +当你在 widget 检查器中选择一个 widget 时, +属性编辑器会显示该所选 widget 的所有属性。 +这是一个强大的工具,用于理解 widget 为何呈现当前外观, +并实时试验属性值的更改。 + A screenshot of the Flutter property editor tool. Look at the `Tile` widget's `build` method from earlier: +查看前面 `Tile` widget 的 `build` 方法: + ```dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); @@ -213,6 +306,12 @@ property. You could then expand the `BoxDecoration` to see the `border` and `color` properties. +如果你在 widget 检查器中选择 `Tile` 内的 `Container` widget, +属性编辑器会显示其 `width`(60)、`height`(60)以及 `decoration` +属性。 +然后你可以展开 `BoxDecoration` 以查看 `border` 和 `color` +属性。 + For many properties, you can even modify their values directly within the property editor. For example, to quickly test how a different `width` or `height` would look @@ -223,66 +322,79 @@ allowing you to instantly view the visual update on your running app upon saving or triggering a hot reload. This allows for rapid iteration on UI design. +对于许多属性,你甚至可以在 +属性编辑器中直接修改其值。 +例如,要快速测试 `Tile` widget 中 `Container` 的不同 `width` 或 `height` 效果, +可在属性编辑器中更改数值。 +该工具会直接将此更新写回磁盘上的 `.dart` 源文件, +让你在保存或触发热重载后 +立即在运行中的应用上查看视觉更新。 +这让你能够快速迭代 UI 设计。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Explored your app's widget tree with the widget inspector + - title: 使用 widget 检查器探索了应用的 widget 树 icon: account_tree details: >- - The widget inspector lets you visualize your entire widget tree, - select any widget to view its properties, and - jump directly to its source code. - It's an essential tool for understanding your app's structure. - - title: Learned about common layout issues + widget 检查器让你可视化整个 widget 树, + 选择任意 widget 以查看其属性, + 并直接跳转到其源代码。 + 它是理解应用结构的重要工具。 + - title: 了解了常见布局问题 icon: bug_report details: >- - You learned about **unbounded constraints**, - one of the most common errors hit in Flutter development. - This happens when widgets like - `Row`, `Column`, or `ListView` receive infinite constraints. - Now you can recognize and fix these issues when they occur. - - title: Experimented with properties in real-time + 你学习了 **无界约束**, + 这是 Flutter 开发中最常见的错误之一。 + 当 `Row`、`Column` 或 `ListView` 等 widget + 收到无限约束时就会发生这种情况。 + 现在你可以在遇到这些问题时识别并修复它们。 + - title: 实时试验了属性 icon: tune details: >- - The property editor shows all properties of a selected widget and - lets you modify values directly on disk, allowing you to instantly view - updates upon saving or triggering a hot reload. - This enables rapid iteration when fine-tuning your UI. + 属性编辑器显示所选 widget 的所有属性, + 并让你直接在磁盘上修改值,保存或触发热重载后即可 + 立即查看更新。 + 这让你在微调 UI 时能够快速迭代。 ### Test yourself - -- question: What is a common cause of "unbounded constraints" errors in Flutter? +### 自测 + + +- question: Flutter 中「无界约束」错误的常见原因是什么? options: - - text: Using too many StatefulWidgets in the widget tree. + - text: 在 widget 树中使用过多 StatefulWidget。 correct: false - explanation: StatefulWidget usage doesn't cause unbounded constraints. - - text: Placing a widget that tries to expand infinitely inside a scrollable or flex container without proper constraints. + explanation: 使用 StatefulWidget 不会导致无界约束。 + - text: 将试图无限扩展的 widget 放在可滚动或 flex 容器中,且未提供适当约束。 correct: true - explanation: Widgets like ListView inside a Row, or nested scrollables, can receive infinite constraints and fail. - - text: Forgetting to call setState after changing data. + explanation: 例如 Row 内的 ListView,或嵌套的可滚动组件,可能收到无限约束并失败。 + - text: 更改数据后忘记调用 setState。 correct: false - explanation: Missing setState causes UI not to update, not constraint errors. - - text: Using Container without specifying a color. + explanation: 未调用 setState 会导致 UI 不更新,而非约束错误。 + - text: 使用 Container 时未指定颜色。 correct: false - explanation: Color is optional and unrelated to layout constraints. -- question: What can you do with the Widget Inspector in Flutter DevTools? + explanation: 颜色是可选的,与布局约束无关。 +- question: 你可以在 Flutter DevTools 的 Widget Inspector 中做什么? options: - - text: Automatically generate unit tests for your widgets. + - text: 自动为你的 widget 生成单元测试。 correct: false - explanation: The Widget Inspector is for visualization and debugging, not test generation. - - text: Visualize your widget tree, select widgets to view their properties, and jump to source code. + explanation: Widget Inspector 用于可视化和调试,而非生成测试。 + - text: 可视化 widget 树,选择 widget 以查看其属性,并跳转到源代码。 correct: true - explanation: The Widget Inspector lets you explore your app's structure, inspect widget properties, and navigate to the corresponding source code. - - text: Deploy your app directly to the app store. + explanation: Widget Inspector 让你探索应用结构、检查 widget 属性,并导航到相应的源代码。 + - text: 直接将应用部署到应用商店。 correct: false - explanation: Deployment is handled separately; the Widget Inspector is for debugging. - - text: Edit your app's theme colors and typography. + explanation: 部署需单独处理;Widget Inspector 用于调试。 + - text: 编辑应用的主题颜色和排版。 correct: false - explanation: Theme editing requires code changes; the Widget Inspector is for inspecting the current state. + explanation: 主题编辑需要修改代码;Widget Inspector 用于检查当前状态。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/http-requests.md b/sites/docs/src/content/learn/pathway/tutorial/http-requests.md index 9856fdf213..9c189890fb 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/http-requests.md +++ b/sites/docs/src/content/learn/pathway/tutorial/http-requests.md @@ -1,19 +1,24 @@ --- -title: Fetch data from the internet -description: Instructions on how to make HTTP requests and parse responses. +# title: Fetch data from the internet +title: 从互联网获取数据 +# description: Instructions on how to make HTTP requests and parse responses. +description: 如何发起 HTTP 请求并解析响应的说明。 layout: tutorial +ai-translated: true --- Learn the MVVM architecture pattern and how to build HTTP requests with async/await. +学习 MVVM 架构模式,以及如何使用 async/await 构建 HTTP 请求。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Understand the MVVM architecture pattern + - title: 理解 MVVM 架构模式 icon: layers - - title: Build HTTP requests with async/await + - title: 使用 async/await 构建 HTTP 请求 icon: cloud_download - - title: Handle errors and parse JSON responses + - title: 处理错误并解析 JSON 响应 icon: data_object @@ -21,34 +26,66 @@ items: ### Introduction +### 简介 + The overarching pattern that this tutorial implements is called _Model-View-ViewModel_ or _MVVM_. MVVM is an [architectural pattern][] used in client apps that separates your app into three layers: +本教程实现的总体模式称为 +_模型-视图-视图模型_(_Model-View-ViewModel_)或 _MVVM_。 +MVVM 是一种用于客户端应用的[架构模式][architectural pattern],它将 +应用分为三层: + - **Model**: Handles data operations. + + **Model**:处理数据操作。 + - **View**: Displays the UI. + + **View**:显示 UI。 + - **ViewModel**: Manages state and connects the two. + **ViewModel**:管理状态并连接前两者。 + The core tenet of MVVM (and many other patterns) is *separation of concerns*. Managing state in separate classes (outside your UI widgets) makes your code more testable, reusable, and easier to maintain. +MVVM(及许多其他模式)的核心原则是*关注点分离*。 +在单独的类中管理状态(在 UI widget 之外)使 +代码更易测试、复用和维护。 + A single feature in your app contains each one of the MVVM components. In this tutorial, in addition to Flutter widgets, you'll create `ArticleModel`, `ArticleViewModel`, and `ArticleView`. +应用中的单个功能包含 MVVM 的每个组件。 +在本教程中,除了 Flutter widget 外, +你还将创建 `ArticleModel`、`ArticleViewModel` 和 `ArticleView`。 + [architectural pattern]: /app-architecture/guide ### Define the Model +### 定义 Model + The Model is the source-of-truth for your app's data and is responsible for low-level tasks such as making HTTP requests, caching data, or managing system resources such as used by a Flutter plugin. A model doesn't usually need to import Flutter libraries. +Model 是应用数据的唯一真实来源,负责 +底层任务,例如发起 HTTP 请求、缓存数据,或 +管理 Flutter 插件等使用的系统资源。 +Model 通常不需要导入 Flutter 库。 + Create an empty `ArticleModel` class in your `main.dart` file: +在 `main.dart` 文件中创建一个空的 `ArticleModel` 类: + ```dart class ArticleModel { @@ -58,15 +95,22 @@ class ArticleModel { ### Build the HTTP request +### 构建 HTTP 请求 + Wikipedia provides a REST API that returns JSON data about articles. For this app, you'll use the endpoint that returns a random article summary. +Wikipedia 提供返回文章 JSON 数据的 REST API。 +在本应用中,你将使用返回随机文章摘要的端点。 + ```text https://en.wikipedia.org/api/rest_v1/page/random/summary ``` Add a method to fetch a random Wikipedia article summary: +添加一个获取随机 Wikipedia 文章摘要的方法: + ```dart class ArticleModel { @@ -87,21 +131,37 @@ Use the [`async` and `await`][] keywords to handle asynchronous operations. The `async` keyword marks a method as asynchronous, and `await` waits for expressions that return a [`Future`][]. +使用 [`async` 和 `await`][`async` and `await`] 关键字处理异步操作。 +`async` 关键字将方法标记为异步, +`await` 会等待返回 [`Future`][] 的表达式。 + The `Uri.https` constructor safely builds URLs by handling encoding and formatting. This approach is more reliable than string concatenation, especially when dealing with special characters or query parameters. +`Uri.https` 构造函数通过 +处理编码和格式安全地构建 URL。 +这种方式比字符串拼接更可靠, +尤其在处理特殊字符或查询参数时。 + [`async` and `await`]: {{site.dart-site}}/language/async [`Future`]: {{site.api}}/flutter/dart-async/Future-class.html ### Handle network errors +### 处理网络错误 + Always handle errors when making HTTP requests. A status code of **200** indicates success, while other codes indicate errors. If the status code isn't **200**, the model throws an error for the UI to display to users. +发起 HTTP 请求时务必处理错误。 +状态码 **200** 表示成功,其他状态码表示错误。 +若状态码不是 **200**,Model 会抛出错误供 +UI 向用户显示。 + ```dart class ArticleModel { @@ -124,10 +184,16 @@ class ArticleModel { ### Parse JSON from Wikipedia +### 解析来自 Wikipedia 的 JSON + The [Wikipedia API][] returns [JSON][] data that -you decode into a `Summary` class +you decode into a `Summary` class. Complete the `getRandomArticleSummary` method: +[Wikipedia API][] 返回 [JSON][] 数据, +你需要将其解码为 `Summary` 类。 +完成 `getRandomArticleSummary` 方法: + ```dart class ArticleModel { @@ -151,70 +217,78 @@ The `Summary` class is defined in `summary.dart`. If you're unfamiliar with JSON parsing, check out the [Getting started with Dart][] tutorial. +`Summary` 类定义在 `summary.dart` 中。 +如果你不熟悉 JSON 解析, +请查看 [Getting started with Dart][] 教程。 + [Wikipedia API]: https://en.wikipedia.org/api/rest_v1/ [JSON]: {{site.dart-site}}/tutorial/json [Getting started with Dart]: {{site.dart-site}}/tutorial ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你已完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Understood the MVVM architecture pattern + - title: 理解了 MVVM 架构模式 icon: layers details: >- - MVVM separates your app into Model (data operations), - View (user interface), and ViewModel (state management). - This separation of concerns makes your code more - testable, reusable, and easier to maintain. - - title: Built an HTTP request to fetch Wikipedia data + MVVM 将应用分为 Model(数据操作)、 + View(用户界面)和 ViewModel(状态管理)。 + 这种关注点分离使代码更 + 易测试、复用和维护。 + - title: 构建了获取 Wikipedia 数据的 HTTP 请求 icon: cloud_download details: >- - You created an `ArticleModel` class with a method that - uses `async` and `await` to fetch data from the Wikipedia API. - To safely build the URLs for the requests, - you used the `Uri.https` constructor which - handles encoding and special characters for you. - - title: Handled errors and parsed JSON responses + 你创建了 `ArticleModel` 类,其中包含使用 + `async` 和 `await` 从 Wikipedia API 获取数据的方法。 + 为安全构建请求 URL, + 你使用了 `Uri.https` 构造函数,它会 + 为你处理编码和特殊字符。 + - title: 处理了错误并解析了 JSON 响应 icon: data_object details: >- - You checked the HTTP status code to detect errors and - used `jsonDecode` to parse the response body. - Then to convert the raw JSON into a typed Dart object, - you used the `Summary.fromJson` named constructor. + 你检查了 HTTP 状态码以检测错误,并 + 使用 `jsonDecode` 解析响应体。 + 然后将原始 JSON 转换为类型化的 Dart 对象时, + 你使用了 `Summary.fromJson` 命名构造函数。 ### Test yourself - -- question: "What do the `async` and `await` keywords do in Dart?" +### 自测 + + +- question: "Dart 中 `async` 和 `await` 关键字的作用是什么?" options: - - text: They make code run on a separate thread. + - text: 它们让代码在单独线程上运行。 correct: false - explanation: Dart is single-threaded; async/await handles asynchronous operations without threads. - - text: They mark a function as asynchronous and pause execution until a Future completes. + explanation: Dart 是单线程的;async/await 在不使用线程的情况下处理异步操作。 + - text: 它们将函数标记为异步,并暂停执行直到 Future 完成。 correct: true - explanation: "The `async` keyword marks a function as asynchronous, and `await` pauses execution until the Future resolves." - - text: They automatically cache the results of function calls. + explanation: "`async` 关键字将函数标记为异步,`await` 会暂停执行直到 Future 完成。" + - text: 它们自动缓存函数调用的结果。 correct: false - explanation: Caching requires separate implementation; async/await is for handling asynchronous operations. - - text: They convert synchronous code to run in the background. + explanation: 缓存需要单独实现;async/await 用于处理异步操作。 + - text: 它们将同步代码转换为在后台运行。 correct: false - explanation: They don't move code to the background; they manage asynchronous execution flow. -- question: "Why is `Uri.https` preferred over string concatenation when building URLs in Dart?" + explanation: 它们不会把代码移到后台;它们管理异步执行流程。 +- question: "在 Dart 中构建 URL 时,为什么更推荐使用 `Uri.https` 而不是字符串拼接?" options: - - text: It makes the code shorter. + - text: 它让代码更短。 correct: false - explanation: Code length isn't the main benefit; proper encoding is. - - text: It safely handles encoding and formatting, especially for special characters and query parameters. + explanation: 代码长度不是主要好处;正确编码才是。 + - text: 它安全处理编码和格式,尤其适用于特殊字符和查询参数。 correct: true - explanation: Uri.https properly encodes special characters and formats URLs, preventing common errors. - - text: It's required by the http package. + explanation: Uri.https 正确编码特殊字符并格式化 URL,避免常见错误。 + - text: http package 要求必须使用它。 correct: false - explanation: You can use strings, but Uri.https is safer and more reliable. - - text: It automatically validates that the URL exists. + explanation: 可以使用字符串,但 Uri.https 更安全可靠。 + - text: 它会自动验证 URL 是否存在。 correct: false - explanation: Uri.https builds the URL; it doesn't check if the endpoint exists. + explanation: Uri.https 构建 URL;不会检查端点是否存在。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md b/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md index 0be5d6f84e..f7b64aac3c 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md +++ b/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md @@ -1,7 +1,10 @@ --- -title: Simple animations -description: Learn the simplest way to implement animations in Flutter. +# title: Simple animations +title: 简单动画 +# description: Learn the simplest way to implement animations in Flutter. +description: 学习在 Flutter 中实现动画的最简单方式。 layout: tutorial +ai-translated: true --- Flutter provides a rich set of animation APIs, and the simplest way to @@ -10,14 +13,20 @@ start using them is with **implicit animations**. automatically animate changes to their properties without you needing to manage any intermediate behavior. +Flutter 提供丰富的动画 API,开始使用它们的 +最简单方式是 **隐式动画**(implicit animations)。 +「隐式动画」指一类 widget, +它们会自动为属性变化添加动画,而你 +无需管理任何中间行为。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Discover implicit animations in Flutter + - title: 了解 Flutter 中的隐式动画 icon: auto_awesome - - title: Animate property changes with AnimatedContainer + - title: 使用 AnimatedContainer 为属性变化添加动画 icon: animation - - title: Customize timing with duration and curves + - title: 用 duration 和 curve 自定义时序 icon: timeline @@ -26,19 +35,33 @@ versatile implicit animation widgets: [`AnimatedContainer`][]. With just two additional lines of code, the background color of each `Tile` animates to a new color in about half a second. +在本课中,你将了解最常见、 +最灵活的隐式动画 widget 之一:[`AnimatedContainer`][]。 +只需额外两行代码,每个 `Tile` 的背景色 +会在大约半秒内动画过渡到新颜色。 + [`AnimatedContainer`]: {{site.api}}/flutter/widgets/AnimatedContainer-class.html --- ### Convert `Container` to `AnimatedContainer` +### 将 `Container` 转换为 `AnimatedContainer` + Currently, the `Tile.build` method returns a `Container` to display a letter. When the `hitType` changes, like from `HitType.none` to `HitType.hit`, the background color of the tile changes instantly. For example, from white to green in the case of `HitType.none` to `HitType.hit`. +目前,`Tile.build` 方法返回 `Container` 来显示字母。 +当 `hitType` 变化时,例如从 `HitType.none` 变为 `HitType.hit`, +方块背景色会瞬间改变。 +例如,在 `HitType.none` 到 `HitType.hit` 的情况下从白色变为绿色。 + For reference, here's the current implementation of the `Tile` widget: +作为参考,这是 `Tile` widget 的当前实现: + ```dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); @@ -74,6 +97,9 @@ class Tile extends StatelessWidget { To make the color change animate smoothly, replace the `Container` widget with an `AnimatedContainer`. +要使颜色变化平滑动画, +将 `Container` widget 替换为 `AnimatedContainer`。 + An `AnimatedContainer` is like a `Container`, but it automatically animates changes to its properties over a specified `duration`. When properties such as @@ -81,8 +107,16 @@ When properties such as `AnimatedContainer` interpolates between the old and new values, creating a smooth transition. +`AnimatedContainer` 类似 `Container`,但会 +在指定的 `duration` 内自动为属性变化添加动画。 +当 `color`、`height`、`width`、`decoration` 或 `alignment` 等属性变化时, +`AnimatedContainer` 会在旧值和新值之间插值, +产生平滑过渡。 + Modify your `Tile` widget as follows: +按如下方式修改 `Tile` widget: + ```dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); @@ -122,32 +156,60 @@ In this example, passing `Duration(milliseconds: 500)` means the color transition will take half a second. You can also specify seconds, minutes, and many other units of time. +**`duration`** 是必填属性,用于 +指定动画应持续多久。 +在本例中,传入 `Duration(milliseconds: 500)` 表示 +颜色过渡将耗时半秒。 +你也可以指定秒、分钟以及许多其他时间单位。 + Now, when the `hitType` changes and the `Tile` widget rebuilds (because `setState` was called in `GamePage`), the color of the tile smoothly animates from its old color to the new one over the specified duration. +现在,当 `hitType` 变化且 `Tile` widget 重建时 +(因为在 `GamePage` 中调用了 `setState`), +方块颜色会在指定的 duration 内从其旧颜色 +平滑动画过渡到新颜色。 + ### Adjust the animation curve +### 调整动画曲线 + To add a bit of customization to an implicit animation, you can pass it a different [`Curve`][]. Different curves change the speed of the animation at different points throughout the animation. +要为隐式动画增加一点自定义, +可以传入不同的 [`Curve`][]。 +不同曲线会在动画过程中的不同点 +改变动画速度。 + For example, the default curve for Flutter animations is `Curves.linear`. This gif shows how the animation curve behaves: +例如,Flutter 动画的默认曲线是 `Curves.linear`。此 gif 展示该动画曲线的表现: + A gif that shows a linear curve. +(上图 alt:展示线性曲线的 gif。) + Compare that to `Curve.bounceIn`, another common curve: +与之对比,`Curve.bounceIn` 是另一种常见曲线: + A gif that shows a bounce-in curve +(上图 alt:展示弹入曲线的 gif。) + To change the `Curve` of this animation, update the code to the following: +要更改此动画的 `Curve`,将代码更新为以下内容: + ```dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); @@ -185,80 +247,94 @@ class Tile extends StatelessWidget { There are many different curves provided by the Flutter SDK, so feel free to try them out by passing different types to the `curve` parameter. +Flutter SDK 提供了许多不同曲线,因此 +欢迎通过向 `curve` 参数传入不同类型来尝试。 + Implicit animations like `AnimatedContainer` are powerful because you just tell the widget what the new state should be, and it handles the "how" of the animation. +像 `AnimatedContainer` 这样的隐式动画很强大,因为 +你只需告诉 widget 新状态应该是什么, +它会处理动画的「如何」实现。 + For complex, custom animations, you can write your own animated widgets. If you're curious, try it out in the [animations tutorial][]. +对于复杂的自定义动画,你可以编写自己的动画 widget。 +如果你感兴趣,可在[动画教程][animations tutorial]中尝试。 + [`Curve`]: {{site.api}}/flutter/animation/Curves-class.html [animations tutorial]: /ui/animations/tutorial ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你已完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Discovered implicit animations + - title: 了解了隐式动画 icon: auto_awesome details: >- - Implicit animations are widgets that automatically - animate changes to their properties. - You specify the new state, and the widget handles - the animation for you without requiring manual animation management. - - title: Animated the tiles with AnimatedContainer + 隐式动画是会自动为 + 属性变化添加动画的 widget。 + 你指定新状态,widget 会为你处理 + 动画,无需手动管理动画。 + - title: 使用 AnimatedContainer 为方块添加动画 icon: animation details: >- - By replacing `Container` with `AnimatedContainer` and adding a `duration`, - your tiles now smoothly transition between colors. - With just two lines of code, you added professional polish to your app! - - title: Customized timing with duration and curves + 通过将 `Container` 替换为 `AnimatedContainer` 并添加 `duration`, + 你的方块现在会在颜色之间平滑过渡。 + 仅用两行代码,就为应用增添了专业质感! + - title: 用 duration 和 curve 自定义时序 icon: timeline details: >- - The `duration` property controls how long the animation takes, - while `curve` changes the animation's feel. - You tried `Curves.decelerate`, but you can also try other values - like `Curves.easeIn`, `Curves.bounceOut`, or `Curves.elasticIn`. - - title: Completed the Birdle game + `duration` 属性控制动画持续多久, + 而 `curve` 改变动画的感觉。 + 你尝试了 `Curves.decelerate`,也可以尝试其他值, + 例如 `Curves.easeIn`、`Curves.bounceOut` 或 `Curves.elasticIn`。 + - title: 完成了 Birdle 游戏 icon: celebration details: >- - You've built a complete Wordle-style game with custom widgets, - dynamic layouts, user input, state management, and smooth animations. - You now have the foundational skills to build your own Flutter apps! + 你已构建完整的 Wordle 风格游戏,包含自定义 widget、 + 动态布局、用户输入、状态管理和流畅动画。 + 你现在具备构建自己的 Flutter 应用的基础技能! ### Test yourself - -- question: What widget can you use to automatically animate changes to properties like color, size, and decoration? +### 自测 + + +- question: 你可以使用哪个 widget 自动为颜色、尺寸和 decoration 等属性变化添加动画? options: - text: Container correct: false - explanation: Container doesn't animate; property changes happen instantly. + explanation: Container 不会添加动画;属性变化会瞬间发生。 - text: AnimatedContainer correct: true - explanation: AnimatedContainer automatically animates changes to its properties over the specified duration. + explanation: AnimatedContainer 会在指定的 duration 内自动为其属性变化添加动画。 - text: AnimationController correct: false - explanation: AnimationController is for explicit animations; AnimatedContainer is simpler for basic animations. + explanation: AnimationController 用于显式动画;AnimatedContainer 更适合基础动画且更简单。 - text: TransitionContainer correct: false - explanation: There is no TransitionContainer widget; use AnimatedContainer for implicit animations. -- question: "What does the `duration` property control in an AnimatedContainer?" + explanation: 没有 TransitionContainer widget;隐式动画请使用 AnimatedContainer。 +- question: "AnimatedContainer 中的 `duration` 属性控制什么?" options: - - text: How long the widget stays on screen before disappearing. + - text: widget 在消失前停留在屏幕上的时长。 correct: false - explanation: Duration controls animation time, not widget visibility. - - text: How long the animation takes to transition from the old value to the new value. + explanation: duration 控制动画时长,而非 widget 可见性。 + - text: 动画从旧值过渡到新值所需的时间。 correct: true - explanation: The duration specifies the time over which the property change is animated. - - text: The delay before the animation starts. + explanation: duration 指定属性变化被动画化的时间长度。 + - text: 动画开始前的延迟。 correct: false - explanation: Duration is about animation length; delays require separate configuration. - - text: How many times the animation repeats. + explanation: duration 关乎动画长度;延迟需要单独配置。 + - text: 动画重复的次数。 correct: false - explanation: Implicit animations run once per state change; repetition requires explicit animation controllers. + explanation: 隐式动画在每次状态变化时运行一次;重复需要显式动画控制器。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/index.md b/sites/docs/src/content/learn/pathway/tutorial/index.md index 7fb928bd3b..1ce72a4b78 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/index.md +++ b/sites/docs/src/content/learn/pathway/tutorial/index.md @@ -1,10 +1,15 @@ --- -title: Flutter tutorial -shortTitle: Tutorial +# title: Flutter tutorial +title: Flutter 教程 +# shortTitle: Tutorial +shortTitle: 教程 +# description: >- +# Learn how to use Flutter to build pixel-perfect applications from scratch that +# run on mobile, desktop, and web. description: >- - Learn how to use Flutter to build pixel-perfect applications from scratch that - run on mobile, desktop, and web. + 学习如何使用 Flutter 从零构建可在移动端、桌面端和 Web 上运行的像素级完美应用。 layout: tutorial +ai-translated: true ---
@@ -13,18 +18,31 @@ layout: tutorial Welcome to the Flutter tutorial! This tutorial teaches you how to build applications from scratch that run on mobile, desktop, and web. +欢迎学习 Flutter 教程!本教程将教你从零构建可在移动端、桌面端和 Web 上运行的应用。 + You'll start from the very beginning: creating a blank Flutter application. By the end, you'll have built a handful of small apps that demonstrate the critical features of Flutter development (and more!) +你将从最基础的内容开始:创建一个空白的 Flutter 应用。 +学完之后,你将构建若干小型应用,它们演示了 Flutter 开发的关键特性(还有更多内容!) + ### Before you begin -This tutorial is the third step in the Flutter learning pathway, and therefor assumes that you have: +### 开始之前 + +This tutorial is the third step in the Flutter learning pathway, and therefore assumes that you have: 1. A Flutter environment set up 2. Basic understanding of the Dart programming language +本教程是 Flutter 学习路径的第三步,因此假定你已经具备: +1. 已配置好的 Flutter 环境 +2. 对 Dart 编程语言的基本了解 + If either of those aren't true, please start at the [Learning pathway page](/learn/pathway). +如果以上任一项尚未满足,请从[学习路径页面](/learn/pathway)开始。 +
@@ -33,6 +51,10 @@ If either of those aren't true, please start at the [Learning pathway page](/lea ## Contents +## 目录 + Start learning + +开始学习 diff --git a/sites/docs/src/content/learn/pathway/tutorial/layout.md b/sites/docs/src/content/learn/pathway/tutorial/layout.md index 447967041e..c5625af887 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/layout.md +++ b/sites/docs/src/content/learn/pathway/tutorial/layout.md @@ -1,23 +1,28 @@ --- -title: Layout -description: Learn about common layout widgets in Flutter. +# title: Layout +title: 布局 +# description: Learn about common layout widgets in Flutter. +description: 了解 Flutter 中常见的布局 widget。 layout: tutorial +ai-translated: true --- Learn how to build layouts with common widgets like Scaffold, AppBar, Column, and Row. +学习如何使用 Scaffold、AppBar、Column 和 Row 等常见 widget 构建布局。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Structure an app with Scaffold and AppBar + - title: 使用 Scaffold 和 AppBar 构建应用结构 icon: web_asset - - title: Arrange widgets using Column and Row + - title: 使用 Column 和 Row 排列 widget icon: view_column - - title: Generate widgets dynamically from data + - title: 根据数据动态生成 widget icon: repeat - - title: Build a grid layout for the game board + - title: 为游戏棋盘构建网格布局 icon: grid_view @@ -25,9 +30,14 @@ items: ### Introduction +### 简介 + Given that Flutter is a UI toolkit, you'll spend a lot of time creating layouts with Flutter widgets. +鉴于 Flutter 是 UI 工具包, +你将花费大量时间使用 Flutter widget 创建布局。 + In this section, you'll learn how to build layouts with some of the most common layout widgets. This includes high-level widgets like @@ -35,6 +45,12 @@ This includes high-level widgets like as well as lower-level widgets like [`Column`][] or [`Row`][] that lay out widgets vertically or horizontally. +在本节中,你将学习如何使用 +一些最常见的布局 widget 构建布局。 +这包括用于布局屏幕结构的高级 widget,例如 +[`Scaffold`][] 和 [`AppBar`][], +以及用于垂直或水平布局 widget 的较低级 widget,例如 [`Column`][] 或 [`Row`][]。 + [`Scaffold`]: {{site.api}}/flutter/material/Scaffold-class.html [`AppBar`]: {{site.api}}/flutter/material/AppBar-class.html [`Column`]: {{site.api}}/flutter/widgets/Column-class.html @@ -42,31 +58,55 @@ lay out widgets vertically or horizontally. ### `Scaffold` and `AppBar` +### `Scaffold` 和 `AppBar` + Mobile applications often have a bar at the top called an "app bar" that can display a title, navigation controls, and/or actions. +移动应用通常在顶部有一个称为「app bar」的栏,可以 +显示标题、导航控件和/或操作。 + A screenshot of a simple application with a bar across the top that has a title and settings button. The simplest way to add an app bar to your app is by using two widgets: `Scaffold` and `AppBar`. +为应用添加 app bar 的最简单方式是使用两个 widget: +`Scaffold` 和 `AppBar`。 + `Scaffold` is a convenience widget that provides a Material-style page layout, making it simple to add an app bar, drawer, navigation bar, and more to a page of your app. `AppBar` is, of course, the app bar. +`Scaffold` 是一个便捷 widget,提供 Material 风格的页面布局, +可轻松为应用页面添加 app bar、抽屉、导航栏等。`AppBar` 当然就是 app bar。 + The code generated from the `flutter create --empty` command already contains an `AppBar` widget and a `Scaffold` widget. The following code updates it to use an additional layout widget: [`Align`][]. This positions the title to the left, which would be centered by default. The `Text` widget contains the title itself. +`flutter create --empty` 命令生成的代码已 +包含 `AppBar` widget 和 `Scaffold` widget。 +以下代码将其更新为使用额外的布局 widget:[`Align`][]。 +这会将标题定位到左侧,默认情况下标题会居中。 +`Text` widget 本身包含标题。 + Modify the `Scaffold` within your `MainApp` widget's `build` method. +修改 `MainApp` widget 的 `build` 方法中的 `Scaffold`。 + Passing an enum or static property directly (like `Alignment.centerLeft`) can also be shortened using [Dart's dot shorthands][] syntax, which you can read more about on both the official Dart documentation and the [Flutter shorthands overview][]. +直接传入枚举或静态属性(如 `Alignment.centerLeft`) +也可以使用 [Dart 的点简写][Dart's dot shorthands] 语法缩短, +你可以在官方 Dart 文档和 +[Flutter 简写概览][Flutter shorthands overview] 中了解更多。 + [Dart's dot shorthands]: https://dart.dev/language/dot-shorthands [Flutter shorthands overview]: /ui/dot-shorthands @@ -95,19 +135,31 @@ class MainApp extends StatelessWidget { #### An updated widget tree +#### 更新后的 widget 树 + Considering your app's widget tree gets more important as your app grows. At this point, there's a "branch" in the widget tree for the first time, and it now looks like the following figure: +随着应用增长,关注应用的 widget 树变得越来越重要。 +此时,widget 树中首次出现「分支」, +现在它看起来像下图: + A screenshot that resembles the popular game Wordle. ### Create a widget for the game page layout +### 为游戏页面布局创建 widget + Add the following code for a new widget, called `GamePage`, to your `main.dart` file. This widget will eventually display the UI elements needed for the game itself. +将以下新 widget(名为 `GamePage`)的代码 +添加到你的 `main.dart` 文件中。 +此 widget 最终将显示游戏本身所需的 UI 元素。 + ```dart title="lib/main.dart" class GamePage extends StatelessWidget { @@ -127,6 +179,9 @@ class GamePage extends StatelessWidget { Then update your `MainPage` widget to create and display a `GamePage` widget instead of "Hello World!". +然后更新 `MainPage` widget,创建并 +显示 `GamePage` widget,而不是「Hello World!」。 + ```dart highlightLines=14 class MainApp extends StatelessWidget { @@ -151,8 +206,12 @@ class MainApp extends StatelessWidget { ### Arrange widgets with `Column` and `Row` +### 使用 `Column` 和 `Row` 排列 widget + The `GamePage` layout contains the grid of tiles that display a user's guesses. +`GamePage` 布局包含显示用户猜测的方块网格。 + A screenshot that resembles the popular game Wordle. There are a number of ways you can build this layout. @@ -162,9 +221,19 @@ with five rows total. So you'll need a single `Column` with five `Row` widgets as children, where each row contains five children. +有多种方式可以构建此布局。 +最简单的是使用 `Column` 和 `Row` widget。 +每行包含五个方块,代表猜测中的五个字母, +共五行。 +因此你需要一个 `Column`,其 children 为五个 `Row` widget, +每行包含五个 children。 + To get started, replace the `Container` in `GamePage.build` with a `Padding` widget with a `Column` widget as its child: +首先,将 `GamePage.build` 中的 `Container` 替换为 +以 `Column` widget 为 child 的 `Padding` widget: + ```dart class GamePage extends StatelessWidget { @@ -189,14 +258,26 @@ class GamePage extends StatelessWidget { The `spacing` property puts five pixels between each element on the main axis. +`spacing` 属性会在主轴上的每个元素之间放置 5 像素间距。 + Within `Column.children`, for each element in the `_game.guesses` list, add a `Row` widget as a child. +在 `Column.children` 中,对 `_game.guesses` 列表中的每个元素, +添加一个 `Row` widget 作为 child。 + :::note + This `guesses` list is a **fixed-size** list, starting with five elements, one for each *potential* guess. The list will always contain exactly five elements, and therefore will always render five rows. + +此 `guesses` 列表是 **固定大小** 的列表,以五个 +元素开始,每个元素对应一次*潜在*猜测。 +列表始终恰好包含五个元素, +因此始终会渲染五行。 + ::: @@ -234,6 +315,13 @@ This syntactic sugar makes it easier for you to work with collections of widgets, providing a declarative alternative to the following: +`children` 列表中的 `for` 循环称为 [collection for element][], +这是一种 Dart 语法,让你在运行时构建集合时 +迭代地向集合添加项。 +这种语法糖让你更容易 +处理 widget 集合, +为以下内容提供声明式替代方案: + ```dart [..._game.guesses.map((guess) => Row(/* ... */))], ``` @@ -241,13 +329,21 @@ providing a declarative alternative to the following: In this case, it adds five `Row` widgets to the column, one for each guess on the `Game` object. +此处,它向 column 添加五个 `Row` widget, +每个对应 `Game` 对象上的一次猜测。 + [collection for element]: {{site.dart-site}}/language/collections#for-element #### An updated widget tree +#### 更新后的 widget 树 + The widget tree for this app has expanded significantly in this lesson. Now, it looks more like the following (abridged) figure: +本课中,此应用的 widget 树已显著扩展。 +现在,它更像下面的(节选)图: + A diagram showing a tree like structure with a node for each widget in the app. :::note Challenge @@ -256,10 +352,18 @@ Add a `Tile` to each row for each letter allowed in the guess. Each element in `guess` is a [record][] with the type `({String char, HitType type})`. +为每次猜测中允许的每个字母,向每行添加一个 `Tile`。 +`guess` 中的每个元素都是类型为 +`({String char, HitType type})` 的 [record][]。 + Use a nested loop to iterate over the letters in each guess. +使用嵌套循环遍历每次猜测中的字母。 + **Solution:** +**解答:** + ```dart title="lib/main.dart" collapsed class GamePage extends StatelessWidget { @@ -293,73 +397,79 @@ class GamePage extends StatelessWidget { When you reload your app, you should see a 5x5 grid of white squares. +热重载应用后,你应看到 5x5 的白色方块网格。 + A screenshot that resembles the popular game Wordle. [record]: {{site.dart-site}}/language/records ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Structured your app with Scaffold and AppBar + - title: 使用 Scaffold 和 AppBar 构建应用结构 icon: web_asset details: >- - You used `Scaffold` to provide a Material-style page layout and - `AppBar` to add a title bar at the top of your app. - These high-level widgets give your app a standard, yet polished structure. - - title: Arranged widgets using Column and Row + 你使用 `Scaffold` 提供 Material 风格的页面布局,使用 + `AppBar` 在应用顶部添加标题栏。 + 这些高级 widget 为你的应用提供标准且精致的结构。 + - title: 使用 Column 和 Row 排列 widget icon: view_column details: >- - `Column` arranges widgets vertically and `Row` arranges them horizontally. - These are fundamental layout widgets you'll use constantly in Flutter. - The `spacing` property adds consistent gaps between children. - - title: Generated widgets dynamically from data + `Column` 垂直排列 widget,`Row` 水平排列 widget。 + 这些是你在 Flutter 中会经常使用的基本布局 widget。 + `spacing` 属性在 children 之间添加一致的间距。 + - title: 根据数据动态生成 widget icon: repeat details: >- - You used a collection for element to build widgets from a list. - This declarative approach lets you build user interfaces that - automatically and visually reflect your data, - a pattern central to Flutter development. - - title: Built the game board grid + 你使用 collection for element 从列表构建 widget。 + 这种声明式方法让你构建的用户界面 + 能自动并在视觉上反映你的数据, + 这是 Flutter 开发的核心模式。 + - title: 构建了游戏棋盘网格 icon: grid_view details: >- - By nesting `Row` widgets inside a `Column` and using nested loops, - you created a 5x5 grid of `Tile` widgets. - Your app now displays the complete game board layout! + 通过在 `Column` 内嵌套 `Row` widget 并使用嵌套循环, + 你创建了 5x5 的 `Tile` widget 网格。 + 你的应用现在显示完整的游戏棋盘布局! ### Test yourself - -- question: What is the primary difference between a Column and a Row widget? +### 自测 + + +- question: Column 和 Row widget 的主要区别是什么? options: - - text: Column is for scrolling content; Row is for static content. + - text: Column 用于滚动内容;Row 用于静态内容。 correct: false - explanation: Both Column and Row are for layout, not scrolling. Use ListView or SingleChildScrollView for scrolling. - - text: Column arranges children vertically; Row arranges children horizontally. + explanation: Column 和 Row 都用于布局,而非滚动。滚动请使用 ListView 或 SingleChildScrollView。 + - text: Column 垂直排列 children;Row 水平排列 children。 correct: true - explanation: Column lays out its children along the vertical axis, while Row uses the horizontal axis. - - text: Column can have unlimited children; Row is limited to two. + explanation: Column 沿垂直轴布局其 children,Row 使用水平轴。 + - text: Column 可以有无限个 children;Row 限制为两个。 correct: false - explanation: Both widgets can have any number of children. - - text: Column requires a Scaffold parent; Row does not. + explanation: 两种 widget 都可以有任意数量的 children。 + - text: Column 需要 Scaffold 父级;Row 不需要。 correct: false - explanation: Neither widget requires a Scaffold as a parent. -- question: What does the Scaffold widget provide in a Flutter app? + explanation: 两种 widget 都不需要 Scaffold 作为父级。 +- question: Scaffold widget 在 Flutter 应用中提供什么? options: - - text: Only a background color for the page. + - text: 仅为页面提供背景色。 correct: false - explanation: Scaffold provides much more, including structure for app bars, drawers, and more. - - text: A Material-style page layout with slots for app bar, body, drawer, and more. + explanation: Scaffold 提供的远不止这些,包括 app bar、抽屉等的结构。 + - text: Material 风格的页面布局,带有 app bar、body、drawer 等插槽。 correct: true - explanation: Scaffold is a convenience widget that provides a standard Material page structure. - - text: A way to navigate between different pages. + explanation: Scaffold 是提供标准 Material 页面结构的便捷 widget。 + - text: 在不同页面之间导航的方式。 correct: false - explanation: Navigation is handled by Navigator, not Scaffold. - - text: Automatic state management for the page. + explanation: 导航由 Navigator 处理,而非 Scaffold。 + - text: 页面的自动状态管理。 correct: false - explanation: Scaffold doesn't manage state; you use StatefulWidget or state management solutions for that. + explanation: Scaffold 不管理状态;你需要使用 StatefulWidget 或状态管理方案。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md b/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md index 86c980e24e..42202eda2e 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md +++ b/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md @@ -1,7 +1,10 @@ --- -title: Rebuild UI when state changes -description: Instructions on how to manage state with ChangeNotifiers. +# title: Rebuild UI when state changes +title: 状态变化时重建 UI +# description: Instructions on how to manage state with ChangeNotifiers. +description: 如何使用 ChangeNotifier 管理状态的说明。 layout: tutorial +ai-translated: true --- @@ -9,14 +12,17 @@ layout: tutorial Learn to use ListenableBuilder to automatically rebuild UI and handle all possible states with switch expressions. +学习使用 ListenableBuilder 在状态变化时自动重建 UI, +并用 switch 表达式处理所有可能的状态。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Use ListenableBuilder to rebuild UI automatically + - title: 使用 ListenableBuilder 自动重建 UI icon: sync - - title: Handle all possible states with switch expressions + - title: 用 switch 表达式处理所有可能的状态 icon: alt_route - - title: Build the complete View layer with proper styling + - title: 用合适的样式构建完整的 View 层 icon: article @@ -24,6 +30,8 @@ items: ### Introduction +### 简介 + The view layer is your UI, and in Flutter, that refers to your app's widgets. As it pertains to this tutorial, the important part is @@ -32,18 +40,35 @@ wiring up your UI to respond to data changes from the ViewModel. [`ChangeNotifier`][], and automatically rebuilds when it's provided `ChangeNotifier` calls `notifyListeners()`. +View 层就是你的 UI,在 Flutter 中, +这指的是你应用中的 widget。 +就本教程而言,重要的是 +将 UI 与 ViewModel 的数据变化关联起来。 +[`ListenableBuilder`][] 是一个可以「监听」 +[`ChangeNotifier`][] 的 widget,当其 +提供的 `ChangeNotifier` 调用 `notifyListeners()` 时会自动重建。 + [`ListenableBuilder`]: {{site.api}}/flutter/widgets/ListenableBuilder-class.html [`ChangeNotifier`]: {{site.api}}/flutter/foundation/ChangeNotifier-class.html ### Create the article view widget +### 创建文章视图 widget + Create the `ArticleView` widget that manages your page's layout and ViewModel lifecycle. Because it must explicitly initialize data fetching before rendering, implement it as a `StatefulWidget`. +创建 `ArticleView` widget,用于 +管理页面的布局与 ViewModel 生命周期。 +由于必须在渲染前显式初始化数据获取, +请将其实现为 `StatefulWidget`。 + Start by creating the basic stateful structure: +先创建基本的 stateful 结构: + ```dart import 'package:flutter/material.dart'; @@ -70,9 +95,14 @@ class _ArticleViewState extends State { ### Instantiate the article view model +### 实例化文章 ViewModel + Next, initialize your `ArticleViewModel` mapping it to the state's lifecycle. Provide the ViewModel and execute `fetchArticle()` within `initState()`: +接下来,初始化 `ArticleViewModel` 并将其与 state 的生命周期绑定。 +在 `initState()` 中提供 ViewModel 并执行 `fetchArticle()`: + ```dart highlightLines=2-8 class ArticleView extends StatefulWidget { @@ -103,11 +133,18 @@ class _ArticleViewState extends State { ### Update your app to include the article view +### 更新应用以包含文章视图 + Connect everything together by updating your `MainApp` to include your completed `ArticleView`. +通过更新 `MainApp` 以 +包含已完成的 `ArticleView`,将所有部分连接起来。 + Replace your existing `MainApp` with this updated version: +用以下更新版本替换现有的 `MainApp`: + ```dart highlightLines=6 class MainApp extends StatelessWidget { @@ -123,12 +160,21 @@ class MainApp extends StatelessWidget { This change switches from the console-based test to the full UI experience with proper state management. +这一更改从基于控制台的测试切换到 +具备完善状态管理的完整 UI 体验。 + ### Listen for state changes +### 监听状态变化 + Wrap your UI in a [`ListenableBuilder`][] to listen for state changes, and pass it a `ChangeNotifier` object. In this case, the `ArticleViewModel` extends `ChangeNotifier`. +用 [`ListenableBuilder`][] 包裹 UI 以监听状态变化, +并向其传入 `ChangeNotifier` 对象。 +在本例中,`ArticleViewModel` 继承自 `ChangeNotifier`。 + ```dart highlightLines=21-26 class ArticleView extends StatefulWidget { @@ -169,22 +215,47 @@ These widgets are flexible because you can perform operations within the callback, building different widgets based on the state. +`ListenableBuilder` 使用 *builder* 模式, +它需要回调而不是 `child` widget 来 +构建其下方的 widget 树。 +这些 widget 很灵活,因为你可以在 +回调中执行操作, +根据状态构建不同的 widget。 + [`ListenableBuilder`]: {{site.api}}/flutter/widgets/ListenableBuilder-class.html ### Handle possible view model states +### 处理 ViewModel 的可能状态 + Recall the `ArticleViewModel`, which has three properties that the UI is interested in: +回顾 `ArticleViewModel`,它有三个 UI +关心的属性: + - `Summary? summary` + + 文章摘要(`summary`,可选) + - `bool isLoading` + + 是否正在加载(`isLoading`) + - `Exception? error` + 错误信息(`error`,可选) + Depending on the combined state of these properties, the UI can display different widgets. Use Dart's support for [switch expressions][] to handle all possible combinations in a clean, readable way: +根据这些属性的组合状态, +UI 可以显示不同的 widget。 +利用 Dart 对 [switch 表达式][switch expressions] 的支持, +以清晰、可读的方式处理所有可能的组合: + ```dart class ArticleView extends StatefulWidget { @@ -240,16 +311,31 @@ a state changes demands it, but it doesn't manage any state or the process of updating itself. The business logic and rendering are completely separate from each other. +这是声明式、响应式框架(如 Flutter)与 +MVVM 等模式如何协同工作的绝佳示例: +UI 根据状态渲染,并在 +状态变化需要时更新,但 +它不管理任何状态,也不管理自我更新的过程。 +业务逻辑与渲染完全彼此分离。 + [switch expressions]: {{site.dart-site}}/language/branches#switch-expressions ### Complete the UI +### 完成 UI + The only thing remaining is to use the properties and methods provided by the view model to build the UI. +剩下要做的就是用 ViewModel 提供的 +属性和方法来构建 UI。 + Now create a `ArticlePage` widget that displays the actual article content. This reusable widget takes summary data and a callback function: +现在创建 `ArticlePage` widget 以显示实际文章内容。 +这个可复用 widget 接收摘要数据和回调函数: + ```dart class ArticlePage extends StatelessWidget { @@ -273,8 +359,12 @@ class ArticlePage extends StatelessWidget { ### Add a scrollable layout +### 添加可滚动布局 + Replace the placeholder with a scrollable column layout: +用可滚动的 Column 布局替换占位内容: + ```dart highlightLines=13-16 class ArticlePage extends StatelessWidget { @@ -300,8 +390,12 @@ class ArticlePage extends StatelessWidget { ### Add article content and button +### 添加文章内容与按钮 + Complete the layout with an article widget and navigation button: +用文章 widget 和导航按钮完成布局: + ```dart highlightLines=16-20 class ArticlePage extends StatelessWidget { @@ -333,13 +427,22 @@ class ArticlePage extends StatelessWidget { ### Create the `ArticleWidget` +### 创建 `ArticleWidget` + The `ArticleWidget` handles the display of the actual article content with proper styling and conditional rendering. +`ArticleWidget` 负责以合适的样式和条件渲染 +显示实际文章内容。 + #### Set up the basic article structure +#### 搭建基本文章结构 + Start with the widget that accepts a `summary` parameter: +从接受 `summary` 参数的 widget 开始: + ```dart class ArticleWidget extends StatelessWidget { @@ -356,8 +459,12 @@ class ArticleWidget extends StatelessWidget { #### Add padding and column layout +#### 添加内边距与 Column 布局 + Wrap the content in proper padding and layout: +用合适的内边距和布局包裹内容: + ```dart highlightLines=8-14 class ArticleWidget extends StatelessWidget { @@ -380,8 +487,12 @@ class ArticleWidget extends StatelessWidget { #### Add conditional image display +#### 添加条件图片显示 + Add the article image that only shows when available: +添加仅在可用时显示的文章图片: + ```dart highlightLines=13 class ArticleWidget extends StatelessWidget { @@ -407,9 +518,14 @@ class ArticleWidget extends StatelessWidget { #### Complete with styled text content +#### 用带样式的文本内容完成 + Replace the placeholder text with a properly styled title, description, and extract: +用带样式的 +标题、描述和摘录替换占位文本: + ```dart highlightLines=14-25 class ArticleWidget extends StatelessWidget { @@ -446,96 +562,137 @@ class ArticleWidget extends StatelessWidget { This widget demonstrates a few important UI concepts: +该 widget 演示了几个重要的 UI 概念: + - **Conditional rendering**: The `if` statements show content only when available. + + **条件渲染**: + `if` 语句仅在内容可用时显示。 + - **Text styling**: Different text styles create visual hierarchy using Flutter's theme system. + + **文本样式**: + 不同文本样式借助 Flutter 的主题系统建立视觉层次。 + - **Proper spacing**: The `spacing` parameter provides consistent vertical spacing. + + **合适间距**: + `spacing` 参数提供一致的垂直间距。 + - **Overflow handling**: `TextOverflow.ellipsis` prevents text from breaking the layout. + **溢出处理**: + `TextOverflow.ellipsis` 防止文本破坏布局。 + ### Run the complete app +### 运行完整应用 + Hot reload your app one final time. You should now see: +最后一次热重载应用。你现在应能看到: + 1. A loading spinner while the initial article loads. + + 初始文章加载时显示的加载指示器。 + 1. The article's title, description, and summary extract. + + 文章的标题、描述和摘要摘录。 + 1. An image (if the article has one). + + 图片(若文章有图片)。 + 1. A button to load another random article. + 用于加载另一篇随机文章的按钮。 + To see the reactive UI in action, click the **Next random article** button. The app shows a loading state, fetches new data, and updates the display automatically. +要查看响应式 UI 的实际效果, +点击 **Next random article**(下一篇随机文章)按钮。 +应用会显示加载状态、获取新数据,并 +自动更新显示。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你已完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Used ListenableBuilder to rebuild UI automatically + - title: 使用 ListenableBuilder 自动重建 UI icon: sync details: >- - `ListenableBuilder` listens to your ViewModel and automatically rebuilds - its children whenever `notifyListeners()` is called. - In the MVVM pattern, - this is the key connection between your ViewModel and View. - - title: Handled all possible states with switch expressions + `ListenableBuilder` 会监听你的 ViewModel,并在每次调用 + `notifyListeners()` 时自动重建其子 widget。 + 在 MVVM 模式中, + 这是 ViewModel 与 View 之间的关键连接。 + - title: 用 switch 表达式处理所有可能的状态 icon: alt_route details: >- - Using a switch expression, you accounted for - the possible state combinations with an appropriate user interface, - Conditionally displaying a loading spinner, an error message, - or the actual article content. - With this handling, the UI is now more robust and complete. - - title: Built the complete View layer with proper styling + 通过 switch 表达式,你为 + 可能的状态组合提供了合适的用户界面, + 有条件地显示加载指示器、错误消息 + 或实际文章内容。 + 有了这种处理,UI 现在更加健壮和完整。 + - title: 用合适的样式构建完整的 View 层 icon: article details: >- - You created `ArticleView`, `ArticlePage`, and - `ArticleWidget` with conditional rendering, text styling, - proper spacing, and overflow handling. - These are core UI patterns you'll use in every Flutter app. - - title: Completed the MVVM architecture + 你创建了 `ArticleView`、`ArticlePage` 和 + `ArticleWidget`,包含条件渲染、文本样式、 + 合适间距和溢出处理。 + 这些是你将在每个 Flutter 应用中使用的核心 UI 模式。 + - title: 完成了 MVVM 架构 icon: celebration details: >- - You've built a complete app with Model (data operations), - ViewModel (state management), and View (reactive UI) layers. - This separation of concerns helps your code be - more testable, maintainable, and scalable. + 你已构建包含 Model(数据操作)、 + ViewModel(状态管理)和 View(响应式 UI)层的完整应用。 + 这种关注点分离有助于代码 + 更易测试、维护和扩展。 ### Test yourself - -- question: What is the purpose of ListenableBuilder in Flutter? +### 自测 + + +- question: ListenableBuilder 在 Flutter 中的作用是什么? options: - - text: To create animations based on a ChangeNotifier. + - text: 基于 ChangeNotifier 创建动画。 correct: false - explanation: ListenableBuilder rebuilds UI on state changes, not specifically for animations. - - text: "To listen to a ChangeNotifier and automatically rebuild its child widgets when `notifyListeners()` is called." + explanation: ListenableBuilder 在状态变化时重建 UI,并非专门用于动画。 + - text: "监听 ChangeNotifier,并在调用 `notifyListeners()` 时自动重建其子 widget。" correct: true - explanation: ListenableBuilder listens to a Listenable and rebuilds its builder function when notified. - - text: To manually control when widgets should be rebuilt. + explanation: ListenableBuilder 监听 Listenable,并在收到通知时重建其 builder 函数。 + - text: 手动控制何时应重建 widget。 correct: false - explanation: The rebuild is automatic when notifyListeners() is called; you don't control it manually. - - text: To cache widget builds for better performance. + explanation: 调用 notifyListeners() 时重建是自动的;你无需手动控制。 + - text: 缓存 widget 构建以提升性能。 correct: false - explanation: ListenableBuilder is about reactive updates, not caching. -- question: When does ListenableBuilder rebuild its child widgets? + explanation: ListenableBuilder 用于响应式更新,而非缓存。 +- question: ListenableBuilder 何时重建其子 widget? options: - - text: Every time the app's frame refreshes. + - text: 每次应用帧刷新时。 correct: false - explanation: ListenableBuilder only rebuilds when notified, not on every frame. - - text: When the Listenable it's listening to calls notifyListeners(). + explanation: ListenableBuilder 仅在收到通知时重建,而非每一帧。 + - text: 当它监听的 Listenable 调用 notifyListeners() 时。 correct: true - explanation: "ListenableBuilder subscribes to the Listenable and rebuilds its builder function whenever `notifyListeners()` is called." - - text: Only when the widget is first mounted. + explanation: "ListenableBuilder 订阅 Listenable,并在每次调用 `notifyListeners()` 时重建其 builder 函数。" + - text: 仅在 widget 首次挂载时。 correct: false - explanation: "It rebuilds whenever `notifyListeners()` is called, not just on mount." - - text: When the parent widget rebuilds. + explanation: "它在每次调用 `notifyListeners()` 时都会重建,而不仅限于挂载时。" + - text: 当父 widget 重建时。 correct: false - explanation: "ListenableBuilder rebuilds based on the Listenable, not parent rebuilds." + explanation: "ListenableBuilder 根据 Listenable 重建,而非父 widget 重建。" diff --git a/sites/docs/src/content/learn/pathway/tutorial/navigation.md b/sites/docs/src/content/learn/pathway/tutorial/navigation.md index a1c4998b87..0e372c80d4 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/navigation.md +++ b/sites/docs/src/content/learn/pathway/tutorial/navigation.md @@ -1,20 +1,26 @@ --- -title: Stack-based navigation -description: Learn how to navigate from one page to another in a Flutter app. +# title: Stack-based navigation +title: 基于栈的导航 +# description: Learn how to navigate from one page to another in a Flutter app. +description: 学习如何在 Flutter 应用中从一个页面导航到另一个页面。 layout: tutorial +ai-translated: true --- Learn to navigate between screens with Navigator.push and implement adaptive navigation patterns for different screen sizes. +学习使用 Navigator.push 在屏幕之间导航, +并为不同屏幕尺寸实现自适应导航模式。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Navigate between screens with Navigator.push + - title: 使用 Navigator.push 在屏幕之间导航 icon: open_in_new - - title: Use CupertinoPageRoute for iOS-style transitions + - title: 使用 CupertinoPageRoute 实现 iOS 风格转场 icon: swipe_right - - title: Create different navigation patterns for each screen size + - title: 为每种屏幕尺寸创建不同的导航模式 icon: devices @@ -22,15 +28,26 @@ items: ### Introduction +### 简介 + Now that you understand slivers and scrolling, you can implement navigation between screens. In this lesson, you'll update the small-screen view such that when a contact group is tapped, it navigates to the contact list for that group. +既然你已理解 sliver 与滚动, +就可以实现屏幕之间的导航。 +在本课中, +你将更新小屏视图,使得点击联系人分组时, +会导航到该分组的联系人列表。 + First, revert changes in the adaptive layout widget so that it displays the `ContactGroupsPage` by default on small screens. +首先,还原自适应布局 widget 中的更改,使其 +在小屏上默认显示 `ContactGroupsPage`。 + ```dart class _AdaptiveLayoutState extends State { @@ -62,14 +79,24 @@ class _AdaptiveLayoutState extends State { ### Add navigation to contact groups +### 为联系人分组添加导航 + The `ContactGroupsPage` already uses a `_ContactGroupsView` and provides it with a callback. That callback needs to be updated to navigate when a group is tapped, rather than printing the group to the console. +`ContactGroupsPage` 已使用 `_ContactGroupsView` +并为其提供了回调。 +需要更新该回调,使得点击分组时进行导航, +而不是将分组打印到控制台。 + Ensure that the `onListSelected` callback and imports in `lib/screens/contact_groups.dart` are implemented as follows: +确保 `lib/screens/contact_groups.dart` 中的 +`onListSelected` 回调和 import 实现如下: + ```dart title="lib/screens/contact_groups.dart" import 'contacts.dart'; @@ -93,31 +120,63 @@ class ContactGroupsPage extends StatelessWidget { This small code block contains the most important new information on this page. +这一小段代码包含本页最重要的新信息。 + `Navigator.of(context)` retrieves the nearest `Navigator` widget from the widget tree. The `push` method adds a new route to the navigator's stack, and displays the widget returned from the `builder` property. +`Navigator.of(context)` 从 widget 树中获取 +最近的 `Navigator` widget。 +`push` 方法向 Navigator 的栈添加新路由,并 +显示 `builder` 属性返回的 widget。 + This is the most basic implementation of using stack-based navigation, where new screens are pushed on top of the current screen. To navigate back to the previous screen, you'd use the `Navigator.pop` method. +这是使用基于栈的导航的最基本实现, +新屏幕被 push 到当前屏幕之上。 +要返回上一屏幕,可使用 `Navigator.pop` 方法。 + `CupertinoPageRoute` creates iOS-style page transitions with the following features: +`CupertinoPageRoute` 创建 iOS 风格的页面转场,具有以下特性: + - A slide-in animation from the right. + + 从右侧滑入的动画。 + - Automatic back button support. + + 自动支持返回按钮。 + - Proper title handling. + + 正确的标题处理。 + - Swipe-to-go-back gesture support. + 支持滑动返回手势。 + ### Create the sidebar component for large screens +### 为大屏创建侧边栏组件 + For large screens, you need a sidebar that doesn't navigate but instead updates the main content area. Thanks to the refactoring in the previous step, creating this component is more straightforward. Add this widget to the bottom of `lib/screens/contact_groups.dart`: +对于大屏,你需要一个不进行导航、 +而是更新主内容区域的侧边栏。 +得益于上一步的重构, +创建该组件更加直接。 +将此 widget 添加到 `lib/screens/contact_groups.dart` 底部: + ```dart /// A sidebar component for selecting contact groups on large screens. @@ -147,13 +206,26 @@ it calls `onListSelected` with the ID of the tapped list. It also passes the `selectedListId` to `_ContactGroupsView` so that the selected item can be highlighted. +该侧边栏组件复用 `_ContactGroupsView` 并 +提供不同的回调。它不进行导航, +而是用被点击列表的 ID 调用 `onListSelected`。 +它还将 `selectedListId` 传给 `_ContactGroupsView`,以便 +高亮显示选中项。 + ### Create the detail view for large screens +### 为大屏创建详情视图 + For the large screen layout, you need a detail view that doesn't show navigation controls. Just like the sidebar, this can be recreated by reusing the `_ContactListView`. Add this widget to the bottom of your `contacts.dart` file: +对于大屏布局,你需要一个不显示 +导航控件的详情视图。与侧边栏一样, +可以通过复用 `_ContactListView` 来重新创建。 +将此 widget 添加到你的 `contacts.dart` 文件底部: + ```dart class ContactListDetail extends StatelessWidget { @@ -172,12 +244,22 @@ The detail view reuses `_ContactListView` and sets the `automaticallyImplyLeading` parameter to `false` to hide the back button, as navigation is handled by the sidebar. +详情视图复用 `_ContactListView`,并将 +`automaticallyImplyLeading` 参数设为 `false` 以 +隐藏返回按钮,因为导航由侧边栏处理。 + ### Connect the sidebar to the adaptive layout +### 将侧边栏连接到自适应布局 + Now, connect the sidebar to your adaptive layout. Update your `adaptive_layout.dart` file to import the necessary files and update the large screen layout: +现在,将侧边栏连接到你的自适应布局。 +更新 `adaptive_layout.dart` 文件以导入必要文件并 +更新大屏布局: + ```dart import 'package:flutter/cupertino.dart'; @@ -188,6 +270,8 @@ import 'contacts.dart'; Then update the `_buildLargeScreenLayout` method: +然后更新 `_buildLargeScreenLayout` 方法: + ```dart Widget _buildLargeScreenLayout() { @@ -215,26 +299,57 @@ Widget _buildLargeScreenLayout() { This code creates the classic menu-detail layout where the sidebar controls the content of the detail area. +这段代码创建经典的菜单-详情布局,侧边栏 +控制详情区域的内容。 + ### Test the adaptive navigation behavior +### 测试自适应导航行为 + Hot reload your app and test the navigation: +热重载应用并测试导航: + **Small screens (<600px width):** +**小屏(宽度 < 600px):** + - Tap contact groups to navigate to contact details. + + 点击联系人分组以导航到联系人详情。 + - Use the back button or a swipe gesture to return. + + 使用返回按钮或滑动手势返回。 + - This is a classic stack-based navigation flow. + 这是经典的基于栈的导航流程。 + **Large screens (>600px width):** +**大屏(宽度 > 600px):** + - Click contact groups in the sidebar to update the detail view. + + 在侧边栏中点击联系人分组以更新详情视图。 + - There is no navigation stack. The selection updates the content area. + + 没有导航栈。选择会更新内容区域。 + - This is a master-detail interface pattern. + 这是主从(master-detail)界面模式。 + The app automatically chooses the appropriate navigation pattern based on screen size. This provides an optimal experience on both phones and tablets. +应用会根据屏幕尺寸自动选择 +合适的导航模式。 +这在手机和平板上都能提供最佳体验。 + :::note If you resize the app from a small screen to a large screen while on a contact detail page, and then press the back button, @@ -244,74 +359,84 @@ route while the large-screen layout simultaneously renders the detail view, causing duplicate widgets (like the navigation bar) in the widget tree during transition. This is an expected edge case for this simple architecture and can be ignored for the purpose of this learning pathway. +如果你在小屏上处于联系人详情页时将应用从小屏放大到大屏,然后按返回键, +你可能会看到 `Hero` tag 异常。 +这是因为 `Navigator` 仍持有已 push 的小屏 +路由,而大屏布局同时渲染详情视图, +在转场期间导致 widget 树中出现重复 widget(例如导航栏)。 +对于这一简单架构,这是预期的边界情况,在本学习路径中可以忽略。 ::: ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你已完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Navigated between screens with Navigator.push + - title: 使用 Navigator.push 在屏幕之间导航 icon: open_in_new details: >- - `Navigator.of(context).push` adds a new route to the navigation stack. - This is the foundation of stack-based navigation, where screens are - pushed on top of each other and popped to go back. - - title: Used CupertinoPageRoute for iOS-style transitions + `Navigator.of(context).push` 向导航栈添加新路由。 + 这是基于栈的导航的基础,屏幕 + 彼此叠加上去,通过 pop 返回。 + - title: 使用 CupertinoPageRoute 实现 iOS 风格转场 icon: swipe_right details: >- - `CupertinoPageRoute` provides support for native iOS navigation features: - slide-in animations from the right, automatic back buttons, - proper title handling, and swipe-to-go-back gesture support. - - title: Implemented adaptive navigation patterns + `CupertinoPageRoute` 提供原生 iOS 导航特性支持: + 从右侧滑入的动画、自动返回按钮、 + 正确的标题处理,以及滑动返回手势支持。 + - title: 实现了自适应导航模式 icon: devices details: >- - You set up different navigation patterns for small and large screens. - Small screens use stack-based navigation where - tapping a group pushes a new screen. - Large screens use a master-detail pattern where selecting - a group updates the detail panel without navigation. - - title: Completed the Rolodex app + 你为小屏和大屏设置了不同的导航模式。 + 小屏使用基于栈的导航,点击 + 分组会 push 新屏幕。 + 大屏使用主从模式,选择 + 分组会更新详情面板而无需导航。 + - title: 完成了 Rolodex 应用 icon: celebration details: >- - You've built a complete iOS-style contacts app with - adaptive layouts, advanced scrolling, - collapsible headers with search, and responsive navigation. - These are common patterns used in production apps! + 你已构建完整的 iOS 风格联系人应用,具备 + 自适应布局、高级滚动、 + 带搜索的可折叠标题和响应式导航。 + 这些都是生产应用中常用的模式! ### Test yourself - -- question: "What does `Navigator.of(context).push` do?" +### 自测 + + +- question: "`Navigator.of(context).push` 的作用是什么?" options: - - text: Replaces the current screen with a new one. + - text: 用新屏幕替换当前屏幕。 correct: false - explanation: "Push adds to the stack; `pushReplacement` replaces the current screen." - - text: Adds a new route to the navigation stack, displaying it on top of the current screen. + explanation: "push 会添加到栈;`pushReplacement` 才会替换当前屏幕。" + - text: 向导航栈添加新路由,显示在当前屏幕之上。 correct: true - explanation: Push adds the new route to the stack, allowing users to go back to the previous screen. - - text: Removes the current screen from the navigation stack. + explanation: push 将新路由添加到栈,使用户可以返回上一屏幕。 + - text: 从导航栈移除当前屏幕。 correct: false - explanation: "That's what `pop` does; push adds a new screen." - - text: Opens a dialog box over the current screen. + explanation: "那是 `pop` 的作用;push 会添加新屏幕。" + - text: 在当前屏幕上方打开对话框。 correct: false - explanation: "Dialogs use `showDialog`; Navigator.push navigates to a full screen." -- question: "What does `Navigator.of(context).pop()` do?" + explanation: "对话框使用 `showDialog`;Navigator.push 用于导航到全屏界面。" +- question: "`Navigator.of(context).pop()` 的作用是什么?" options: - - text: Closes the entire app. + - text: 关闭整个应用。 correct: false - explanation: Pop only removes the current route; it doesn't close the app. - - text: Removes the current route from the navigation stack, returning to the previous screen. + explanation: pop 仅移除当前路由;不会关闭应用。 + - text: 从导航栈移除当前路由,返回上一屏幕。 correct: true - explanation: Pop removes the top route from the stack, revealing the screen beneath it. - - text: Clears all routes and shows the home screen. + explanation: pop 从栈顶移除路由,露出其下的屏幕。 + - text: 清除所有路由并显示主屏幕。 correct: false - explanation: That would require popUntil or pushAndRemoveUntil; pop removes only the top route. - - text: Refreshes the current screen with new data. + explanation: 那需要 popUntil 或 pushAndRemoveUntil;pop 仅移除栈顶路由。 + - text: 用新数据刷新当前屏幕。 correct: false - explanation: Pop navigates back; to refresh, you'd use setState or other state management. + explanation: pop 用于返回导航;要刷新需使用 setState 或其他状态管理。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md b/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md index b1aa0b938f..8f257d4cec 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md +++ b/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md @@ -1,19 +1,24 @@ --- -title: Set up your project -description: Instructions on how to create a new Flutter app. +# title: Set up your project +title: 设置项目 +# description: Instructions on how to create a new Flutter app. +description: 如何创建新 Flutter 应用的说明。 layout: tutorial +ai-translated: true --- Preview the Wikipedia reader app you'll build and set up the initial project with required packages. +预览你将构建的 Wikipedia 阅读器应用,并使用所需 package 设置初始项目。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Preview the Wikipedia reader app you'll build + - title: 预览你将构建的 Wikipedia 阅读器应用 icon: preview - - title: Add packages for handling HTTP requests and Wikipedia data + - title: 添加用于处理 HTTP 请求与 Wikipedia 数据的 package icon: inventory_2 - - title: Set up the initial project structure + - title: 设置初始项目结构 icon: code @@ -21,27 +26,48 @@ items: ### Introduction +### 介绍 + In the next few lessons, you'll learn how to work with data in a Flutter app. You'll build an app that fetches and displays article summaries from the [Wikipedia API][]. +在接下来的几课中,你将学习如何在 Flutter 应用中处理数据。 +你将构建一个从 +[Wikipedia API][] 获取并显示文章摘要的应用。 + A screenshot of the completed
 Wikipedia reader app showing an article with image, title,
 description, and extract text. -These lessons explore: +这些课程将探讨: - Making HTTP requests in Flutter. + + 在 Flutter 中发起 HTTP 请求。 + - Managing application state with `ChangeNotifier`. + + 使用 `ChangeNotifier` 管理应用状态。 + - Using the MVVM architecture pattern. + + 使用 MVVM 架构模式。 + - Creating responsive user interfaces that update automatically when data changes. + 创建在数据变化时自动更新的响应式用户界面。 + This tutorial assumes you've completed the [Getting started with Dart][] and the [Introduction to Flutter UI][] tutorials, and therefore doesn't explain concepts like HTTP, JSON, or widget basics. +本教程假定你已完成 +[Getting started with Dart][] 和 [Introduction to Flutter UI][] 教程, +因此不会解释 HTTP、JSON 或 widget 基础等概念。 + :::recommend Support Wikipedia [Wikipedia][] is a valuable resource, providing free @@ -50,6 +76,11 @@ collaboratively by volunteers worldwide. Consider [donating to Wikipedia][] to help keep this incredible resource free and accessible to everyone. +[Wikipedia][] 是宝贵资源,通过全球志愿者协作撰写的数百万篇文章 +免费提供人类知识。 +考虑 [donating to Wikipedia][],帮助保持这一非凡资源 +免费且人人可及。 + ::: [Wikipedia API]: https://en.wikipedia.org/api/rest_v1/ @@ -60,10 +91,16 @@ free and accessible to everyone. ### Create a new Flutter project +### 创建新的 Flutter 项目 + Create a new Flutter project using the [Flutter CLI][]. In your preferred terminal, run the following command to create a minimal Flutter app: +使用 [Flutter CLI][] 创建新的 Flutter 项目。 +在你偏好的终端中,运行以下命令以 +创建精简的 Flutter 应用: + ```console $ flutter create wikipedia_reader --empty ``` @@ -72,9 +109,14 @@ $ flutter create wikipedia_reader --empty ### Add required dependencies +### 添加所需依赖 + Your app needs the [`http` package][] to make HTTP requests. Add it to your project: +你的应用需要 [`http` package][] 来发起 HTTP 请求。 +将其添加到你的项目中: + ```console $ cd wikipedia_reader && flutter pub add http ``` @@ -83,12 +125,20 @@ $ cd wikipedia_reader && flutter pub add http ### Examine the starter code +### 查看起始代码 + First, create a new file `lib/summary.dart` to define the data model for Wikipedia article summaries. This file has no special logic, and is simply a collection of classes that represent the data returned by the Wikipedia API. It's sufficient to copy the code below into the file and then ignore it. If you aren't comfortable with basic Dart classes, you should read the [Dart Getting Started][] tutorial first. +首先,创建新文件 `lib/summary.dart` 以定义 +Wikipedia 文章摘要的数据模型。该文件没有特殊逻辑,只是 +表示 Wikipedia API 返回数据的类的集合。 +将下面的代码复制到文件中即可,之后可以忽略它。 +如果你对基本 Dart 类还不熟悉,应先阅读 [Dart Getting Started][] 教程。 + ```dart title="lib/summary.dart" collapsed /// Representation of the JSON data returned by the Wikipedia API. @@ -338,6 +388,9 @@ const acceptableImageFormats = ['png', 'jpg', 'jpeg']; Then, open `lib/main.dart` and replace the existing code with this basic structure, which adds required imports that the app uses: +然后,打开 `lib/main.dart` 并将现有代码替换为 +此基本结构,其中添加了应用所需的导入: + ```dart title="lib/main.dart" import 'dart:convert'; @@ -372,12 +425,20 @@ a title bar and placeholder content. The imports at the top include everything you need for HTTP requests, JSON parsing, and the Wikipedia data model. +此代码提供了带有 +标题栏和占位内容的基本应用结构。 +顶部的导入包含 HTTP 请求、JSON 解析和 Wikipedia 数据模型所需的全部内容。 + [Dart Getting Started]: {{site.dart-site}}/tutorial ### Run your app +### 运行应用 + Test that everything works by running your app: +通过运行应用测试一切是否正常: + ```console $ flutter run -d chrome ``` @@ -385,63 +446,69 @@ $ flutter run -d chrome You should see a simple app with "Wikipedia Flutter" in the app bar and "Loading..." in the center of the screen. +你应看到一个简单应用,应用栏显示「Wikipedia Flutter」, +屏幕中央显示「Loading...」。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Previewed the Wikipedia reader app + - title: 预览了 Wikipedia 阅读器应用 icon: preview details: >- - You're starting a new tutorial section focused on working with data. - You'll learn HTTP requests, state management with `ChangeNotifier`, - and the MVVM architectural pattern. - - title: Added the http package and created a data model + 你正在开始一个专注于数据处理的新教程章节。 + 你将学习 HTTP 请求、使用 `ChangeNotifier` 的状态管理, + 以及 MVVM 架构模式。 + - title: 添加了 http package 并创建了数据模型 icon: inventory_2 details: >- - You used `flutter pub add` to install the http package for making HTTP requests - and created the `Summary` class for Wikipedia data. - Packages let you leverage existing code built by the community - instead of building everything from scratch. - - title: Set up the initial project structure + 你使用 `flutter pub add` 安装了用于发起 HTTP 请求的 http package, + 并创建了用于 Wikipedia 数据的 `Summary` 类。 + package 让你可以利用社区构建的现有代码, + 而不必从零开始构建一切。 + - title: 设置了初始项目结构 icon: code details: >- - Your app has the basic structure with all necessary imports for - HTTP requests, JSON parsing, and Wikipedia data. - You're ready to start fetching real data from the Wikipedia API! + 你的应用已具备基本结构,包含 HTTP 请求、JSON 解析和 Wikipedia 数据所需的全部导入。 + 你已准备好从 Wikipedia API 获取真实数据! ### Test yourself - -- question: "What does the `--empty` flag do when running `flutter create`?" +### 自测 + + +- question: "运行 `flutter create` 时 `--empty` 标志有什么作用?" options: - - text: Creates a project with no files at all. + - text: 创建一个完全没有文件的项目。 correct: false - explanation: The project still has essential files; it just uses a minimal template. - - text: Creates a minimal Flutter project with less boilerplate code. + explanation: 项目仍包含必要文件;只是使用精简模板。 + - text: 创建一个样板代码更少的精简 Flutter 项目。 correct: true - explanation: "The `--empty` flag generates a minimal starter template without the default counter app." - - text: Creates a project without any dependencies. + explanation: "`--empty` 标志会生成不含默认计数器应用的精简起始模板。" + - text: 创建一个没有任何依赖的项目。 correct: false - explanation: The project still includes core Flutter dependencies. - - text: Creates a project that can only run on web. + explanation: 项目仍包含核心 Flutter 依赖。 + - text: 创建一个只能在 Web 上运行的项目。 correct: false - explanation: The flag doesn't restrict platforms; it only affects the starter template. -- question: What command is used to add a package dependency to a Flutter project? + explanation: 该标志不限制平台;只影响起始模板。 +- question: 使用什么命令向 Flutter 项目添加 package 依赖? options: - text: "`flutter install [package_name]`" correct: false - explanation: "The correct command uses `pub add`, not `install`." + explanation: "正确命令使用 `pub add`,而非 `install`。" - text: "`flutter pub add [package_name]`" correct: true - explanation: "Running `flutter pub add` adds the package to pubspec.yaml and downloads it." + explanation: "运行 `flutter pub add` 会将 package 添加到 pubspec.yaml 并下载。" - text: "`dart get [package_name]`" correct: false - explanation: "The command for adding packages is `flutter pub add` or editing pubspec.yaml." + explanation: "添加 package 的命令是 `flutter pub add` 或编辑 pubspec.yaml。" - text: "`flutter package install [package_name]`" correct: false - explanation: "There is no `flutter package` command; use `flutter pub add`." + explanation: "没有 `flutter package` 命令;请使用 `flutter pub add`。" diff --git a/sites/docs/src/content/learn/pathway/tutorial/slivers.md b/sites/docs/src/content/learn/pathway/tutorial/slivers.md index 0727faaeb4..e9f31eeb12 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/slivers.md +++ b/sites/docs/src/content/learn/pathway/tutorial/slivers.md @@ -1,7 +1,10 @@ --- -title: Advanced scrolling and slivers -description: Learn how to implement performant scrolling with slivers. +# title: Advanced scrolling and slivers +title: 高级滚动与 sliver +# description: Learn how to implement performant scrolling with slivers. +description: 学习如何使用 sliver 实现高性能滚动。 layout: tutorial +ai-translated: true --- @@ -15,16 +18,25 @@ By the end of this section, you'll understand how to use `CustomScrollView`, create navigation bars that collapse, and organize content in scrollable sections. +在本课中,你将学习 sliver, +它们是能够利用 +Flutter 强大且可组合的滚动系统的特殊 widget。 +Sliver 让你能够创建复杂的滚动效果, +包括可折叠标题、搜索集成和自定义滚动行为。 +在本节结束时,你将了解如何 +使用 `CustomScrollView`、创建可折叠的导航栏, +以及在可滚动分区中组织内容。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Understand slivers and how they differ from widgets + - title: 理解 sliver 及其与 widget 的区别 icon: view_day - - title: Build scrollable layouts with CustomScrollView + - title: 使用 CustomScrollView 构建可滚动布局 icon: unfold_more - - title: Create collapsible navigation bars with search + - title: 创建带搜索的可折叠导航栏 icon: search - - title: Organize contacts in alphabetized sections + - title: 按字母顺序分区组织联系人 icon: sort_by_alpha @@ -32,19 +44,34 @@ items: ### Slivers and widgets +### Sliver 与 widget + Slivers are scrollable areas that can be composed together in a `CustomScrollView` or other scroll views. Think of slivers as building blocks that each contribute a portion of the overall scrollable content. +Sliver 是可滚动区域,可以在 +`CustomScrollView` 或其他滚动视图中组合在一起。 +将 sliver 视为构建块,每个构建块 +为整体可滚动内容贡献一部分。 + While slivers and widgets are both fundamental Flutter concepts, they serve different purposes and aren't interchangeable. +虽然 sliver 和 widget 都是 Flutter 的基本概念, +但它们用途不同,不能互换使用。 + - **Widgets** are general UI building blocks that can be used anywhere in your widget tree. - **Slivers** are specialized widgets designed specifically for scrollable layouts and have some constraints: + + **Widget** 是通用 UI 构建块,可以 + 在 widget 树的任何位置使用。 + 可滚动布局设计的专用 widget,并具有一些约束: + - Slivers can **only** be direct children of scroll views, such as `CustomScrollView` and `NestedScrollView`. - Some scroll views **only** accept slivers as children. @@ -52,19 +79,39 @@ they serve different purposes and aren't interchangeable. - To use regular widgets within a sliver context, wrap them in `SliverToBoxAdapter` or `SliverFillRemaining`. + Sliver **只能**作为滚动视图的直接子级,例如 + `CustomScrollView` 和 `NestedScrollView`。 +- 某些滚动视图**只**接受 sliver 作为子级。 + 你不能将常规 widget 传递给 `CustomScrollView.slivers`。 +- 要在 sliver 上下文中使用常规 widget, + 请用 `SliverToBoxAdapter` 或 `SliverFillRemaining` 包裹它们。 + This architectural separation allows Flutter to optimize scrolling performance while it maintains clear boundaries between different types of UI components. +这种架构分离使 Flutter 能够 +优化滚动性能,同时在 +不同类型的 UI 组件之间保持清晰的边界。 + ### Add a basic sliver structure to contact groups +### 为联系人分组添加基本 sliver 结构 + First, replace the placeholder content in your contact groups page. To avoid duplicating code between the phone layout and the tablet sidebar, you can create a private, reusable widget. +首先,替换联系人分组页面中的占位内容。 +为避免在手机布局和平板侧边栏之间重复代码, +你可以创建一个私有的、可复用的 widget。 + Update `lib/screens/contact_groups.dart` by adding `_ContactGroupsView` to the bottom of the file. +通过将 `_ContactGroupsView` 添加到文件底部来 +更新 `lib/screens/contact_groups.dart`。 + ```dart import 'package:flutter/cupertino.dart'; @@ -129,6 +176,11 @@ displaying the list of contact groups. On small screens, it will be used as a page, and on large screens it will be used to fill the left column. +此私有 widget 包含用于 +显示联系人分组列表的共享 UI。 +在小屏幕上,它将作为页面使用;在 +大屏幕上,它将用于填充左侧列。 + This widget introduces several slivers: - `CupertinoSliverNavigationBar`: @@ -139,11 +191,26 @@ This widget introduces several slivers: A sliver that takes up the remaining space in the scroll area, and whose child is a non-sliver widget. +此 widget 引入了多个 sliver: + +- `CupertinoSliverNavigationBar`: + 一种有明确设计取向的导航栏,会随页面滚动而折叠。 +- `SliverList`: + 可滚动的项目列表。 +- `SliverFillRemaining`: + 占据滚动区域 + 剩余空间的 sliver,其子级是非 sliver widget。 + It accepts a callback function, `onListSelected`, to handle taps, which makes it adaptable for both navigation and sidebar selection. +它接受回调函数 `onListSelected` 来处理点击, +这使其既适用于导航,也适用于侧边栏选择。 + Now, update `ContactGroupsPage` in `lib/screens/contact_groups.dart` to use your new `_ContactGroupsView` widget: +现在,更新 `lib/screens/contact_groups.dart` 中的 `ContactGroupsPage` 以使用新的 `_ContactGroupsView` widget: + ```dart class ContactGroupsPage extends StatelessWidget { @@ -165,12 +232,22 @@ This structure keeps the `ContactGroupsPage` clean and focused on its primary responsibility: navigation, which you'll learn about in the next section of this tutorial. +此结构使 `ContactGroupsPage` 保持简洁, +并专注于其主要职责:导航, +你将在本教程的下一节中学习相关内容。 + ### Enhance the list with icons and visual elements +### 使用图标和视觉元素增强列表 + Now, add icons and contact counts to make the list more informative. Add this `_buildTrailing` helper method to your `_ContactGroupsView` class in `lib/screens/contact_groups.dart`: +现在,添加图标和联系人数量以使列表更具信息量。 +在 `lib/screens/contact_groups.dart` 中的 +`_ContactGroupsView` 类里添加此 `_buildTrailing` 辅助方法: + ```dart Widget _buildTrailing(List contacts, BuildContext context) { @@ -195,10 +272,17 @@ Widget _buildTrailing(List contacts, BuildContext context) { This helper creates the trailing content for each list item. It shows the contact count and a forward arrow. +此辅助方法为每个列表项创建尾部内容。 +它显示联系人数量和前进箭头。 + Now, update the `CupertinoListSection` in `_ContactGroupsView` to use icons and the trailing helper. Update the code within the `ValueListenableBuilder.builder` callback in the `build` method: +现在,更新 `_ContactGroupsView` 中的 `CupertinoListSection` 以 +使用图标和尾部辅助方法。更新 `build` 方法中 +`ValueListenableBuilder.builder` 回调内的代码: + ```dart child: ValueListenableBuilder>( @@ -236,14 +320,26 @@ The updated code now shows icons that differentiate between the main "All iPhone" group and user-created groups, along with contact counts and navigation indicators. +更新后的代码现在显示图标,用于区分 +主要的「All iPhone」分组和用户创建的分组,同时还包含 +联系人数量和导航指示器。 + ### Create advanced scrolling for contacts +### 为联系人创建高级滚动 + Next, you'll implement the contacts list page. +接下来,你将实现联系人列表页面。 + In the next lesson, you'll implement navigation for small screens. To see your progress on the contacts list page in the meantime, first update `lib/screens/adaptive_layout.dart` to display the contacts list page: +在下一课中,你将为小屏幕实现导航。 +与此同时,要查看联系人列表页面的进度,请先 +更新 `lib/screens/adaptive_layout.dart` 以显示联系人列表页面: + ```dart @@ -304,6 +400,9 @@ class _AdaptiveLayoutState extends State { Update `lib/screens/contacts.dart` by adding `_ContactListView` to the bottom of the file: +通过将 `_ContactListView` 添加到 +文件底部来更新 `lib/screens/contacts.dart`: + ```dart class _ContactListView extends StatelessWidget { @@ -347,6 +446,8 @@ class _ContactListView extends StatelessWidget { Now, update `ContactListsPage` to use this view: +现在,更新 `ContactListsPage` 以使用此视图: + ```dart class ContactListsPage extends StatelessWidget { @@ -364,13 +465,23 @@ class ContactListsPage extends StatelessWidget { This basic implementation demonstrates how to use slivers with dynamic data in a reusable component. +此基本实现演示了如何在可复用组件中 +将 sliver 与动态数据一起使用。 + ### Add search integration with slivers +### 使用 sliver 添加搜索集成 + Now, enhance the contacts page with integrated search functionality UI. Update the `CustomScrollView` in `_ContactListView` to use the `CupertinoSliverNavigationBar.search` constructor instead of the default `CupertinoSliverNavigationBar` constructor: +现在,为联系人页面增强集成的搜索功能 UI。 +更新 `_ContactListView` 中的 `CustomScrollView`,使用 +`CupertinoSliverNavigationBar.search` 构造函数,而不是 +默认的 `CupertinoSliverNavigationBar` 构造函数: + ```dart class _ContactListView extends StatelessWidget { @@ -414,13 +525,24 @@ The `CupertinoSliverNavigationBar.search` constructor provides integrated search functionality. As you scroll down, the search field smoothly transitions into the collapsed navigation bar. +`CupertinoSliverNavigationBar.search` 构造函数提供 +集成的搜索功能。当你向下滚动时, +搜索字段会平滑过渡到折叠的导航栏中。 + ### Create alphabetized contact sections +### 创建按字母分区的联系人区块 + Real-world contact apps organize contacts alphabetically. To do this, create sections for each letter. Add the following widget to the bottom of your `contacts.dart` file. This widget doesn't contain any slivers. +真实的联系人应用会按字母顺序组织联系人。 +为此,为每个字母创建分区。 +将以下 widget 添加到 `contacts.dart` 文件底部。 +此 widget 不包含任何 sliver。 + ```dart class ContactListSection extends StatelessWidget { @@ -474,11 +596,19 @@ class ContactListSection extends StatelessWidget { This widget creates the familiar alphabetized sections that you see in the iOS Contacts app. +此 widget 创建你在 iOS 通讯录应用中 +看到的熟悉的按字母分区。 + ### Use `SliverList` for the alphabetized sections +### 使用 `SliverList` 实现按字母分区 + Now, replace the placeholder content in `_ContactListView` with the alphabetized sections: +现在,将 `_ContactListView` 中的占位内容替换为 +按字母分区: + ```dart class _ContactListView extends StatelessWidget { @@ -527,72 +657,84 @@ class _ContactListView extends StatelessWidget { become part of the scrollable content. This is the simplest way to add a list of normal widgets to a scrollable sliver area. +`SliverList.list` 让你提供一组 widget, +它们成为可滚动内容的一部分。这是将 +常规 widget 列表添加到可滚动 sliver 区域的最简单方式。 + In the next lesson, you'll learn about stack-based navigation and update the UI on small screens to navigate between the contacts list view and the contacts view. +在下一课中,你将学习基于堆栈的导航, +并更新小屏幕上的 UI,以便在 +联系人列表视图和联系人视图之间导航。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Understood slivers and how they differ from widgets + - title: 理解了 sliver 及其与 widget 的区别 icon: view_day details: >- - Slivers are specialized widgets for scrollable layouts. - They can only be direct children of scroll views like `CustomScrollView`. - In `CustomScrollView` and other sliver contexts, regular widgets must be - wrapped in `SliverToBoxAdapter` or `SliverFillRemaining`. - - title: Built scrollable layouts with CustomScrollView + Sliver 是用于可滚动布局的专用 widget。 + 它们只能是 `CustomScrollView` 等滚动视图的直接子级。 + 在 `CustomScrollView` 和其他 sliver 上下文中,常规 widget 必须 + 用 `SliverToBoxAdapter` 或 `SliverFillRemaining` 包裹。 + - title: 使用 CustomScrollView 构建了可滚动布局 icon: unfold_more details: >- - `CustomScrollView` lets you compose multiple slivers together. - You used `CupertinoSliverNavigationBar`, `SliverFillRemaining`, - and `SliverList` to create sophisticated scrollable interfaces. - - title: Created collapsible navigation bars with search + `CustomScrollView` 让你能够将多个 sliver 组合在一起。 + 你使用了 `CupertinoSliverNavigationBar`、`SliverFillRemaining` + 和 `SliverList` 来创建复杂的可滚动界面。 + - title: 创建了带搜索的可折叠导航栏 icon: search details: >- - You used the `CupertinoSliverNavigationBar.search` constructor to - create a collapsible navigation bar with integrated search functionality. - - title: Organized contacts in alphabetized sections + 你使用了 `CupertinoSliverNavigationBar.search` 构造函数来 + 创建带有集成搜索功能的可折叠导航栏。 + - title: 按字母顺序分区组织了联系人 icon: sort_by_alpha details: >- - You created `ContactListSection` widgets grouped by last name initial, - then used `SliverList.list` to add them to the scrollable area. - This mirrors the familiar iOS Contacts app experience. + 你创建了按姓氏首字母分组的 `ContactListSection` widget, + 然后使用 `SliverList.list` 将它们添加到可滚动区域。 + 这复现了熟悉的 iOS 通讯录应用体验。 ### Test yourself - -- question: What is the key difference between slivers and regular widgets? +### 自测 + + +- question: sliver 与常规 widget 的主要区别是什么? options: - - text: Slivers are faster to render than regular widgets. + - text: Sliver 的渲染速度比常规 widget 更快。 correct: false - explanation: Both are optimized; the difference is their purpose and context. - - text: Slivers are specialized widgets designed for scrollable layouts and can only be direct children of scroll views. + explanation: 两者都经过优化;区别在于用途和上下文。 + - text: Sliver 是专为可滚动布局设计的专用 widget,且只能是滚动视图的直接子级。 correct: true - explanation: Slivers work within scroll views like CustomScrollView; regular widgets can be used anywhere. - - text: Slivers can have an unlimited number of children. + explanation: Sliver 在 CustomScrollView 等滚动视图中工作;常规 widget 可在任何地方使用。 + - text: Sliver 可以有无限数量的子级。 correct: false - explanation: Some slivers like SliverList can have many children, but that's not what distinguishes them. - - text: Slivers automatically handle user gestures. + explanation: 某些 sliver(如 SliverList)可以有很多子级,但这不是它们的区别所在。 + - text: Sliver 会自动处理用户手势。 correct: false - explanation: Gesture handling is separate; slivers are about scrollable layout composition. -- question: How do you use a regular widget inside a CustomScrollView's slivers list? + explanation: 手势处理是独立的;sliver 关乎可滚动布局的组合。 +- question: 如何在 CustomScrollView 的 slivers 列表中使用常规 widget? options: - - text: Just add it directly; CustomScrollView accepts any widget. + - text: 直接添加即可;CustomScrollView 接受任何 widget。 correct: false - explanation: CustomScrollView only accepts slivers; regular widgets must be wrapped. - - text: Wrap it in a SliverToBoxAdapter or SliverFillRemaining. + explanation: CustomScrollView 只接受 sliver;常规 widget 必须被包裹。 + - text: 用 SliverToBoxAdapter 或 SliverFillRemaining 包裹它。 correct: true - explanation: These adapters convert regular widgets into slivers so they can be used in sliver contexts. - - text: "Convert the widget to a sliver by calling `.toSliver()` on it." + explanation: 这些适配器将常规 widget 转换为 sliver,以便在 sliver 上下文中使用。 + - text: "通过调用 `.toSliver()` 将 widget 转换为 sliver。" correct: false - explanation: "There's no `.toSliver()` method; you use adapter widgets like SliverToBoxAdapter." - - text: "Pass it to the `child` property instead of `slivers`." + explanation: "没有 `.toSliver()` 方法;你需要使用 SliverToBoxAdapter 等适配器 widget。" + - text: "将其传递给 `child` 属性,而不是 `slivers`。" correct: false - explanation: CustomScrollView uses the slivers property; there's no child property for this purpose. + explanation: CustomScrollView 使用 slivers 属性;没有用于此目的的 child 属性。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md b/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md index f6e01b6240..b7ea923b7b 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md +++ b/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md @@ -1,21 +1,26 @@ --- -title: Stateful widgets -description: Learn about StatefulWidgets and rebuilding Flutter UI. +# title: Stateful widgets +title: 有状态 widget +# description: Learn about StatefulWidgets and rebuilding Flutter UI. +description: 了解 StatefulWidget 以及 Flutter UI 的重建。 layout: tutorial +ai-translated: true --- Learn when widgets need to be stateful and how to trigger UI updates with setState. +了解 widget 何时需要是有状态的,以及如何使用 setState 触发 UI 更新。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Learn when widgets need to be stateful + - title: 了解 widget 何时需要是有状态的 icon: change_circle - - title: Convert a StatelessWidget to a StatefulWidget + - title: 将 StatelessWidget 转换为 StatefulWidget icon: swap_horiz - - title: Trigger UI updates with setState + - title: 使用 setState 触发 UI 更新 icon: refresh @@ -23,23 +28,42 @@ items: ### Introduction +### 介绍 + So far, your app displays a grid and an input field, but the grid doesn't yet update to reflect the user's guesses. When this app is complete, each tile in the next unfilled row should update after each submitted user guess by: +到目前为止,你的应用会显示网格和输入框, +但网格尚未根据用户的猜测进行更新。 +当应用完成时,下一未填满行中的每个方块应在 +每次提交用户猜测后按以下方式更新: + - Displaying the correct letter. + + 显示正确的字母。 + - Changing color to reflect whether the letter is correct (green), is in the word but at an incorrect position (yellow), or doesn't appear in the word at all (grey). + 更改颜色以反映字母是否正确(绿色)、 + 是否在单词中但位置不正确(黄色),或 + 是否完全不在单词中(灰色)。 + To handle this dynamic behavior, you need to convert `GamePage` from a `StatelessWidget` to a [`StatefulWidget`][]. +要处理这种动态行为,你需要将 `GamePage` 从 +`StatelessWidget` 转换为 [`StatefulWidget`][]。 + [`StatefulWidget`]: {{site.api}}/flutter/widgets/StatefulWidget-class.html ### Why stateful widgets? +### 为什么需要 stateful widget? + When a widget's appearance or data needs to change during its lifetime, you need a `StatefulWidget` and a companion `State` object. While the `StatefulWidget` itself is still immutable (its properties @@ -47,14 +71,26 @@ can't change after creation), the `State` object is long-lived, can hold mutable data, and can be rebuilt when that data changes, causing the UI to update. +当 widget 的外观或数据在其生命周期内需要变化时, +你需要 `StatefulWidget` 以及配套的 `State` 对象。 +虽然 `StatefulWidget` 本身仍不可变(其属性 +在创建后无法更改),但 `State` 对象是长期存在的, +可以保存可变数据,并在数据变化时重建, +从而更新 UI。 + For example, the following widget tree imagines a simple app that uses a stateful widget with a counter that increases when the button is pressed. +例如,下面的 widget 树展示了一个简单应用, +它使用带有计数器的有状态 widget,按下按钮时计数器会增加。 + A diagram of a widget tree with a stateful widget and state object. Here is the basic `StatefulWidget` structure (doesn't do anything yet): +以下是基本的 `StatefulWidget` 结构(目前尚无任何功能): + ```dart class ExampleWidget extends StatefulWidget { ExampleWidget({super.key}); @@ -73,27 +109,50 @@ class _ExampleWidgetState extends State { ### Convert `GamePage` to a stateful widget +### 将 `GamePage` 转换为有状态 widget + To convert the `GamePage` (or any other) widget from a stateless widget to a stateful widget, do the following steps: +要将 `GamePage`(或任何其他)widget 从 +无状态 widget 转换为有状态 widget,请执行以下步骤: + 1. Change `GamePage` to extend `StatefulWidget` instead of `StatelessWidget`. + + 将 `GamePage` 改为继承 `StatefulWidget`,而不是 `StatelessWidget`。 + 1. Create a new class named `_GamePageState`, that extends `State`. This new class will hold the mutable state and the `build` method. Move the `build` method and all properties *instantiated on the widget* from `GamePage` to the state object. + + 创建一个名为 `_GamePageState` 的新类,继承 `State`。 + 这个新类将保存可变状态以及 `build` 方法。 + 将 `build` 方法以及所有在 widget 上*实例化的属性* + 从 `GamePage` 移到 state 对象中。 + 1. Implement the `createState()` method in `GamePage`, which returns an instance of `_GamePageState`. + 在 `GamePage` 中实现 `createState()` 方法,该方法 + 返回 `_GamePageState` 的实例。 + :::tip Quick assists You don't have to manually do this work, as the Flutter plugins for VS Code and IntelliJ provide ["quick assists"][] that can do this conversion for you. +你不必手动完成这些工作,因为适用于 +VS Code 和 IntelliJ 的 Flutter 插件提供了可以 +为你完成此转换的 ["quick assists"][]。 + ::: Your modified code should look like this: +修改后的代码应如下所示: + ```dart class GamePage extends StatefulWidget { GamePage({super.key}); @@ -139,29 +198,50 @@ class _GamePageState extends State { ### Updating the UI with `setState` +### 使用 `setState` 更新 UI + Whenever you mutate a `State` object, you must call [`setState`][] to signal the framework to update the user interface and call the `build` method again. +每当你修改 `State` 对象时, +必须调用 [`setState`][] 以通知框架 +更新用户界面并再次调用 `build` 方法。 + In this app, when a user makes a guess, the word they guessed is saved on the `Game` object, which is a property on the `GamePage` class, and therefore is state that might change and require the UI to update. When this state is mutated, the grid should be re-drawn to show the user's guess. +在本应用中,当用户进行猜测时,他们猜的单词会 +保存在 `Game` 对象上,它是 `GamePage` 类的一个属性, +因此属于可能变化并需要 UI 更新的状态。 +当此状态被修改时,应重新绘制网格以 +显示用户的猜测。 + To implement this, update the callback function passed to `GuessInput`. The function needs to call `setState` and, within `setState`, it needs to execute the logic to determine whether the users guess was correct. +要实现这一点,请更新传给 `GuessInput` 的回调函数。 +该函数需要调用 `setState`,并在 `setState` 内部 +执行用于判断用户猜测是否正确的逻辑。 + :::note The game logic is abstracted away into the `Game` object, and outside the scope of this tutorial. +游戏逻辑已抽象到 `Game` 对象中, +不在本教程范围内。 + ::: Update your code: +更新你的代码: + ```dart class GamePage extends StatefulWidget { GamePage({super.key}); @@ -210,67 +290,76 @@ If you were to call `_game.guess(guess)` *without* a calling `setState`, the internal game data would change, but Flutter wouldn't know it needs to repaint the screen, and the user wouldn't see any updates. +现在,当你在 `TextInput` 中输入合法猜测并提交时, +应用会反映用户的猜测。 +如果你*不*调用 `setState` 就调用 `_game.guess(guess)`, +内部游戏数据会变化,但 Flutter 不会知道需要 +重绘屏幕,用户也就看不到任何更新。 + [`setState`]: {{site.api}}/flutter/widgets/State/setState.html ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Learned when widgets need to be stateful + - title: 了解了 widget 何时需要是有状态的 icon: change_circle details: >- - When a widget's appearance or data needs to change during its lifetime, - you need a `StatefulWidget`. The widget itself stays immutable, but - its companion `State` object holds mutable data and triggers rebuilds. - - title: Converted GamePage to a StatefulWidget + 当 widget 的外观或数据在其生命周期内需要变化时, + 你需要 `StatefulWidget`。widget 本身保持不可变,但 + 其配套的 `State` 对象保存可变数据并触发重建。 + - title: 将 GamePage 转换为 StatefulWidget icon: swap_horiz details: >- - You refactored `GamePage` to be stateful by - creating a companion `_GamePageState` class, moving the - `build` method and mutable properties to it, and - implementing `createState()`. - Your IDE's support for quick assists can automate this conversion. - - title: Made your app respond to user input with setState + 你通过创建配套的 `_GamePageState` 类、将 + `build` 方法和可变属性移到该类中,并 + 实现 `createState()`,将 `GamePage` 重构为有状态 widget。 + IDE 对快速辅助的支持可以自动完成此转换。 + - title: 使用 setState 让应用响应用户输入 icon: refresh details: >- - Calling `setState` tells Flutter to rebuild the UI of a widget. - When a user submits a guess, you call `setState` to update the game state, - and the grid automatically reflects the new data. - Your app is now truly interactive! + 调用 `setState` 会告诉 Flutter 重建 widget 的 UI。 + 当用户提交猜测时,你调用 `setState` 更新游戏状态, + 网格会自动反映新数据。 + 你的应用现在真正具有交互性了! ### Test yourself - -- question: When should you use a StatefulWidget instead of a StatelessWidget? +### 自测 + + +- question: 何时应使用 StatefulWidget 而不是 StatelessWidget? options: - - text: When the widget needs to make HTTP requests. + - text: 当 widget 需要发起 HTTP 请求时。 correct: false - explanation: HTTP requests can be made from either, but state changes require StatefulWidget. - - text: When the widget's appearance or data needs to change during its lifetime. + explanation: 两者都可以发起 HTTP 请求,但状态变化需要 StatefulWidget。 + - text: 当 widget 的外观或数据在其生命周期内需要变化时。 correct: true - explanation: StatefulWidget is needed when the UI must update in response to data changes over time. - - text: When the widget has more than three child widgets. + explanation: 当 UI 必须随时间响应数据变化而更新时,需要 StatefulWidget。 + - text: 当 widget 有超过三个子 widget 时。 correct: false - explanation: The number of children doesn't determine whether a widget is stateful. - - text: When the widget is at the root of the widget tree. + explanation: 子 widget 的数量不决定 widget 是否为有状态 widget。 + - text: 当 widget 位于 widget 树根节点时。 correct: false - explanation: Root widgets can be stateless; statefulness depends on whether data changes during the widget's lifetime. -- question: What happens if you change data in a State object without calling setState? + explanation: 根 widget 可以是无状态的;是否有状态取决于数据是否在 widget 生命周期内变化。 +- question: 如果在不调用 setState 的情况下修改 State 对象中的数据会发生什么? options: - - text: The app will crash with an error. + - text: 应用会因错误而崩溃。 correct: false - explanation: The app won't crash, but the UI won't update. - - text: The data changes internally, but Flutter won't rebuild the UI to reflect the change. + explanation: 应用不会崩溃,但 UI 不会更新。 + - text: 数据在内部会变化,但 Flutter 不会重建 UI 以反映该变化。 correct: true - explanation: Without calling setState, Flutter doesn't know it needs to repaint, so the user won't see updates. - - text: Flutter automatically detects the change and rebuilds the UI. + explanation: 若不调用 setState,Flutter 不知道需要重绘,用户也就看不到更新。 + - text: Flutter 会自动检测变化并重建 UI。 correct: false - explanation: Flutter requires setState to know when to rebuild; it doesn't auto-detect changes. - - text: The widget is removed from the widget tree. + explanation: Flutter 需要 setState 才能知道何时重建;它不会自动检测变化。 + - text: widget 会从 widget 树中移除。 correct: false - explanation: The widget remains; it just won't visually update without setState. + explanation: widget 仍然存在;只是没有 setState 时不会在视觉上更新。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/user-input.md b/sites/docs/src/content/learn/pathway/tutorial/user-input.md index 40b37408fc..2857858f4a 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/user-input.md +++ b/sites/docs/src/content/learn/pathway/tutorial/user-input.md @@ -1,21 +1,26 @@ --- -title: User input -description: Accept input from the user with buttons and text fields. +# title: User input +title: 用户输入 +# description: Accept input from the user with buttons and text fields. +description: 使用按钮和文本字段接受用户输入。 layout: tutorial +ai-translated: true --- Learn to build text inputs, manage text with controllers, and handle user actions with buttons. +学习构建文本输入、使用 controller 管理文本,以及使用按钮处理用户操作。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Build a text input widget with TextField + - title: 使用 TextField 构建文本输入 widget icon: text_fields - - title: Manage text with TextEditingController + - title: 使用 TextEditingController 管理文本 icon: edit_note - - title: Control input focus for a better user experience + - title: 控制输入焦点以改善用户体验 icon: center_focus_strong - - title: Handle user actions with callbacks and buttons + - title: 使用回调和按钮处理用户操作 icon: touch_app @@ -23,24 +28,41 @@ items: ### Introduction +### 介绍 + The app will display the user's guesses in the `Tile` widgets, but it needs a way for the user to input those guesses. In this lesson, build that functionality with two interaction widgets: [`TextField`][] and [`IconButton`][]. +应用会在 `Tile` widget 中显示用户的猜测, +但还需要让用户输入这些猜测的方式。 +在本课中,使用两个交互 widget 构建该功能: +[`TextField`][] 和 [`IconButton`][]。 + [`TextField`]: {{site.api}}/flutter/material/TextField-class.html [`IconButton`]: {{site.api}}/flutter/material/IconButton-class.html ### Implement callback functions +### 实现回调函数 + To allow users to type in their guesses, you'll create a dedicated widget named `GuessInput`. First, create the basic structure for your `GuessInput` widget that requires a callback function as an argument. Name the callback function `onSubmitGuess`. +为了让用户输入猜测, +你将创建一个名为 `GuessInput` 的专用 widget。 +首先,为 `GuessInput` widget 创建基本结构,该结构 +需要一个回调函数作为参数。 +将回调函数命名为 `onSubmitGuess`。 + Add the following code to your `main.dart` file. +将以下代码添加到你的 `main.dart` 文件中。 + ```dart class GuessInput extends StatelessWidget { @@ -62,26 +84,48 @@ that has the type `void Function(String)`. This function takes a single `String` argument (the user's guess) and doesn't return any value (denoted by `void`). +`final void Function(String) onSubmitGuess;` 这一行 +声明了类中名为 `onSubmitGuess` 的 `final` 成员, +其类型为 `void Function(String)`。 +该函数接受单个 `String` 参数(用户的猜测),且 +不返回任何值(由 `void` 表示)。 + This callback tells us that the logic that actually handles the user's guess will be written elsewhere. It's a good practice for interactive widgets to use callback functions to keep the widget that handles interactions reusable and decoupled from any specific functionality. +此回调表明,实际处理 +用户猜测的逻辑将在别处编写。 +对于交互 widget,使用回调函数是良好实践,可保持处理交互的 widget 可复用且 +与任何具体功能解耦。 + By the end of this lesson, the passed-in `onSubmitGuess` function is called when a user enters a guess. First, you'll need to build the visual parts of this widget. This is what the widget will look like. +到本课结束时,当用户输入猜测时会调用传入的 `onSubmitGuess` 函数。 +首先,你需要构建此 widget 的视觉部分。 +widget 将如下所示。 + A screenshot of the Flutter property editor tool. ### The `TextField` widget +### `TextField` widget + Given that the text field and button are displayed side-by-side, create them as a `Row` widget. Replace the `Container` placeholder in your `build` method with a `Row` containing an `Expanded` `TextField`: +由于文本字段和按钮并排显示, +将它们创建为 `Row` widget。 +将 `build` 方法中的 `Container` 占位符替换为 +包含 `Expanded` `TextField` 的 `Row`: + ```dart class GuessInput extends StatelessWidget { @@ -116,35 +160,65 @@ You have seen some of these widgets in previous lessons: `Row` and `Padding`. New, though, is the [`Expanded`][] widget. When a child of a `Row` (or `Column`) is wrapped in `Expanded`, it tells that child to fill all the available space along the main axis -(horizontal for`Row`, vertical for `Column`) that +(horizontal for `Row`, vertical for `Column`) that hasn't been taken by other children. This makes the `TextField` stretch to take up all the space *except* what's taken by other widgets in the row. +你在之前的课程中见过其中一些 widget: +`Row` 和 `Padding`。不过,[`Expanded`][] widget 是新的。 +当 `Row`(或 `Column`)的子 widget 被 `Expanded` 包裹时, +它会告诉该子 widget 沿主轴填满所有可用空间 +(`Row` 为水平方向,`Column` 为垂直方向), +前提是其他子 widget 尚未占用。 +这使 `TextField` 拉伸以占据行中*除* +其他 widget 占用空间外的所有空间。 + :::tip `Expanded` is often the solution to "[unbounded width/height][]" exceptions. + +`Expanded` 通常是解决 "[unbounded width/height][]" 异常的方案。 ::: The `TextField` widget is also new in this lesson and is the star of the show. This is the basic Flutter widget for text input. +`TextField` widget 在本课中也是新的,并且是重头戏。 +这是 Flutter 用于文本输入的基本 widget。 + Thus far, `TextField` has the following configuration. +到目前为止,`TextField` 具有以下配置。 + - It's decorated with a rounded border. Notice that the decoration configuration is very similar to how a `Container` and boxes are decorated. + + 它使用圆角边框装饰。 + 请注意,装饰配置与 + `Container` 和盒子的装饰方式非常相似。 + - Its `maxLength` property is set to 5 because the game only allows guesses of 5-letter words. + 其 `maxLength` 属性设置为 5,因为游戏 + 仅允许 5 个字母的单词猜测。 + [`Expanded`]: {{site.api}}/flutter/widgets/Expanded-class.html [unbounded width/height]: https://www.youtube.com/watch?v=jckqXR5CrPI ### Handle text with `TextEditingController` +### 使用 `TextEditingController` 处理文本 + Next, you need a way to manage the text that the user types into the input field. For this, use a [`TextEditingController`][]. +接下来,你需要一种方式来管理用户 +输入到输入框中的文本。 +为此,请使用 [`TextEditingController`][]。 + ```dart class GuessInput extends StatelessWidget { @@ -183,6 +257,10 @@ A `TextEditingController` is used to read, clear, and modify the text in a `TextField`. To use it, pass it into the `TextField`. +`TextEditingController` 用于 +读取、清除和修改 `TextField` 中的文本。 +使用时,将其传入 `TextField`。 + ```dart class GuessInput extends StatelessWidget { @@ -224,9 +302,19 @@ using the `TextField.onSubmitted` argument. This argument accepts a callback, and the callback is triggered whenever the user presses the "Enter" key on the keyboard while the text field has focus. +现在,当用户输入文本时,你可以 +通过 `_textEditingController` 捕获它,但 +你需要知道_何时_捕获。 +响应输入的最简单方式是 +使用 `TextField.onSubmitted` 参数。 +该参数接受回调,每当文本字段获得焦点时用户在键盘上按下「Enter」键, +就会触发该回调。 + For now, ensure that this works by adding the following callback to `TextField.onSubmitted`: +目前,通过将以下回调添加到 `TextField.onSubmitted` 来确保其正常工作: + ```dart class GuessInput extends StatelessWidget { @@ -269,6 +357,11 @@ you could print the `input` passed to the `onSubmitted` callback directly, but a better user experience clears the text after each guess: You need a `TextEditingController` to do that. Update the code as follows: +在这种情况下, +你可以直接打印传给 `onSubmitted` 回调的 `input`, +但更好的用户体验是在每次猜测后清除文本: +你需要 `TextEditingController` 来实现这一点。按如下方式更新代码: + ```dart class GuessInput extends StatelessWidget { @@ -311,6 +404,9 @@ class GuessInput extends StatelessWidget { In Dart, it's good practice to use the `_` [wildcard][] to hide the input to a function that'll never be used. The preceding example does so. + +在 Dart 中,对永远不会使用的函数输入使用 `_` [wildcard][] 是良好实践。 +前面的示例正是这样做的。 ::: [`TextEditingController`]: {{site.api}}/flutter/widgets/TextEditingController-class.html @@ -318,6 +414,8 @@ The preceding example does so. ### Gain input focus +### 获取输入焦点 + Often, you want a specific input or widget to automatically gain focus without the user taking action. In this app, for example, the only thing a user can do is enter a guess, @@ -325,9 +423,19 @@ so the `TextField` should be focused automatically when the app launches. And after the user enters a guess, the focus should stay in the `TextField` so they can enter their next guess. +通常,你希望特定输入或 widget 在 +用户无需操作的情况下自动获得焦点。 +例如,在本应用中,用户唯一能做的就是输入猜测, +因此应用启动时 `TextField` 应自动获得焦点。 +用户输入猜测后,焦点应保持在 +`TextField` 中,以便输入下一次猜测。 + To resolve the first focus issue, set up the `autofocus` property on the `TextField`. +要解决第一个焦点问题, +请在 `TextField` 上设置 `autofocus` 属性。 + ```dart class GuessInput extends StatelessWidget { @@ -372,8 +480,16 @@ You can use `FocusNode` to request that a `TextField` gain focus, (making the keyboard appear on mobile), or to know when a field has focus. +第二个问题需要你 +使用 [`FocusNode`][] 管理键盘焦点。 +你可以使用 `FocusNode` 请求 `TextField` 获得焦点 +(在移动端使键盘出现), +或了解字段何时具有焦点。 + First, create a `FocusNode` in the `GuessInput` class: +首先,在 `GuessInput` 类中创建 `FocusNode`: + ```dart class GuessInput extends StatelessWidget { @@ -396,6 +512,9 @@ class GuessInput extends StatelessWidget { Then, use the `FocusNode` to request focus whenever the `TextField` is submitted after the controller is cleared: +然后,在 controller 清除后提交 `TextField` 时, +使用 `FocusNode` 请求焦点: + ```dart class GuessInput extends StatelessWidget { @@ -441,16 +560,27 @@ class GuessInput extends StatelessWidget { Now, when you press Enter after inputting text, you can continue typing. +现在,输入文本后按 Enter, +你可以继续输入。 + [`FocusNode`]: {{site.api}}/flutter/widgets/FocusNode-class.html ### Use the input +### 使用输入 + Finally, you need to handle the text that the user enters. Recall that the constructor for `GuessInput` requires a callback called `onSubmitGuess`. In `GuessInput`, you need to use that callback. Replace the `print` statement with a call to that function. +最后,你需要处理用户输入的文本。 +回想一下,`GuessInput` 的构造函数需要一个 +名为 `onSubmitGuess` 的回调。 +在 `GuessInput` 中,你需要使用该回调。 +将 `print` 语句替换为对该函数的调用。 + ```dart class GuessInput extends StatelessWidget { @@ -496,6 +626,9 @@ class GuessInput extends StatelessWidget { :::note The `trim` function prevents whitespace from being entered; otherwise, the user could enter a four-letter word plus a space character. + +`trim` 函数可防止输入空白字符; +否则,用户可能输入四个字母的单词加一个空格字符。 ::: The remaining functionality is handled in the parent widget, `GamePage`. @@ -503,6 +636,11 @@ In the `build` method of that class, under the `Row` widgets in the `Column` widget's children, add the `GuessInput` widget: +其余功能由父 widget `GamePage` 处理。 +在该类的 `build` 方法中, +在 `Column` widget 的 children 中 `Row` widget 下方, +添加 `GuessInput` widget: + ```dart class GamePage extends StatelessWidget { @@ -542,19 +680,39 @@ prove that it's wired up correctly. Submitting the guess requires using the functionality of a `StatefulWidget`, which you'll do in the next lesson. +目前,这只会打印猜测以 +证明连接正确。 +提交猜测需要使用 `StatefulWidget` 的功能, +你将在下一课中完成。 + ### Buttons +### 按钮 + To improve the UX on mobile and reflect well-known UI practices, there should also be a button that can submit the guess. +为改善移动端 UX 并体现常见的 UI 实践, +还应有可提交猜测的按钮。 + There are many button widgets built into Flutter, like [`TextButton`][], [`ElevatedButton`][], and the button you'll use now: [`IconButton`][]. All of these buttons (and many other interaction widgets) require two arguments (in addition to their optional arguments): +Flutter 内置了许多按钮 widget,例如 [`TextButton`][]、 +[`ElevatedButton`][],以及你现在将使用的 [`IconButton`][]。 +所有这些按钮(以及许多其他交互 widget)都需要两个 +参数(除可选参数外): + - A callback function passed to `onPressed`. + + 传给 `onPressed` 的回调函数。 + - A widget that makes up the content of the button (often `Text` or an `Icon`). + 构成按钮内容的 widget(通常是 `Text` 或 `Icon`)。 + Add an icon button to the row widget's children list in the `GuessInput` widget, and give it an [`Icon`][] widget to display. The `Icon` widget requires configuration; in this case, @@ -562,6 +720,13 @@ the `padding` property sets the padding between the edge of the button and the icon it wraps to zero. This removes the default padding and makes the button smaller. +在 `GuessInput` widget 中将图标按钮添加到 row widget 的 children 列表, +并为其提供要显示的 [`Icon`][] widget。 +`Icon` widget 需要配置;在本例中, +`padding` 属性将按钮边缘与其包裹的图标之间的 +内边距设置为零。 +这会移除默认内边距并使按钮更小。 + ```dart class GuessInput extends StatelessWidget { @@ -590,6 +755,8 @@ class GuessInput extends StatelessWidget { The `IconButton.onPressed` callback should look familiar: +`IconButton.onPressed` 回调应该看起来很熟悉: + ```dart class GuessInput extends StatelessWidget { @@ -622,6 +789,8 @@ class GuessInput extends StatelessWidget { This method does the same as the `onSubmitted` callback on the `TextField`. +此方法与 `TextField` 上的 `onSubmitted` 回调作用相同。 + [`Icon`]: {{site.api}}/flutter/material/Icons-class.html [`TextButton`]: {{site.api}}/flutter/material/TextButton-class.html [`ElevatedButton`]: {{site.api}}/flutter/material/ElevatedButton-class.html @@ -629,16 +798,28 @@ This method does the same as the `onSubmitted` callback on the `TextField`. :::note Challenge - Share "on submitted" logic. +挑战 - 共享 "on submitted" 逻辑 + You might be thinking, "Shouldn't we abstract these methods into one function and pass it to both inputs?" You could, and as your app grows in complexity, you probably should. That said, the callbacks `IconButton.onPressed` and `TextField.onSubmitted` have different signatures, so it's not completely straight-forward. +你可能在想:「我们是否应该将这些方法抽象为一个 +函数并传给两个输入?」 +可以,随着应用复杂度增加,你很可能应该这样做。 +也就是说,`IconButton.onPressed` 和 `TextField.onSubmitted` 的回调 +签名不同,因此并非完全直截了当。 + Refactor the code such that the logic inside this method isn't repeated. +重构代码,使此方法内的逻辑不重复。 + **Solution:** +**解答:** + ```dart title="solution.dart" collapsed class GuessInput extends StatelessWidget { @@ -694,68 +875,72 @@ class GuessInput extends StatelessWidget { ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Built a text input widget with TextField + - title: 使用 TextField 构建了文本输入 widget icon: text_fields details: >- - You created a `GuessInput` widget with a `TextField` for text entry. - You configured it with a rounded border, character limit, and - used `Expanded` to make it fill available space in the row. - - title: Managed text with TextEditingController + 你创建了带有用于文本输入的 `TextField` 的 `GuessInput` widget。 + 你为其配置了圆角边框、字符限制,并 + 使用 `Expanded` 使其填满行中的可用空间。 + - title: 使用 TextEditingController 管理了文本 icon: edit_note details: >- - `TextEditingController` lets you read and modify text field content. - You used it to capture the user's input with `.text` and clear the - field after submission with `.clear()`. - - title: Controlled input focus for a polished UX + `TextEditingController` 让你读取和修改文本字段内容。 + 你使用 `.text` 捕获用户输入,并在提交后使用 `.clear()` 清除 + 字段。 + - title: 控制了输入焦点以打造精致的 UX icon: center_focus_strong details: >- - You used `autofocus` to focus the text field on launch and `FocusNode` - with `requestFocus()` to maintain focus after each guess. - These details make your app feel responsive and well-built. - - title: Handled user actions with callbacks and buttons + 你使用 `autofocus` 在启动时聚焦文本字段,并使用 `FocusNode` + 配合 `requestFocus()` 在每次猜测后保持焦点。 + 这些细节让你的应用感觉响应迅速且制作精良。 + - title: 使用回调和按钮处理了用户操作 icon: touch_app details: >- - To respond to user input, - you specified callback functions like `onSubmitted` and `onPressed`. - Passing callback functions as constructor arguments keeps your - widgets reusable and decoupled from specific logic. + 为响应用户输入, + 你指定了 `onSubmitted` 和 `onPressed` 等回调函数。 + 将回调函数作为构造函数参数传入可保持 + widget 可复用且与具体逻辑解耦。 ### Test yourself - -- question: How do you programmatically read or clear the text in a TextField? +### 自测 + + +- question: 如何以编程方式读取或清除 TextField 中的文本? options: - - text: Access the TextField's text property directly. + - text: 直接访问 TextField 的 text 属性。 correct: false - explanation: TextField doesn't expose a text property; you need a controller. - - text: Use the TextEditingController attached to the TextField. + explanation: TextField 不暴露 text 属性;你需要 controller。 + - text: 使用附加到 TextField 的 TextEditingController。 correct: true - explanation: TextEditingController provides the text property to read the value and clear() method to reset it. - - text: Listen to the onChanged callback and store the value in a variable. + explanation: TextEditingController 提供 text 属性以读取值,并提供 clear() 方法以重置。 + - text: 监听 onChanged 回调并将值存储在变量中。 correct: false - explanation: While onChanged works for reading, clearing requires a TextEditingController. - - text: Call TextField.getText() method. + explanation: onChanged 可用于读取,但清除需要 TextEditingController。 + - text: 调用 TextField.getText() 方法。 correct: false - explanation: TextField doesn't have a getText method; use TextEditingController instead. -- question: How do you programmatically move focus to a specific TextField? + explanation: TextField 没有 getText 方法;请改用 TextEditingController。 +- question: 如何以编程方式将焦点移到特定 TextField? options: - - text: "Call `TextField.focus()` directly." + - text: "直接调用 `TextField.focus()`。" correct: false - explanation: TextField doesn't have a focus method; you use a FocusNode. - - text: "Set the `autofocus` property to true at runtime." + explanation: TextField 没有 focus 方法;你需要使用 FocusNode。 + - text: "在运行时将 `autofocus` 属性设置为 true。" correct: false - explanation: The 'autofocus' property only works on initial build, not for moving focus later. - - text: "Use a FocusNode and call `requestFocus()` on it." + explanation: autofocus 属性仅在初始构建时有效,不能用于之后移动焦点。 + - text: "使用 FocusNode 并对其调用 `requestFocus()`。" correct: true - explanation: "A FocusNode gives you control over focus, and calling `requestFocus()` moves focus to its associated widget." - - text: Wrap the TextField in a GestureDetector and tap programmatically. + explanation: "FocusNode 让你控制焦点,调用 `requestFocus()` 会将焦点移到其关联的 widget。" + - text: 将 TextField 包裹在 GestureDetector 中并以编程方式点击。 correct: false - explanation: This is not how focus is managed; FocusNode is the proper approach. + explanation: 焦点不是这样管理的;FocusNode 才是正确方式。 diff --git a/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md b/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md index 53a6a9dc9f..1911be0192 100644 --- a/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md +++ b/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md @@ -1,22 +1,28 @@ --- -title: Create widgets -description: Learn about stateless widgets and how to build your own. +# title: Create widgets +title: 创建 widget +# description: Learn about stateless widgets and how to build your own. +description: 了解无状态 widget 以及如何构建你自己的 widget。 layout: tutorial +ai-translated: true --- Learn to create custom widgets and use the most common SDK widgets like Container, Center, and Text. +学习创建自定义 widget,并使用最常见的 SDK widget,例如 +Container、Center 和 Text。 + -title: What you'll accomplish +title: 你将完成的内容 items: - - title: Create a custom StatelessWidget + - title: 创建自定义 StatelessWidget icon: widgets - - title: Make widgets reusable with constructor parameters + - title: 通过构造函数参数使 widget 可复用 icon: tune - - title: Style widgets using Container and BoxDecoration + - title: 使用 Container 和 BoxDecoration 为 widget 设置样式 icon: palette @@ -24,18 +30,30 @@ items: ### Before you start +### 开始之前 + This app relies on a bit of game logic that isn't UI-related, and thus is outside the scope of this tutorial. Before you move on, you need to add this logic to your app. +本应用依赖一些与 UI 无关的游戏逻辑, +因此不在本教程范围内。 +在继续之前,你需要将此逻辑添加到你的应用中。 + 1. Download the following Dart file and save it as `lib/game.dart` in your project directory. +1. 下载以下 Dart 文件,并将其保存为 + 项目目录中的 `lib/game.dart`。 + 1. To enable access to the types defined in the `game.dart` library, add an import to it from your `lib/main.dart` file: +1. 要访问 `game.dart` 库中定义的类型, + 请在你的 `lib/main.dart` 文件中添加对它的 import: + ```dart title="main.dart" highlightLines=3 import 'package:flutter/material.dart'; @@ -50,22 +68,40 @@ The full lists combined have over 10,000 words and were omitted for brevity. You don't need the full lists to continue the tutorial. When you're testing your app, make sure to use the words from those lists. +你可能会注意到 +`legalGuesses` 和 `legalWords` 列表只包含少量单词。 +完整列表合计超过 10,000 个单词,为简洁起见已省略。 +你无需完整列表即可继续本教程。 +测试应用时,请确保使用这些列表中的单词。 + Alternatively, you can find the full lists in [this GitHub repository][full-words], as well as instructions to import it into your project. +或者,你可以在 +[此 GitHub 仓库][full-words] 中找到完整列表,以及 +将其导入项目的说明。 + ::: [full-words]: https://github.com/ericwindmill/legal_wordle_words ### Anatomy of a stateless widget +### 无状态 widget 的结构 + A `Widget` is a Dart class that extends one of the Flutter widget classes, in this case [`StatelessWidget`][]. +`Widget` 是一个继承 Flutter widget 类之一的 Dart 类, +此处为 [`StatelessWidget`][]。 + Open your `main.dart` file and add this code below the `MainApp` class, which defines a new widget called `Tile`. +打开 `main.dart` 文件,在 `MainApp` 类下方添加以下代码, +其中定义了一个名为 `Tile` 的新 widget。 + ```dart class Tile extends StatelessWidget { @@ -85,25 +121,43 @@ class Tile extends StatelessWidget { #### Constructor +#### 构造函数 + The `Tile` class has a [constructor][] that defines what data needs to be passed into the widget to render the widget. In this case, the constructor accepts two parameters: +`Tile` 类有一个 [constructor][],用于定义 +渲染 widget 时需要传入哪些数据。 +此处,构造函数接受两个参数: + - A `String` representing the guessed letter of the tile. -- A `HitType` [enum value][] represent the guess result and +- A `HitType` [enum value][] represents the guess result and used to determine the color of the tile. For example, `HitType.hit` results in a green tile. + 表示方块猜测字母的 `String`。 +- 表示猜测结果的 `HitType` [enum value][], + 用于确定方块颜色。 + 例如,`HitType.hit` 会得到绿色方块。 + Passing data into widget constructors is at the core of making widgets reusable. +向 widget 构造函数传入数据是使 widget 可复用的核心。 + [constructor]: {{site.dart-site}}/language/constructors [enum value]: {{site.dart-site}}/language/enums #### Build method +#### Build 方法 + Finally, there's the all important `build` method, which must be defined on every widget, and will always return another widget. +最后,还有至关重要的 `build` 方法,每个 widget 都必须定义它, +且它始终返回另一个 widget。 + ```dart class Tile extends StatelessWidget { @@ -122,11 +176,18 @@ class Tile extends StatelessWidget { ### Use the custom widget +### 使用自定义 widget + When the app is finished, there will be 25 instances of this widget on the screen. For now, though, display just one so you can see the updates as they're made. In the `MainApp.build` method, replace the `Text` widget with the following: +应用完成后, +屏幕上将有 25 个此 widget 的实例。 +不过现在只显示一个,以便你能看到每次更新。 +在 `MainApp.build` 方法中,将 `Text` widget 替换为以下内容: + ```dart class MainApp extends StatelessWidget { @@ -149,19 +210,36 @@ At the moment, your app will be blank, because the `Tile` widget returns an empty `Container`, which doesn't display anything by default. +目前,你的应用会是空白的, +因为 `Tile` widget 返回空的 `Container`, +默认情况下不会显示任何内容。 + ### The `Container` widget +### `Container` widget + The `Tile` widget consists of three of the most common core widgets: `Container`, `Center`, and `Text`. [`Container`][] is a convenience widget that wraps several core styling widgets, such as [`Padding`][], [`ColoredBox`][], [`SizedBox`][], and [`DecoratedBox`][]. +`Tile` widget 由三个最常见的核心 widget 组成: +`Container`、`Center` 和 `Text`。 +[`Container`][] 是一个便捷 widget,封装了多个核心样式 widget, +例如 [`Padding`][]、[`ColoredBox`][]、[`SizedBox`][] 和 [`DecoratedBox`][]。 + Because the finished UI contains 25 `Tile` widgets in neat columns and rows, it should have an explicit size. Set the width and height properties on the `Container`. (You could also do this with a `SizedBox` widget, but you'll use more properties of the `Container` next.) +因为完成的 UI 包含 25 个整齐排列的 `Tile` widget, +它应有明确尺寸。 +在 `Container` 上设置 width 和 height 属性。 +(你也可以用 `SizedBox` widget 实现,但接下来你会用到 +`Container` 的更多属性。) + ```dart class Tile extends StatelessWidget { @@ -190,8 +268,12 @@ class Tile extends StatelessWidget { ### BoxDecoration +### BoxDecoration + Next, add a [`Border`][] to the box with the following code: +接下来,使用以下代码为方块添加 [`Border`][]: + ```dart class Tile extends StatelessWidget { @@ -222,20 +304,38 @@ In this case, you've added a border. When you hot reload, there should be a lightly colored border around the white square. +`BoxDecoration` 是一个知道如何 +为 widget 添加多种装饰的对象,从 +背景色到边框、盒子阴影等。 +此处,你添加了边框。 +热重载后,白色方块周围应出现 +浅色边框。 + When this game is complete, the color of the tile will depend on the user's guess. The tile will be green when the user has guessed correctly, yellow when the letter is correct but the position is incorrect, and gray if the guess is wrong in both respects. +游戏完成后, +方块颜色将取决于用户的猜测。 +用户猜对时方块为绿色, +字母正确但位置错误时为黄色, +字母和位置都错误时为灰色。 + The following figure shows all three possibilities. +下图展示了三种情况。 + A screenshot of a green, yellow, and grey tile. To achieve this in UI, use a [switch expression][] to set the `color` of the `BoxDecoration`. +要在 UI 中实现这一点,使用 [switch expression][] 设置 +`BoxDecoration` 的 `color`。 + ```dart class Tile extends StatelessWidget { @@ -269,13 +369,21 @@ class Tile extends StatelessWidget { ### Child widgets +### 子 widget + Finally, add the `Center` and `Text` widgets to the `Container.child` property. +最后,将 `Center` 和 `Text` widget 添加到 `Container.child` 属性。 + Most widgets in the Flutter SDK have a `child` or `children` property that's meant to be passed a widget or a list of widgets, respectively. It's the best practice to use the same naming convention in your own custom widgets. +Flutter SDK 中的大多数 widget 都有 `child` 或 `children` 属性, +分别用于传入 widget 或 widget 列表。 +在自己的自定义 widget 中使用相同的命名约定是最佳实践。 + ```dart class Tile extends StatelessWidget { @@ -312,6 +420,9 @@ class Tile extends StatelessWidget { Hot reload and a green box appears. To toggle the color, update and hot reload the `HitType` passed into the `Tile` you created: +热重载后会出现绿色方块。要切换颜色, +更新并热重载传入你创建的 `Tile` 的 `HitType`: + ```dart // main.dart line ~16 @@ -326,66 +437,73 @@ Tile('A', HitType.partial); Soon, this small box will be one of many widgets on the screen. In the next lesson, you'll start building the game grid itself. +很快,这个小方块将成为屏幕上众多 widget 之一。在下一课中, +你将开始构建游戏网格本身。 + ### Review +### 回顾 + -title: What you accomplished -subtitle: Here's a summary of what you built and learned in this lesson. +title: 你完成的内容 +subtitle: 以下是你本课构建与学习内容的摘要。 completed: true items: - - title: Built a custom StatelessWidget + - title: 构建了自定义 StatelessWidget icon: widgets details: >- - You created a new `Tile` widget by extending `StatelessWidget`. - Every widget has a constructor to accept data and - a `build` method that returns other widgets. - This pattern is fundamental to building user interfaces with Flutter. - - title: Made widgets reusable with constructor parameters + 你通过扩展 `StatelessWidget` 创建了新的 `Tile` widget。 + 每个 widget 都有用于接收数据的构造函数和 + 返回其他 widget 的 `build` 方法。 + 此模式是 Flutter 构建用户界面的基础。 + - title: 通过构造函数参数使 widget 可复用 icon: tune details: >- - By accepting `letter` and `hitType` as constructor parameters, - your `Tile` widget can display different content and colors. - Passing data through constructors is how you can - create flexible, reusable components. - - title: Styled widgets using Container and BoxDecoration + 通过将 `letter` 和 `hitType` 作为构造函数参数, + 你的 `Tile` widget 可以显示不同内容和颜色。 + 通过构造函数传递数据是 + 创建灵活、可复用组件的方式。 + - title: 使用 Container 和 BoxDecoration 为 widget 设置样式 icon: palette details: >- - You used `Container` to set the widget's size and - `BoxDecoration` to add borders and background colors. - Then to conditional style the tile's color, - you used a switch expression on the `hitType` value. + 你使用 `Container` 设置 widget 尺寸,使用 + `BoxDecoration` 添加边框和背景色。 + 然后通过对 `hitType` 值使用 switch 表达式 + 有条件地设置方块颜色。 ### Test yourself - -- question: "What must every Flutter widget's `build` method return?" +### 自测 + + +- question: "每个 Flutter widget 的 `build` 方法必须返回什么?" options: - - text: A String describing the widget. + - text: 描述 widget 的 String。 correct: false - explanation: "The `build` method returns a widget, not a String." - - text: Another widget. + explanation: "`build` 方法返回 widget,而非 String。" + - text: 另一个 widget。 correct: true - explanation: "The `build` method always returns another widget, which forms part of the widget tree." - - text: A boolean indicating success or failure. + explanation: "`build` 方法始终返回另一个 widget,该 widget 构成 widget 树的一部分。" + - text: 表示成功或失败的 boolean。 correct: false - explanation: Widgets don't indicate success; they return other widgets to be rendered. - - text: Null if there's nothing to display. + explanation: widget 不表示成功与否;它们返回要渲染的其他 widget。 + - text: 若无内容可显示则返回 null。 correct: false - explanation: "The `build` method cannot return null; it must return a valid widget." -- question: Which object is used to add decorations like borders, background colors, and shadows to a Container? + explanation: "`build` 方法不能返回 null;它必须返回有效的 widget。" +- question: 哪个对象用于向 Container 添加边框、背景色和阴影等装饰? options: - text: ThemeData correct: false - explanation: ThemeData is for app-wide styling, not individual container decorations. + explanation: ThemeData 用于应用级样式,而非单个 container 的装饰。 - text: TextStyle correct: false - explanation: TextStyle is for text formatting, not container decorations. + explanation: TextStyle 用于文本格式,而非 container 装饰。 - text: BoxDecoration correct: true - explanation: BoxDecoration can add borders, background colors, gradients, shadows, and more to a Container. + explanation: BoxDecoration 可为 Container 添加边框、背景色、渐变、阴影等。 - text: EdgeInsets correct: false - explanation: EdgeInsets is for specifying padding or margin, not visual decorations. + explanation: EdgeInsets 用于指定内边距或外边距,而非视觉装饰。 diff --git a/sites/docs/src/content/packages-and-plugins/index.md b/sites/docs/src/content/packages-and-plugins/index.md index d0d8d326bd..3f810d106a 100644 --- a/sites/docs/src/content/packages-and-plugins/index.md +++ b/sites/docs/src/content/packages-and-plugins/index.md @@ -1,6 +1,10 @@ --- layout: toc -title: Packages & plugins +# title: Packages & plugins +title: 包与插件 +# description: > +# Content covering using and developing packages and plugins for Flutter apps. description: > - Content covering using and developing packages and plugins for Flutter apps. + 涵盖在 Flutter 应用中使用和开发包与插件的内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md index 31c9274caa..0bb3dea23b 100644 --- a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md +++ b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md @@ -1,6 +1,9 @@ --- -title: Swift Package Manager for app developers -description: How to use Swift Package Manager for native iOS or macOS dependencies +# title: Swift Package Manager for app developers +title: 面向应用开发者的 Swift Package Manager +# description: How to use Swift Package Manager for native iOS or macOS dependencies +description: 如何使用 Swift Package Manager 管理 iOS 或 macOS 原生依赖 +ai-translated: true --- :::warning @@ -11,17 +14,31 @@ If you find a bug in Flutter's Swift Package Manager support, [open an issue][]. Swift Package Manager support is [off by default][]. Flutter continues to support CocoaPods. + +Flutter 正在迁移到 [Swift Package Manager][] 以管理 iOS 和 macOS 原生依赖。 +Flutter 对 Swift Package Manager 的支持仍在开发中。 +若发现 Flutter 的 Swift Package Manager 支持存在 bug, +请 [open an issue][](提交 issue)。 +Swift Package Manager 支持默认 [off by default][](关闭)。 +Flutter 仍继续支持 CocoaPods。 ::: Flutter's Swift Package Manager integration has several benefits: +Flutter 的 Swift Package Manager 集成带来多项好处: + 1. **Provides access to the Swift package ecosystem**. Flutter plugins can use the growing ecosystem of [Swift packages][]. + +1. **可访问 Swift 包生态**。Flutter 插件可使用不断增长的 [Swift packages][](Swift 包)生态。 + 1. **Simplifies Flutter installation**. Xcode includes Swift Package Manager. You don't need to install Ruby and CocoaPods if your project uses Swift Package Manager. +2. **简化 Flutter 安装**。Xcode 已包含 Swift Package Manager。若项目使用 Swift Package Manager,则无需安装 Ruby 和 CocoaPods。 + [Swift Package Manager]: https://www.swift.org/documentation/package-manager/ [off by default]: #how-to-turn-on-swift-package-manager [Swift packages]: https://swiftpackageindex.com/ @@ -31,8 +48,12 @@ Flutter's Swift Package Manager integration has several benefits: ## How to add Swift Package Manager integration +## 如何添加 Swift Package Manager 集成 + ### Add to a Flutter app +### 添加到 Flutter 应用 + @@ -48,6 +69,8 @@ Flutter's Swift Package Manager integration has several benefits: ### Add to a Flutter app _manually_ +### 手动添加到 Flutter 应用 + @@ -63,27 +86,44 @@ Flutter's Swift Package Manager integration has several benefits: ### Add to an existing app (add-to-app) +### 添加到现有应用(add-to-app) + Flutter's Swift Package Manager support doesn't work with add-to-app scenarios. +Flutter 的 Swift Package Manager 支持不适用于 add-to-app 场景。 + To keep current on status updates, consult [flutter#146957][]. +要了解状态更新,请参阅 [flutter#146957][]。 + [flutter#146957]: https://github.com/flutter/flutter/issues/146957 ### Add to a custom Xcode target +### 添加到自定义 Xcode target + Your Flutter Xcode project can have custom [Xcode targets][] to build additional products, like frameworks or unit tests. You can add Swift Package Manager integration to these custom Xcode targets. +Flutter Xcode 项目可有自定义 [Xcode targets][] 以构建框架或单元测试等产品。你可以为这些自定义 Xcode target 添加 Swift Package Manager 集成。 + Follow the steps in [How to add Swift Package Manager integration to a project _manually_][manualIntegration]. +请按照 +[How to add Swift Package Manager integration to a project _manually_][manualIntegration](如何手动为项目添加 Swift Package Manager 集成)中的步骤操作。 + In [Step 1][manualIntegrationStep1], list item 6 use your custom target instead of the `Flutter` target. +在 [Step 1][manualIntegrationStep1] 的第 6 项中,使用你的自定义 target 而非 `Flutter` target。 + In [Step 2][manualIntegrationStep2], list item 6 use your custom target instead of the `Flutter` target. +在 [Step 2][manualIntegrationStep2] 的第 6 项中,使用你的自定义 target 而非 `Flutter` target。 + [Xcode targets]: https://developer.apple.com/documentation/xcode/configuring-a-new-target-in-your-project [manualIntegration]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-add-swift-package-manager-integration-to-a-flutter-app-manually [manualIntegrationStep1]: /packages-and-plugins/swift-package-manager/for-app-developers/#step-1-add-fluttergeneratedpluginswiftpackage-package-dependency @@ -91,15 +131,25 @@ of the `Flutter` target. ## How to remove Swift Package Manager integration +## 如何移除 Swift Package Manager 集成 + To add Swift Package Manager integration, the Flutter CLI migrates your project. This migration updates your Xcode project to add Flutter plugin dependencies. +添加 Swift Package Manager 集成时,Flutter CLI 会迁移你的项目,更新 Xcode 项目以添加 Flutter 插件依赖。 + To undo this migration: +要撤销此迁移: + 1. [Turn off Swift Package Manager][]. +1. [Turn off Swift Package Manager][](关闭 Swift Package Manager)。 + 1. Clean your project: +1. 清理项目: + ```sh flutter clean ``` @@ -107,56 +157,99 @@ To undo this migration: 1. Open your app (`ios/Runner.xcworkspace` or `macos/Runner.xcworkspace`) in Xcode. +1. 在 Xcode 中打开应用(`ios/Runner.xcworkspace` 或 `macos/Runner.xcworkspace`)。 + 1. Navigate to **Package Dependencies** for the project. +1. 导航到项目的 **Package Dependencies**(包依赖)。 + 1. Click the `FlutterGeneratedPluginSwiftPackage` package, then click the button. +1. 点击 `FlutterGeneratedPluginSwiftPackage` 包,再点击 + 按钮。 + + + 1. Navigate to **Frameworks, Libraries, and Embedded Content** for the `Runner` target. +1. 导航到 `Runner` target 的 **Frameworks, Libraries, and Embedded Content**(框架、库与嵌入内容)。 + 1. Click `FlutterGeneratedPluginSwiftPackage`, then click the button. +1. 点击 `FlutterGeneratedPluginSwiftPackage`,再点击 + 按钮。 + + + 1. Go to **Product > Scheme > Edit Scheme**. +1. 前往 **Product > Scheme > Edit Scheme**(编辑 Scheme)。 + 1. Expand the **Build** section in the left side bar. +1. 展开左侧边栏的 **Build**(构建)部分。 + 1. Click **Pre-actions**. +1. 点击 **Pre-actions**(预操作)。 + 1. Expand **Run Prepare Flutter Framework Script**. +1. 展开 **Run Prepare Flutter Framework Script**。 + 1. Click the button. +1. 点击 按钮。 + + + [Turn off Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-off-swift-package-manager ## How to use a Swift Package Manager Flutter plugin that requires a higher OS version +## 如何使用需要更高 OS 版本的 Swift Package Manager Flutter 插件 + If a Swift Package Flutter Manager plugin requires a higher OS version than the project, you might get an error like this: +若 Swift Package Manager Flutter 插件要求的 OS 版本高于项目,你可能会看到类似错误: + ```plaintext Target Integrity (Xcode): The package product 'plugin_name_ios' requires minimum platform version 14.0 for the iOS platform, but this target supports 12.0 ``` To use the plugin: +要使用该插件: + 1. Open your app (`ios/Runner.xcworkspace` or `macos/Runner.xcworkspace`) in Xcode. +1. 在 Xcode 中打开应用(`ios/Runner.xcworkspace` 或 `macos/Runner.xcworkspace`)。 + 1. Increase your app's target **Minimum Deployments**. +1. 提高应用 target 的 **Minimum Deployments**(最低部署版本)。 + + + 1. If you updated your iOS app's **Minimum Deployments**, regenerate the iOS project's configuration files: +1. 若你更新了 iOS 应用的 **Minimum Deployments**, + 请重新生成 iOS 项目配置文件: + ```sh flutter build ios --config-only ``` @@ -164,6 +257,9 @@ To use the plugin: 1. If you updated your macOS app's **Minimum Deployments**, regenerate the macOS project's configuration files: +1. 若你更新了 macOS 应用的 **Minimum Deployments**, + 请重新生成 macOS 项目配置文件: + ```sh flutter build macos --config-only ``` diff --git a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md index 037ca5f7b7..8000460720 100644 --- a/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md +++ b/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md @@ -1,6 +1,9 @@ --- -title: Swift Package Manager for plugin authors -description: How to add Swift Package Manager compatibility to iOS and macOS plugins +# title: Swift Package Manager for plugin authors +title: 面向插件作者的 Swift Package Manager +# description: How to add Swift Package Manager compatibility to iOS and macOS plugins +description: 如何为 iOS 和 macOS 插件添加 Swift Package Manager 兼容性 +ai-translated: true --- :::warning @@ -11,17 +14,31 @@ If you find a bug in Flutter's Swift Package Manager support, [open an issue][]. Swift Package Manager support is [off by default][]. Flutter continues to support CocoaPods. + +Flutter 正在迁移到 [Swift Package Manager][] 以管理 iOS 和 macOS 原生依赖。 +Flutter 对 Swift Package Manager 的支持仍在开发中。 +若发现 Flutter 的 Swift Package Manager 支持存在 bug, +请 [open an issue][](提交 issue)。 +Swift Package Manager 支持默认 [off by default][](关闭)。 +Flutter 仍继续支持 CocoaPods。 ::: Flutter's Swift Package Manager integration has several benefits: +Flutter 的 Swift Package Manager 集成带来多项好处: + 1. **Provides access to the Swift package ecosystem**. Flutter plugins can use the growing ecosystem of [Swift packages][]! + +1. **可访问 Swift 包生态**。Flutter 插件可使用不断增长的 [Swift packages][](Swift 包)生态! + 1. **Simplifies Flutter installation**. Swift Package Manager is bundled with Xcode. - In the future, you won’t need to install Ruby and CocoaPods to target iOS or + In the future, you won't need to install Ruby and CocoaPods to target iOS or macOS. +2. **简化 Flutter 安装**。Swift Package Manager 随 Xcode 捆绑提供。未来 targeting iOS 或 macOS 时可能无需再安装 Ruby 和 CocoaPods。 + [Swift Package Manager]: https://www.swift.org/documentation/package-manager/ [off by default]: #how-to-turn-on-swift-package-manager @@ -32,19 +49,27 @@ Flutter's Swift Package Manager integration has several benefits: ## How to add Swift Package Manager support to an existing Flutter plugin +## 如何为现有 Flutter 插件添加 Swift Package Manager 支持 + This guide shows how to add Swift Package Manager support to a plugin that already supports CocoaPods. This ensures the plugin is usable by all Flutter projects. +本指南说明如何为已支持 CocoaPods 的插件添加 Swift Package Manager 支持,确保所有 Flutter 项目都能使用该插件。 + Flutter plugins should support _both_ Swift Package Manager and CocoaPods until further notice. +在另行通知前,Flutter 插件应 _同时_ 支持 Swift Package Manager 和 CocoaPods。 + Swift Package Manager adoption will be gradual. Plugins that don't support CocoaPods won't be usable by projects that haven't migrated to Swift Package Manager yet. Plugins that don't support Swift Package Manager can cause problems for projects that have migrated. +Swift Package Manager 的采用将逐步推进。不支持 CocoaPods 的插件将无法用于尚未迁移到 Swift Package Manager 的项目。不支持 Swift Package Manager 的插件会给已迁移的项目带来问题。 + @@ -61,71 +86,121 @@ that have migrated. ## (Optional, but Recommended) Add plugin as local package in example app +## (可选但推荐)在示例应用中将插件添加为本地包 + If your plugin includes an example, it is recommended to add the plugin as a local package in the example app. This is not required, but provides better Xcode support when editing the plugin's source code in the example app. See [issue #179032](https://github.com/flutter/flutter/issues/179032). +若插件包含示例,建议在示例应用中将插件添加为本地包。非必须,但在示例应用中编辑插件源码时能提供更好的 Xcode 支持。请参阅 [issue #179032](https://github.com/flutter/flutter/issues/179032)。 + ### Add plugin as local package +### 将插件添加为本地包 + 1. In a terminal navigate to `my_plugin`. +1. 在终端中进入 `my_plugin`。 + 1. Run the following command to open the example app's workspace in Xcode, (replace `ios` with `macos` if your plugin targets macOS): +1. 运行以下命令在 Xcode 中打开示例应用的工作区(若插件面向 macOS,将 `ios` 替换为 `macos`): + ```bash open example/ios/Runner.xcworkspace ``` -1. Right click **Flutter** > **Add Files to “Runner”**. +1. Right click **Flutter** > **Add Files to "Runner"**. + +1. 右键 **Flutter** > **Add Files to "Runner"**(添加文件到 Runner)。 ![Add Files to Runner](/assets/images/docs/development/packages-and-plugins/swift-package-manager/add-files-to-runner.png) 1. Select `my_plugin/ios/my_plugin` (or `macos` or `darwin` depending on what platforms your plugin supports). -1. Make sure “Reference files in place” is selected (it should be the default), and click **Finish**. +1. 选择 `my_plugin/ios/my_plugin`(或根据插件支持的平台选择 `macos` 或 `darwin`)。 + +1. Make sure "Reference files in place" is selected (it should be the default), and click **Finish**. + +1. 确保选中「Reference files in place」(引用文件位置,通常为默认),然后点击 **Finish**(完成)。 ![Select Reference files in place](/assets/images/docs/development/packages-and-plugins/swift-package-manager/reference-files-in-place.png) This adds the plugin as a local package, but it will be referenced by absolute path, which is not desirable for distribution. To change it to a relative path, follow the steps below. +这样会将插件添加为本地包,但会以绝对路径引用,不利于分发。要改为相对路径,请按以下步骤操作。 + ### Change to relative path -1. Copy “Full Path” for plugin from the File Inspector. +### 改为相对路径 + +1. Copy "Full Path" for plugin from the File Inspector. + +1. 从文件检查器复制插件的「Full Path」(完整路径)。 ![Copy Full Path](/assets/images/docs/development/packages-and-plugins/swift-package-manager/copy-full-path.png) 1. In terminal: `open -a Xcode example/ios/Runner.xcodeproj/project.pbxproj` +1. 在终端执行: + `open -a Xcode example/ios/Runner.xcodeproj/project.pbxproj` + 1. Find the following: + +1. 找到以下内容: + ```text path = [COPIED FULL PATH]; sourceTree = "" ``` For example: + 例如: + ```text path = /Users/username/path/to/my_plugin/ios/my_plugin; sourceTree = "" ``` 1. And replace with relative path: + +1. 并替换为相对路径: + ```text path = ../../ios/my_plugin; sourceTree = "" ``` (Adjust `ios` to `macos` or `darwin` as needed). + (按需将 `ios` 调整为 `macos` 或 `darwin`。) + ## How to update unit tests in a plugin's example app +## 如何更新插件示例应用中的单元测试 + If your plugin has native XCTests, you might need to update them to work with Swift Package Manager if one of the following is true: +若插件有原生 XCTests,在以下任一情况下可能需要更新以配合 Swift Package Manager: + * You're using a CocoaPod dependency for the test. + + 测试使用了 CocoaPod 依赖。 + * Your plugin is explicitly set to `type: .dynamic` in its `Package.swift` file. + 插件在 `Package.swift` 中显式设为 `type: .dynamic`。 + To update your unit tests: +要更新单元测试: + 1. Open your `example/ios/Runner.xcworkspace` in Xcode. +1. 在 Xcode 中打开 `example/ios/Runner.xcworkspace`。 + 1. If you were using a CocoaPod dependency for tests, such as `OCMock`, you'll want to remove it from your `Podfile` file. +1. 若测试曾使用 CocoaPod 依赖(如 `OCMock`),请从 `Podfile` 中移除。 + ```ruby title="ios/Podfile" diff target 'RunnerTests' do inherit! :search_paths @@ -137,55 +212,98 @@ To update your unit tests: Then in the terminal, run `pod install` in the `plugin_name_ios/example/ios` directory. + 然后在终端于 `plugin_name_ios/example/ios` 目录运行 `pod install`。 + 1. Navigate to **Package Dependencies** for the project. +1. 导航到项目的 **Package Dependencies**(包依赖)。 + + + 1. Click the **+** button and add any test-only dependencies by searching for them in the top right search bar. +1. 点击 **+** 按钮,在右上角搜索栏搜索并添加仅用于测试的依赖。 + + + :::note OCMock uses unsafe build flags and can only be used if targeted by commit. `fe1661a3efed11831a6452f4b1a0c5e6ddc08c3d` is the commit for the 3.9.3 version. ::: + :::note + OCMock 使用不安全的构建标志,只能通过指定 commit 使用。 + `fe1661a3efed11831a6452f4b1a0c5e6ddc08c3d` 为 3.9.3 版本对应的 commit。 + ::: + 1. Ensure the dependency is added to the `RunnerTests` Target. +1. 确保依赖已添加到 `RunnerTests` Target。 + + + 1. Click the **Add Package** button. +1. 点击 **Add Package**(添加包)按钮。 + 1. If you've explicitly set your plugin's library type to `.dynamic` in its `Package.swift` file ([not recommended by Apple][library type recommendations]), you'll also need to add it as a dependency to the `RunnerTests` target. +1. 若在 `Package.swift` 中将插件库类型显式设为 `.dynamic`([Apple 不推荐][library type recommendations]),还需将其添加为 `RunnerTests` target 的依赖。 + 1. Ensure `RunnerTests` **Build Phases** has a **Link Binary With Libraries** build phase: + 确保 `RunnerTests` 的 **Build Phases**(构建阶段)包含 **Link Binary With Libraries**(链接二进制与库)构建阶段: + + + If the build phase doesn't exist already, create one. Click the button and then click **New Link Binary With Libraries Phase**. + 若尚无该构建阶段,请创建:点击 按钮,再点击 **New Link Binary With Libraries Phase**。 + + + 1. Navigate to **Package Dependencies** for the project. + 导航到项目的 **Package Dependencies**。 + 1. Click the button. + 点击 按钮。 + 1. In the dialog that opens, click the **Add Local...** button. + 在打开的对话框中点击 **Add Local...**(添加本地)按钮。 + 1. Navigate to `plugin_name/plugin_name_ios/ios/plugin_name_ios` and click the **Add Package** button. + 导航到 `plugin_name/plugin_name_ios/ios/plugin_name_ios` 并点击 **Add Package** 按钮。 + 1. Ensure that it's added to the `RunnerTests` target and click the **Add Package** button. + 确保已添加到 `RunnerTests` target 并点击 **Add Package** 按钮。 + 1. Ensure tests pass **Product > Test**. +1. 确保 **Product > Test**(测试)通过。 + [library type recommendations]: https://developer.apple.com/documentation/packagedescription/product/library(name:type:targets:) diff --git a/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md b/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md index 26f868c289..a9e786bba4 100644 --- a/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md +++ b/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md @@ -1,6 +1,10 @@ --- layout: toc -title: Swift Package Manager for Flutter +# title: Swift Package Manager for Flutter +title: Flutter 的 Swift Package Manager +# description: > +# Content covering Flutter's integration with Swift Package Manager description: > - Content covering Flutter's integration with Swift Package Manager + 涵盖 Flutter 与 Swift Package Manager 集成的内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/perf/appendix.md b/sites/docs/src/content/perf/appendix.md index 6d69a88823..e1efe55c3c 100644 --- a/sites/docs/src/content/perf/appendix.md +++ b/sites/docs/src/content/perf/appendix.md @@ -1,16 +1,25 @@ --- -title: More thoughts about performance -description: What is performance, and why is performance important +# title: More thoughts about performance +title: 关于性能的更多思考 +# description: What is performance, and why is performance important +description: 什么是性能,以及性能为何重要 +ai-translated: true --- ## What is performance? +## 什么是性能? + Performance is a set of quantifiable properties of a performer. +性能是执行者(performer)的一组可量化属性。 + In this context, performance isn't the execution of an action itself; it's how well something or someone performs. Therefore, we use the adjective _performant_. +在此语境下,性能不是动作本身的执行,而是某物或某人表现得好不好。因此我们使用形容词 _performant_(高性能的)。 + While the _how well_ part can, in general, be described in natural languages, in our limited scope, the focus is on something that is quantifiable as a real number. Real numbers include integers and 0/1 binaries as special cases. @@ -20,65 +29,107 @@ without any numbers (a quantifiable value) could still be meaningful, and it could have great impacts. The limited scope is chosen only because of our limited resources. +虽然「多好」一般可用自然语言描述,但在我们有限的范围内,焦点放在可量化为实数的指标上。实数包括整数和 0/1 二进制等特殊情形。自然语言描述仍然很重要。例如,一篇仅用文字、没有任何数字(可量化值)却大力批评 Flutter 性能的新闻文章仍可能有意义并产生重大影响。范围有限仅因资源有限。 + The required quantity to describe performance is often referred to as a metric. +描述性能所需的量通常称为 metric(指标)。 + To navigate through countless performance issues and metrics, you can categorize based on performers. +要在无数性能问题与指标中导航,可按执行者分类。 + For example, most of the content on this website is about the Flutter app performance, where the performer is a Flutter app. Infra performance is also important to Flutter, where the performers are build bots and CI task runners: they heavily affect how fast Flutter can incorporate code changes, to improve the app's performance. +例如,本站大部分内容关于 Flutter 应用性能,执行者是 Flutter 应用。基础设施性能对 Flutter 也很重要,执行者是构建 bot 和 CI 任务运行器:它们严重影响 Flutter 融入代码变更的速度,从而影响应用性能。 + Here, the scope was intentionally broadened to include performance issues other than just app performance issues because they can share many tools regardless of who the performers are. For example, Flutter app performance and infra performance might share the same dashboard and similar alert mechanisms. +此处有意扩大范围,纳入应用性能以外的问题,因为无论执行者是谁,它们可共享许多工具。例如 Flutter 应用性能与基础设施性能可能共用同一仪表板和类似的告警机制。 + Broadening the scope also allows performers to be included that traditionally are easy to ignore. Document performance is such an example. The performer could be an API doc of the SDK, and a metric could be: the percentage of readers who find the API doc useful. +扩大范围还可纳入传统上易被忽视的执行者。文档性能即是一例:执行者可以是 SDK 的 API 文档,指标可以是:认为 API 文档有用的读者百分比。 + ## Why is performance important? +## 性能为何重要? + Answering this question is not only crucial for validating the work in performance, but also for guiding the performance work in order to be more useful. The answer to "why is performance important?" often is also the answer to "how is performance useful?" +回答此问题不仅对验证性能工作至关重要,也有助于指导性能工作使其更有用。「性能为何重要?」的答案往往也是「性能如何有用?」的答案。 + Simply speaking, performance is important and useful because, in the scope, performance must have quantifiable properties or metrics. This implies: + +简而言之,性能重要且有用,因为在该范围内性能必须有可量化属性或指标。这意味着: + 1. A performance report is easy to consume. + +1. 性能报告易于阅读。 + 2. Performance has little ambiguity. + +2. 性能歧义少。 + 3. Performance is comparable and convertible. + +3. 性能可比较、可转换。 + 4. Performance is fair. +4. 性能是公平的。 + Not that non-performance, or non-measurable issues or descriptions are not important. They're meant to highlight the scenarios where performance can be more useful. +并非说非性能、不可衡量的问题或描述不重要。此处意在突出性能更有用的场景。 + ### 1. A performance report is easy to consume +### 1. 性能报告易于阅读 + Performance metrics are numbers. Reading a number is much easier than reading a passage. For example, it probably takes an engineer 1 second to consume the performance rating as a number from 1 to 5. It probably takes the same engineer at least 1 minute to read the full, 500-word feedback summary. +性能指标是数字。读数字比读段落容易得多。例如工程师可能用 1 秒读完 1 到 5 的性能评分,却至少需要 1 分钟读完 500 字的完整反馈摘要。 + If there are many numbers, it's easy to summarize or visualize them for quick consumption. For example, you can quickly consume millions of numbers by looking at its histogram, average, quantiles, and so on. If a metric has a history of thousands of data points, then you can easily plot a timeline to read its trend. +若有许多数字,易于汇总或可视化以便快速阅读。例如可通过直方图、平均值、分位数等快速理解数百万个数字。若某指标有数千个历史数据点,可轻松绘制时间线观察趋势。 + On the other hand, having _n_ number of 500-word texts almost guarantees an _n_-time cost to consume those texts. It would be a daunting task to analyze thousands of historical descriptions, each having 500 words. +另一方面,_n_ 篇各 500 字的文本几乎保证需要 _n_ 倍时间阅读。分析数千条各 500 字的历史描述将是艰巨任务。 + ### 2. Performance has little ambiguity +### 2. 性能歧义少 + Another advantage of having performance as a set of numbers is its unambiguity. When you want an animation to have a performance of 20 ms per frame or 50 fps, there's little room for different interpretations about the numbers. On @@ -88,6 +139,8 @@ word or phrase could be interpreted differently by different people. You might interpret an OK frame rate to be 60 fps, while someone else might interpret it to be 30 fps. +将性能表示为一组数字的另一优势是歧义少。当你要求动画达到每帧 20 ms 或 50 fps 时,对数字的解读空间很小。而用文字描述同一动画时,有人可能说好,有人可能说差。同样,同一词语不同人理解不同:你可能认为可接受的帧率是 60 fps,他人可能认为是 30 fps。 + Numbers can still be noisy. For example, the measured time per frame might be a true computation time of this frame, plus a random amount of time (noise) that CPU/GPU spends on some unrelated work. Hence, the metric fluctuates. @@ -97,8 +150,12 @@ could take multiple measurements to estimate the distribution of a random variable, or you could take the average of many measurements to eliminate the noise by [the law of large numbers][1]. +数字仍可能有噪声。例如测得的每帧时间可能是该帧的真实计算时间加上 CPU/GPU 在无关工作上花费的随机时间(噪声),因此指标会波动。但数字含义并无歧义,且有严谨理论和测试工具处理噪声。例如可多次测量估计随机变量分布,或通过 [the law of large numbers][1](大数定律)对多次测量取平均以消除噪声。 + ### 3. Performance is comparable and convertible +### 3. 性能可比较、可转换 + Performance numbers not only have unambiguous meanings, but they also have unambiguous comparisons. For example, there's no doubt that 5 is greater than 4. On the other hand, it might be subjective to figure out whether excellent is @@ -108,6 +165,8 @@ could be better than _superb_ in someone's interpretation. It only becomes unambiguous and comparable after a definition that maps strongly exceeds expectations to 4 and superb to 5. +性能数字不仅含义明确,比较也明确。例如 5 大于 4 毫无疑问。而判断 excellent 是否优于 superb 可能很主观。epic 是否优于 legendary?实际上 _strongly exceeds expectations_ 在某人理解中可能优于 _superb_。只有定义将 strongly exceeds expectations 映射为 4、superb 映射为 5 后才变得明确可比较。 + Numbers are also easily convertible using formulas and functions. For example, 60 fps can be converted to 16.67 ms per frame. A frame's rendering time _x_ (ms) can be converted to a binary indicator @@ -117,40 +176,61 @@ measurement without any added noise or ambiguity. The converted quantity can then be used for further comparisons and consumption. Such conversions are almost impossible if you're dealing with natural languages. +数字也易于用公式和函数转换。例如 60 fps 可转为每帧 16.67 ms。帧渲染时间 _x_(ms)可转为二元指标 `isSmooth = [x <= 16] = (x <= 16 ? 1 :0)`。此类转换可复合或链式进行,用单次测量得到多种量而无额外噪声或歧义,再用于进一步比较与分析。自然语言几乎无法实现此类转换。 + ### 4. Performance is fair +### 4. 性能是公平的 + If issues rely on verbose words to be discovered, then an unfair advantage is given to people who are more verbose (more willing to chat or write) or those who are closer to the development team, who have a larger bandwidth and lower cost for chatting or face-to-face meetings. +若问题依赖冗长文字才能被发现,则更健谈(更愿意聊天或写作)或更接近开发团队、拥有更大沟通带宽和更低面对面会议成本的人会获得不公平优势。 + By having the same metrics to detect problems no matter how far away or how silent the users are, we can treat all issues fairly. That, in turn, allows us to focus on the right issues that have greater impact. +无论用户多远或多沉默,用相同指标发现问题,可公平对待所有问题,从而聚焦影响更大的正确问题。 + ### How to make performance useful +### 如何让性能更有用 + The following summarizes the 4 points discussed here, from a slightly different perspective: + +以下从不同角度总结前述 4 点: + 1. Make performance metrics easy to consume. Do not overwhelm the readers with a lot of numbers (or words). If there are many numbers, then try to summarize them into a smaller set of numbers (for example, summarize many numbers into a single average number). Only notify readers when the numbers change significantly (for example, automatic alerts on spikes or regressions). +1. 让性能指标易于阅读。不要用大量数字(或文字)淹没读者。若数字很多,尽量汇总为更少的数字(例如汇总为单个平均值)。仅在数字显著变化时通知读者(例如尖峰或回归时自动告警)。 + 2. Make performance metrics as unambiguous as possible. Define the unit that the number is using. Precisely describe how the number is measured. Make the number easily reproducible. When there's a lot of noise, try to show the full distribution, or eliminate the noise as much as possible by aggregating many noisy measurements. +2. 尽量消除性能指标的歧义。定义数字使用的单位,精确描述测量方式,使数字易于复现。噪声大时尽量展示完整分布,或通过聚合多次噪声测量尽可能消除噪声。 + 3. Make it easy to compare performance. For example, provide a timeline to compare the current version with the old version. Provide ways and tools to convert one metric to another. For example, if we can convert both memory increase and fps drops into the number of users dropped or revenue lost in dollars, then we can compare them and make an informed trade-off. +3. 便于比较性能。例如提供时间线比较当前版与旧版,提供将一种指标转换为另一种的方式和工具。例如若将内存增长和 fps 下降都转换为用户流失数或美元收入损失,即可比较并做出明智权衡。 + 4. Make performance metrics monitor a population that is as wide as possible, so no one is left behind. +4. 让性能指标监控尽可能广泛的人群,以免有人被遗漏。 + [1]: https://en.wikipedia.org/wiki/Law_of_large_numbers diff --git a/sites/docs/src/content/perf/impeller.md b/sites/docs/src/content/perf/impeller.md index 036424d801..90b0ccb02b 100644 --- a/sites/docs/src/content/perf/impeller.md +++ b/sites/docs/src/content/perf/impeller.md @@ -1,6 +1,9 @@ --- -title: Impeller rendering engine -description: What is Impeller and how to enable it? +# title: Impeller rendering engine +title: Impeller 渲染引擎 +# description: What is Impeller and how to enable it? +description: 什么是 Impeller,如何启用? +ai-translated: true --- :::note @@ -8,65 +11,109 @@ As of the 3.27 release, Impeller is the default rendering engine for both iOS and Android API 29+. To see _detailed_ info on where Impeller is currently supported, check out the [Can I use Impeller?][] page. + +自 3.27 版本起,Impeller 是 iOS 以及 Android API 29+ 的默认渲染引擎。 +要了解 Impeller 当前支持的 _详细_ 信息, +请参阅 [Can I use Impeller?][] 页面。 ::: [Can I use Impeller?]: {{site.main-url}}/go/can-i-use-impeller ## What is Impeller? +## 什么是 Impeller? + Impeller provides a new rendering runtime for Flutter. Impeller precompiles a [smaller, simpler set of shaders][] at engine-build time so they don't compile at runtime. +Impeller 为 Flutter 提供新的渲染运行时。Impeller 在引擎构建时预编译 [smaller, simpler set of shaders][](更小、更简单的着色器集),避免在运行时编译。 + [smaller, simpler set of shaders]: {{site.repo.flutter}}/issues/77412 For a video introduction to Impeller, check out the following talk from Google I/O 2023. +要了解 Impeller 的视频介绍,可观看 Google I/O 2023 的以下演讲。 + Impeller has the following objectives: +Impeller 的目标包括: + * **Predictable performance**: Impeller compiles all shaders and reflection offline at build time. It builds all pipeline state objects upfront. The engine controls caching and caches explicitly. + + **可预测的性能**:Impeller 在构建时离线编译所有着色器与反射,预先构建所有管线状态对象,由引擎显式控制缓存。 + * **Instrumentable**: Impeller tags and labels all graphics resources, such as textures and buffers. It can capture and persist animations to disk without affecting per-frame rendering performance. + + + **可观测**:Impeller 为纹理、缓冲区等图形资源打标签,可在不影响每帧渲染性能的情况下捕获动画并持久化到磁盘。 + * **Portable**: Flutter doesn't tie Impeller to a specific client-rendering API. You can author shaders once and convert them to backend-specific formats, as necessary. + + + **可移植**:Flutter 不将 Impeller 绑定到特定客户端渲染 API,可一次编写着色器并按需转换为后端格式。 + * **Leverages modern graphics APIs**: Impeller uses, but doesn't depend on, features available in modern APIs like Metal and Vulkan. + + + **利用现代图形 API**:Impeller 使用 Metal、Vulkan 等现代 API 的特性,但不依赖它们。 + * **Leverages concurrency**: Impeller can distribute single-frame workloads across multiple threads, if necessary. + + **利用并发**:必要时 Impeller 可将单帧工作负载分布到多个线程。 + ## Availability +## 可用性 + Where can you use Impeller? For _detailed_ info, check out the [Can I use Impeller?][] page. +在哪里可以使用 Impeller?_详细_ 信息请参阅 [Can I use Impeller?][] 页面。 + ### iOS +### iOS 平台 + Impeller is the **only supported** rendering engine on iOS with no ability to switch to Skia. +Impeller 是 iOS 上 **唯一支持** 的渲染引擎,无法切换回 Skia。 + ### Android +### Android 平台 + Impeller is **available and enabled by default on Android API 29+**. On devices running lower versions of Android or don't support Vulkan, Impeller falls back to the legacy OpenGL renderer. No action on your part is necessary for this fallback behavior. +Impeller 在 **Android API 29+ 上默认可用并已启用**。在较低 Android 版本或不支持 Vulkan 的设备上,Impeller 会回退到旧版 OpenGL 渲染器。你无需为此回退行为做任何操作。 + * To _disable_ Impeller when debugging, pass `--no-enable-impeller` to the `flutter run` command. + 调试时 _禁用_ Impeller,向 `flutter run` 传入 `--no-enable-impeller`。 + ```console flutter run --no-enable-impeller ``` @@ -75,6 +122,8 @@ No action on your part is necessary for this fallback behavior. add the following setting to your project's `AndroidManifest.xml` file under the `` tag: + 部署应用时 _禁用_ Impeller,在项目的 `AndroidManifest.xml` 中 `` 标签下添加以下设置: + ```xml ` tag in your app's `Info.plist` file. +macOS 部署时启用 Impeller,在应用 `Info.plist` 顶层 `` 标签下添加以下内容: + ```xml FLTEnableImpeller @@ -113,6 +174,8 @@ add the following tags under the top-level ### Bugs and issues +### Bug 与 issue + The team continues to improve Impeller support. If you encounter performance or fidelity issues with Impeller on any platform, @@ -120,37 +183,78 @@ file an issue in the [GitHub tracker][file-issue]. Prefix the issue title with `[Impeller]` and include a small reproducible test case. +团队持续改进 Impeller 支持。若在任何平台上遇到 Impeller 的性能或保真度问题,请在 [GitHub tracker][file-issue] 提交 issue,标题以 `[Impeller]` 为前缀,并附上可复现的小型测试用例。 + Please include the following information when submitting an issue for Impeller: +提交 Impeller 相关 issue 时请包含以下信息: + * The device you are running on, including the chip information. + + 运行设备,包括芯片信息。 + * Screenshots or recordings of any visible issues. + + 任何可见问题的截图或录屏。 + * An [export of the performance trace][]. Zip the file and attach it to the GitHub issue. + [export of the performance trace][](性能跟踪导出)。将文件 zip 后附在 GitHub issue 中。 + [export of the performance trace]:/tools/devtools/performance#import-and-export [file-issue]: {{site.github}}/flutter/flutter/issues/new/choose [Impeller project board]: {{site.github}}/orgs/flutter/projects/21 ## Architecture +## 架构 + To learn more details about Impeller's design and architecture, check out the [README.md][] file in the source tree. +要了解 Impeller 设计与架构的更多细节,请参阅源码树中的 [README.md][] 文件。 + [README.md]: {{site.repo.flutter}}/blob/main/engine/src/flutter/impeller/README.md ## Additional information +## 更多信息 + * [Frequently asked questions][impeller-faq] + + [常见问题][impeller-faq] + * [Impeller's coordinate system][impeller-coords] + + [Impeller 坐标系][impeller-coords] + * [How to set up Xcode for GPU frame captures with metal][impeller-xcode-capture] + + [如何使用 Xcode 通过 Metal 进行 GPU 帧捕获][impeller-xcode-capture] + * [Learning to read GPU frame captures][impeller-read-capture] + + [学习阅读 GPU 帧捕获][impeller-read-capture] + * [How to enable metal validation for command line apps][impeller-metal-validation] + + [如何为命令行应用启用 Metal 验证][impeller-metal-validation] + * [How Impeller works around the lack of uniform buffers in Open GL ES 2.0][impeller-ubo-gles2] + + [Impeller 如何应对 Open GL ES 2.0 缺少 uniform buffer 的问题][impeller-ubo-gles2] + * [Guidance for writing efficient shaders][impeller-shader-optimization] + + [编写高效着色器的指南][impeller-shader-optimization] + * [How color blending works in Impeller][impeller-blending] + [Impeller 中的颜色混合原理][impeller-blending] + [impeller-faq]: {{site.repo.flutter}}/blob/main/docs/engine/impeller/docs/faq.md [impeller-coords]: {{site.repo.flutter}}/blob/main/docs/engine/impeller/docs/coordinate_system.md [impeller-xcode-capture]: {{site.repo.flutter}}/blob/main/docs/engine/impeller/docs/xcode_frame_capture.md diff --git a/sites/docs/src/content/perf/isolates.md b/sites/docs/src/content/perf/isolates.md index 19b886ddcf..915c9f12f9 100644 --- a/sites/docs/src/content/perf/isolates.md +++ b/sites/docs/src/content/perf/isolates.md @@ -1,6 +1,9 @@ --- -title: Concurrency and isolates -description: Multithreading in Flutter using Dart isolates. +# title: Concurrency and isolates +title: 并发与 isolate +# description: Multithreading in Flutter using Dart isolates. +description: 在 Flutter 中使用 Dart isolate 实现多线程。 +ai-translated: true --- @@ -16,6 +19,8 @@ the main isolate. In most cases, this model allows for simpler programming and is fast enough that the application's UI doesn't become unresponsive. +所有 Dart 代码在 [isolate]({{site.dart-site}}/language/concurrency) 中运行,类似线程,但各有独立内存,不以任何方式共享状态,只能通过消息通信。默认情况下 Flutter 应用的所有工作都在单个 isolate——主 isolate——上完成。多数情况下该模型使编程更简单,且足够快,应用 UI 不会无响应。 + Sometimes though, applications need to perform exceptionally large computations that can cause "UI jank" (jerky motion). @@ -26,6 +31,8 @@ to run the computation concurrently with the main UI isolate's work and takes advantage of multi-core devices. +但有时应用需要执行特别大的计算,可能导致「UI jank」(卡顿)。若因此出现 jank,可将计算移到辅助 isolate,让运行时环境与主 UI isolate 的工作并发执行,并利用多核设备。 + Each isolate has its own memory and its own event loop. The event loop processes @@ -34,6 +41,8 @@ On the main isolate, these events can be anything from handling a user tapping in the UI, to executing a function, to painting a frame on the screen. + +每个 isolate 有独立内存和事件循环。事件循环按加入事件队列的顺序处理事件。在主 isolate 上,这些事件可以是处理 UI 点击、执行函数或在屏幕上绘制一帧等。 The following figure shows an example event queue with 3 events waiting to be processed. @@ -47,6 +56,8 @@ the application experiences UI jank, or worse, become unresponsive altogether. +为流畅渲染,Flutter 每秒向事件队列添加 60 次「paint frame」事件(60Hz 设备)。若这些事件未及时处理,应用会出现 UI jank,甚至更糟——完全无响应。 + ![Event jank diagram](/assets/images/docs/development/concurrency/event-jank.png){:width="60%" .diagram-wrap} Whenever a process can't be completed in a frame gap, @@ -57,23 +68,31 @@ When you spawn an isolate in Dart, it can process the work concurrently with the main isolate, without blocking it. +若某过程无法在帧间隔(两帧之间的时间)内完成,最好将工作卸载到另一 isolate,确保主 isolate 每秒产出 60 帧。在 Dart 中 spawn isolate 时,它可与主 isolate 并发处理工作而不阻塞主 isolate。 + You can read more about how isolates and the event loop work in Dart on the [concurrency page][] of the Dart documentation. +有关 isolate 与事件循环的更多说明,请参阅 Dart 文档的 [concurrency page][](并发页面)。 + [concurrency page]: {{site.dart-site}}/language/concurrency ## Common use cases for isolates +## isolate 的常见用例 + There is only one hard rule for when you should use isolates, and that's when large computations are causing your Flutter application to experience UI jank. This jank happens when there is any computation that takes longer than Flutter's frame gap. +何时应使用 isolate 只有一条硬性规则:大型计算导致 Flutter 应用出现 UI jank。当任何计算耗时超过 Flutter 的帧间隔时就会出现 jank。 + ![Event jank diagram](/assets/images/docs/development/concurrency/event-jank.png){:width="60%" .diagram-wrap} Any process _could_ take longer to complete, @@ -84,16 +103,34 @@ when you need to consider using isolates. That said, isolates are commonly used for the following: +此外,isolate 常用于以下场景: + - Reading data from a local database + + 从本地数据库读取数据 - Sending push notifications + + 发送推送通知 - Parsing and decoding large data files + + 解析和解码大型数据文件 - Processing or compressing photos, audio files, and video files + + 处理或压缩照片、音频和视频文件 - Converting audio and video files + + 转换音频和视频文件 - When you need asynchronous support while using FFI + + 使用 FFI 时需要异步支持 - Applying filtering to complex lists or filesystems + 对复杂列表或文件系统应用过滤 + ## Message passing between isolates +## isolate 之间的消息传递 + Dart's isolates are an implementation of the [Actor model][]. They can only communicate with each other by message passing, which is done with [`Port` objects][]. @@ -104,6 +141,8 @@ This means that any value passed to an isolate, even if mutated on that isolate, doesn't change the value on the original isolate. +Dart 的 isolate 是 [Actor model][](Actor 模型)的实现,只能通过 [`Port` objects][] 进行消息传递通信。消息在 isolate 之间「传递」时,通常从发送 isolate 复制到接收 isolate,因此传给 isolate 的值即使在该 isolate 上被修改,也不会改变原 isolate 上的值。 + The only [objects that aren't copied when passed][] to an isolate are immutable objects that can't be changed anyway, such a String or an unmodifiable byte. @@ -114,6 +153,8 @@ for better performance. Because immutable objects can't be updated, this effectively retains the actor model behavior. +传给 isolate 时 [objects that aren't copied when passed][](不复制的对象)仅包括不可变对象,如 String 或不可修改的字节。传递不可变对象时,为提升性能会发送引用而非复制对象。因不可变对象无法更新,这有效保持了 Actor 模型行为。 + [`Port` objects]: {{site.dart.api}}/dart-isolate/ReceivePort-class.html [objects that aren't copied when passed]: {{site.dart.api}}/dart-isolate/SendPort/send.html @@ -123,6 +164,8 @@ Because the sending isolate won't exist after sending the message, it can pass ownership of the message from one isolate to the other, ensuring that only one isolate can access the message. +例外是使用 `Isolate.exit` 发送消息时 isolate 退出:因发送 isolate 发送后不再存在,可将消息所有权从一个 isolate 转给另一个,确保只有一个 isolate 能访问该消息。 + The two lowest-level primitives that send messages are `SendPort.send`, which makes a copy of a mutable message as it sends, and `Isolate.exit`, @@ -130,8 +173,12 @@ which sends the reference to the message. Both `Isolate.run` and `compute` use `Isolate.exit` under the hood. +发送消息的两种底层原语是 `SendPort.send`(发送时复制可变消息)和 `Isolate.exit`(发送消息引用)。`Isolate.run` 和 `compute` 底层都使用 `Isolate.exit`。 + ## Short-lived isolates +## 短期 isolate + The easiest way to move a process to an isolate in Flutter is with the `Isolate.run` method. This method spawns an isolate, @@ -141,6 +188,8 @@ and then shuts the isolate down when the computation is complete. This all happens concurrently with the main isolate, and doesn't block it. +在 Flutter 中将过程移到 isolate 的最简单方式是使用 `Isolate.run`。该方法 spawn isolate,向 spawn 的 isolate 传递回调以开始计算,返回计算结果,计算完成后关闭 isolate。这一切与主 isolate 并发进行,不会阻塞主 isolate。 + ![Isolate diagram](/assets/images/docs/development/concurrency/isolate-bg-worker.png){:width="70%" .diagram-wrap} The `Isolate.run` method requires a single argument, @@ -152,6 +201,8 @@ When the computation completes, it returns the callback's value back to the main isolate, and exits the spawned isolate. +`Isolate.run` 需要一个参数:在新 isolate 上运行的回调函数。该回调的函数签名必须恰好有一个必需的无名参数。计算完成后,将回调的返回值返回主 isolate 并退出 spawn 的 isolate。 + For example, consider this code that loads a large JSON blob from a file, and converts that JSON into custom Dart objects. @@ -159,6 +210,8 @@ If the json decoding process wasn't off loaded to a new isolate, this method would cause the UI to become unresponsive for several seconds. +例如,以下代码从文件加载大型 JSON 并转换为自定义 Dart 对象。若 JSON 解码未卸载到新 isolate,该方法会使 UI 数秒无响应。 + ```dart // Produces a list of 211,640 photo objects. @@ -176,19 +229,27 @@ Future> getPhotos() async { For a complete walkthrough of using Isolates to parse JSON in the background, see [this cookbook recipe][]. +有关在后台使用 Isolate 解析 JSON 的完整 walkthrough,请参阅 [this cookbook recipe][](cookbook 食谱)。 + [this cookbook recipe]: /cookbook/networking/background-parsing ## Stateful, longer-lived isolates +## 有状态的长期 isolate + Short-live isolates are convenient to use, but there is performance overhead required to spawn new isolates, and to copy objects from one isolate to another. If you're doing the same computation using `Isolate.run` repeatedly, you might have better performance by creating isolates that don't exit immediately. +短期 isolate 使用方便,但 spawn 新 isolate 和在 isolate 间复制对象有性能开销。若反复用 `Isolate.run` 做相同计算,创建不立即退出的 isolate 可能性能更好。 + To do this, you can use a handful of lower-level isolate-related APIs that `Isolate.run` abstracts: +为此可使用 `Isolate.run` 所封装的一些底层 isolate API: + - [`Isolate.spawn()`][] and [`Isolate.exit()`][] - [`ReceivePort`][] and [`SendPort`][] - [`send()`][] method @@ -202,17 +263,23 @@ In Dart, you can accomplish this with the Isolate API and Ports. These long-lived isolates are colloquially known as _background workers_. +使用 `Isolate.run` 时,新 isolate 在向主 isolate 返回单条消息后立即关闭。有时你需要长期存活、可随时间互发多条消息的 isolate。在 Dart 中可用 Isolate API 和 Port 实现,这些长期 isolate 俗称 _background workers_(后台 worker)。 + Long-lived isolates are useful when you have a specific process that either needs to be run repeatedly throughout the lifetime of your application, or if you have a process that runs over a period of time and needs to yield multiple return values to the main isolate. +长期 isolate 适用于需要在应用生命周期内反复运行的特定过程,或在一段时间内运行并需向主 isolate 产生多个返回值的过程。 + Or, you might use [worker_manager][] to manage long-lived isolates. [worker_manager]: {{site.pub-pkg}}/worker_manager ### ReceivePorts and SendPorts +### ReceivePort 与 SendPort + Set up long-lived communication between isolates with two classes (in addition to Isolate): [`ReceivePort`][] and [`SendPort`][]. @@ -229,15 +296,21 @@ and you can "add" messages with the `send()` method. and when these listeners receive a new message, they call a provided callback with the message as an argument. +`Port` 的行为类似 `Stream`:在一个 isolate 中创建 `StreamController` 或 `Sink`,在另一个 isolate 中设置监听器。类比中 `StreamController` 称为 `SendPort`,可用 `send()`「添加」消息;`ReceivePort` 是监听器,收到新消息时用消息作为参数调用提供的回调。 + For an in-depth explanation on setting up two-way communication between the main isolate and a worker isolate, follow the examples in the [Dart documentation][]. +有关主 isolate 与 worker isolate 之间双向通信的深入说明,请参阅 [Dart documentation][](Dart 文档)中的示例。 + [Dart documentation]: {{site.dart-site}}/language/concurrency ## Using platform plugins in isolates +## 在 isolate 中使用平台插件 + You can use platform plugins in background isolates. This enables plugins to offload heavy, platform-dependent computations to an isolate that won't block your UI. @@ -246,10 +319,14 @@ For example, imagine you're encrypting data using a native host API Previously, [marshaling data][] to the host platform could waste UI thread time, and can now be done in a background isolate. +可在后台 isolate 中使用平台插件,使插件将繁重的平台相关计算卸载到不阻塞 UI 的 isolate。例如使用原生宿主 API 加密数据时,以前 [marshaling data][](编组数据)到宿主平台可能占用 UI 线程时间,现可在后台 isolate 完成。 + Platform channel isolates use the [`BackgroundIsolateBinaryMessenger`][] API. The following snippet shows an example of using the `shared_preferences` package in a background isolate. +以下片段展示在后台 isolate 中使用 `shared_preferences` 包的示例。 + ```dart import 'dart:isolate'; @@ -276,6 +353,8 @@ Future _isolateMain(RootIsolateToken rootIsolateToken) async { ## Limitations of Isolates +## Isolate 的限制 + If you're coming to Dart from a language with multithreading, it's reasonable to expect isolates to behave like threads, but that isn't the case. @@ -294,8 +373,12 @@ to the new isolate. This is how isolates are meant to function, and it's important to keep in mind when you consider using isolates. +若你来自支持多线程的语言,可能预期 isolate 像线程一样工作,但事实并非如此。isolate 有独立全局字段,只能通过消息传递通信,确保可变对象仅在一个 isolate 中可访问,因此受限于自身内存访问。例如应用有全局可变变量 `configuration`,spawn 的 isolate 会复制为新全局字段;在 spawn 的 isolate 中修改该变量,主 isolate 中不变,即使将 `configuration` 作为消息传给新 isolate 亦然。这是 isolate 的设计行为,考虑使用 isolate 时需牢记。 + ### Web platforms and compute +### Web 平台与 compute + Dart web platforms, including Flutter web, don't support isolates. If you're targeting the web with your Flutter app, @@ -307,44 +390,70 @@ On mobile and desktop platforms `await compute(fun, message)` is equivalent to `await Isolate.run(() => fun(message))`. +包括 Flutter web 在内的 Dart Web 平台不支持 isolate。若 Flutter 应用面向 Web,可使用 `compute` 确保代码能编译。[`compute()`][] 在 Web 上于主线程运行计算,在移动设备上 spawn 新线程。在移动和桌面平台上 `await compute(fun, message)` 等价于 `await Isolate.run(() => fun(message))`。 + For more information on concurrency on the web, check out the [concurrency documentation][] on dart.dev. +有关 Web 并发的更多信息,请参阅 dart.dev 的 [concurrency documentation][](并发文档)。 + [concurrency documentation]: {{site.dart-site}}/language/concurrency ### No `rootBundle` access or `dart:ui` methods +### 无法访问 `rootBundle` 或 `dart:ui` 方法 + All UI tasks and Flutter itself are coupled to the main isolate. Therefore, you can't access assets using `rootBundle` in spawned isolates, nor can you perform any widget or UI work in spawned isolates. +所有 UI 任务和 Flutter 本身都与主 isolate 绑定,因此无法在 spawn 的 isolate 中用 `rootBundle` 访问资源,也不能在 spawn 的 isolate 中执行 widget 或 UI 工作。 + ### Limited plugin messages from host platform to Flutter +### 从宿主平台到 Flutter 的插件消息受限 + With background isolate platform channels, you can use platform channels in isolates to send messages to the host platform (for example Android or iOS), and receive responses to those messages. However, you can't receive unsolicited messages from the host platform. +通过后台 isolate 平台通道,可在 isolate 中使用平台通道向宿主平台(如 Android 或 iOS)发送消息并接收响应,但无法接收来自宿主平台的主动消息。 + As an example, you can't set up a long-lived Firestore listener in a background isolate, because Firestore uses platform channels to push updates to Flutter, which are unsolicited. You can, however, query Firestore for a response in the background. +例如,无法在后台 isolate 中设置长期 Firestore 监听器,因为 Firestore 通过平台通道向 Flutter 推送主动更新。但可在后台查询 Firestore 获取响应。 + ## More information +## 更多信息 + For more information on isolates, check out the following resources: +有关 isolate 的更多信息,请参阅以下资源: + - If you're using many isolates, consider the [IsolateNameServer][] class in Flutter, or the pub package that clones the functionality for Dart applications not using Flutter. + + 若使用多个 isolate,可考虑 Flutter 的 [IsolateNameServer][] 类,或为非 Flutter 的 Dart 应用使用复制该功能的 pub 包。 - Dart's Isolates are an implementation of the [Actor model][]. + + Dart 的 Isolate 是 [Actor model][] 的实现。 - [isolate_agents][] is a package that abstracts Ports and make it easier to create long-lived isolates. + + [isolate_agents][] 是抽象 Port、便于创建长期 isolate 的包。 - Read more about the `BackgroundIsolateBinaryMessenger` API [announcement][]. + 阅读 `BackgroundIsolateBinaryMessenger` API [announcement][](公告)的更多内容。 + [announcement]: {{site.flutter-blog}}/introducing-background-isolate-channels-7a299609cad8 [Actor model]: https://en.wikipedia.org/wiki/Actor_model [isolate_agents]: {{site.medium}}/@gaaclarke/isolate-agents-easy-isolates-for-flutter-6d75bf69a2e7 diff --git a/sites/docs/src/content/perf/rendering-performance.md b/sites/docs/src/content/perf/rendering-performance.md index e6d3d8e299..4492161cf9 100644 --- a/sites/docs/src/content/perf/rendering-performance.md +++ b/sites/docs/src/content/perf/rendering-performance.md @@ -5,6 +5,7 @@ title: 提高渲染性能 description: 如何测量以及评估你的应用渲染性能。 tags: Flutter性能 keywords: 性能测量,建议,常见问题,性能调试 +ai-translated: true --- {% render "docs/performance.md" %} @@ -46,7 +47,7 @@ A couple common pitfalls: * Rebuilding far more of the UI than expected each frame. To track widget rebuilds, see [Show performance data][]. - 每帧重建的 UI 比预期的要多得多。要跟踪组件的重建, + 每帧重建的 UI 比预期的要多得多。要跟踪 widget 的重建, 请参阅 [显示性能数据][Show performance data]。 * Building a large list of children directly, rather than diff --git a/sites/docs/src/content/perf/web-performance.md b/sites/docs/src/content/perf/web-performance.md index 720774a3ee..49b8506a52 100644 --- a/sites/docs/src/content/perf/web-performance.md +++ b/sites/docs/src/content/perf/web-performance.md @@ -1,10 +1,15 @@ --- -title: Debug performance for web apps -description: Learn how to use Chrome DevTools to debug web performance issues. +# title: Debug performance for web apps +title: 调试 Web 应用性能 +# description: Learn how to use Chrome DevTools to debug web performance issues. +description: 学习如何使用 Chrome DevTools 调试 Web 性能问题。 +ai-translated: true --- :::note Profiling Flutter web apps requires Flutter version 3.14 or later. + +分析 Flutter Web 应用需要 Flutter 3.14 或更高版本。 ::: The Flutter framework emits timeline events as it works to build frames, @@ -12,17 +17,29 @@ draw scenes, and track other activity such as garbage collections. These events are exposed in the [Chrome DevTools performance panel][] for debugging. +Flutter 框架在构建帧、绘制场景以及跟踪垃圾回收等活动时会发出 timeline 事件。这些事件会暴露在 +[Chrome DevTools performance panel][](性能面板)中,供调试使用。 + :::note For information on how to optimize web loading speed, check out the (free) article on Medium, [Best practices for optimizing Flutter web loading speed][article]. +[article]: {{site.flutter-blog}}/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c + +有关如何优化 Web 加载速度的信息, +请参阅 Medium 上的(免费)文章 +[Best practices for optimizing Flutter web loading speed][article](优化 Flutter Web 加载速度的最佳实践)。 + [article]: {{site.flutter-blog}}/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c ::: You can also emit your own timeline events using the `dart:developer` [Timeline][] and [TimelineTask][] APIs for further performance analysis. +你还可以使用 `dart:developer` 的 +[Timeline][] 和 [TimelineTask][] API 发出自定义 timeline 事件,以进行更深入的性能分析。 + [Chrome DevTools performance panel]: https://developer.chrome.com/docs/devtools/performance [Timeline]: {{site.api}}/flutter/dart-developer/Timeline-class.html [TimelineTask]: {{site.api}}/flutter/dart-developer/TimelineTask-class.html @@ -31,14 +48,29 @@ You can also emit your own timeline events using the `dart:developer` ## Optional flags to enhance tracing +## 用于增强跟踪的可选标志 + To configure which timeline events are tracked, set any of the following top-level properties to `true` in your app's `main` method. +要在应用中配置跟踪哪些 timeline 事件,请在 `main` 方法中将以下任一顶层属性设为 `true`。 + - [debugProfileBuildsEnabled][]: Adds `Timeline` events for every `Widget` built. + + [debugProfileBuildsEnabled][]:为每个已构建的 `Widget` 添加 `Timeline` 事件。 + - [debugProfileBuildsEnabledUserWidgets][]: Adds `Timeline` events for every user-created `Widget` built. + + [debugProfileBuildsEnabledUserWidgets][]:为每个用户创建的已构建 `Widget` 添加 `Timeline` 事件。 + - [debugProfileLayoutsEnabled][]: Adds `Timeline` events for every `RenderObject` layout. + + [debugProfileLayoutsEnabled][]:为每次 `RenderObject` 布局添加 `Timeline` 事件。 + - [debugProfilePaintsEnabled][]: Adds `Timeline` events for every `RenderObject` painted. + [debugProfilePaintsEnabled][]:为每次 `RenderObject` 绘制添加 `Timeline` 事件。 + [debugProfileBuildsEnabled]: {{site.api}}/flutter/widgets/debugProfileBuildsEnabled.html [debugProfileBuildsEnabledUserWidgets]: {{site.api}}/flutter/widgets/debugProfileBuildsEnabledUserWidgets.html [debugProfileLayoutsEnabled]: {{site.api}}/flutter/rendering/debugProfileLayoutsEnabled.html @@ -46,11 +78,22 @@ in your app's `main` method. ## Instructions +## 操作步骤 + 1. _[Optional]_ Set any desired tracing flags to true from your app's main method. + +1. _[可选]_ 在应用的 `main` 方法中将所需的跟踪标志设为 true。 + 2. Run your Flutter web app in [profile mode][]. + +2. 以 [profile mode][](profile 模式)运行 Flutter Web 应用。 + 3. Open up the [Chrome DevTools Performance panel][] for your application, and [start recording][] to capture timeline events. +3. 为应用打开 [Chrome DevTools Performance panel][](性能面板), + 并 [start recording][](开始录制)以捕获 timeline 事件。 + [start recording]: https://developer.chrome.com/docs/devtools/performance/#record [profile mode]: /testing/build-modes#profile diff --git a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md index d2476b9145..5053d62788 100644 --- a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md +++ b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md @@ -1,6 +1,9 @@ --- -title: "Calling JetPack APIs" -description: "Use the latest Android APIs from your Dart code" +# title: "Calling JetPack APIs" +title: "调用 Jetpack API" +# description: "Use the latest Android APIs from your Dart code" +description: "从 Dart 代码使用最新的 Android API" +ai-translated: true --- @@ -10,14 +13,22 @@ latest APIs on the first day they are released on Android, no matter what. This page outlines available ways to invoke Android-specific APIs. +在 Android 上运行的 Flutter 应用始终可以在 Android 发布最新 API 的当天就使用它们,无一例外。本页概述调用 Android 专用 API 的可用方式。 + ## Use an existing solution +## 使用现有方案 + In most scenarios, you can use a plugin (as shown in the next section) to invoke native APIs without writing any custom boilerplate or glue code yourself. +在大多数场景下,你可以使用插件(如下一节所示)调用原生 API,而无需自己编写自定义样板或胶水代码。 + ### Use a plugin +### 使用插件 + Using a plugin is often the easiest way to access native APIs, regardless of where your Flutter app is running. To use plugins, visit [pub.dev][pub] and search for @@ -25,9 +36,14 @@ the topic you need. Most native features, including accessing common hardware like GPS, the camera, or step counters are supported by robust plugins. +使用插件通常是访问原生 API 最简便的方式,无论你的 Flutter 应用运行在哪里。要使用插件,请访问 [pub.dev][pub] 并搜索你需要的主题。大多数原生功能(包括访问 GPS、相机、计步器等常见硬件)都有成熟的插件支持。 + For complete guidance on adding plugins to your Flutter app, see the [Using packages documentation][packages]. +有关向 Flutter 应用添加插件的完整指南, +请参阅[使用 package 文档][packages]。 + [packages]: /packages-and-plugins/using-packages [pub]: {{site.pub}} @@ -36,21 +52,32 @@ immediately after their release. In any scenario where your desired native feature is not covered by a package on [pub.dev][pub], continue on to the following sections. +并非所有原生功能都有插件支持,尤其是在刚发布时。若你需要的原生功能在 +[pub.dev][pub] 上没有对应 package,请继续阅读以下章节。 + ## Creating a custom solution +## 创建自定义方案 + Not all scenarios and APIs will be supported by existing solutions; but luckily, you can always add whatever support you need. The next sections describe two different ways to call native code from Dart. +并非所有场景和 API 都有现成方案支持;但幸运的是,你始终可以按需添加所需支持。以下章节介绍从 Dart 调用原生代码的两种方式。 + :::note Neither solution below is inherently better or worse than existing plugins, because all plugins use one of the following two options. + +以下两种方案本身并不优于或劣于现有插件,因为所有插件都使用下列两种方式之一。 ::: ### Call native code directly via FFI +### 通过 FFI 直接调用原生代码 + The most direct and efficient way to invoke native APIs is by calling the API directly, via FFI. This links your Dart executable to any specified native code at compile-time, allowing you to @@ -58,14 +85,20 @@ call it directly from the UI thread through a small amount of glue code. In most cases, [ffigen][ffigen] or [jnigen][jnigen] are helpful in writing this glue code. +调用原生 API 最直接、最高效的方式是通过 FFI 直接调用 API。这会在编译时将你的 Dart 可执行文件链接到指定的原生代码,使你可通过少量胶水代码在 UI 线程直接调用。在大多数情况下,[ffigen][ffigen] 或 [jnigen][jnigen] 有助于编写这类胶水代码。 + For complete guidance on directly calling native code from your Flutter app, see the [FFI documentation][ffi]. +有关从 Flutter 应用直接调用原生代码的完整指南,请参阅 [FFI 文档][ffi]。 + In the coming months, the Dart team hopes to make this process easier with direct support for calling native APIs using the FFI approach, but without any need for the developer to write glue code. +未来数月内,Dart 团队希望借助 FFI 方式直接支持调用原生 API,且开发者无需编写胶水代码。 + [ffi]: {{site.dart-site}}/interop/c-interop [ffigen]: {{site.pub}}/packages/ffigen [jnigen]: {{site.pub}}/packages/jnigen @@ -73,6 +106,8 @@ glue code. ### Add a MethodChannel +### 添加 MethodChannel + [`MethodChannel`][methodchannels-api-docs]s are an alternate way Flutter apps can invoke arbitrary native code. Unlike the FFI solution described in the previous step, @@ -83,9 +118,13 @@ requires a small amount of glue code to translate your Dart objects into native objects, and then back again. In most cases, [`pkg:pigeon`][pigeon] is helpful in writing this glue code. +[`MethodChannel`][methodchannels-api-docs] 是 Flutter 应用调用任意原生代码的另一种方式。与上一步介绍的 FFI 方案不同,MethodChannel 始终为异步,是否重要取决于你的用例。与 FFI 及直接调用原生代码一样,使用 `MethodChannel` 需要少量胶水代码将 Dart 对象转换为原生对象,再转换回来。在大多数情况下,[`pkg:pigeon`][pigeon] 有助于编写这类胶水代码。 + For complete guidance on adding MethodChannels to your Flutter app, see the [`MethodChannel`s documentation][methodchannels]. +有关向 Flutter 应用添加 MethodChannel 的完整指南,请参阅 [`MethodChannel` 文档][methodchannels]。 + [methodchannels]: /platform-integration/platform-channels [methodchannels-api-docs]: {{site.api}}/flutter/services/MethodChannel-class.html [pigeon]: {{site.pub}}/packages/pigeon diff --git a/sites/docs/src/content/platform-integration/android/chromeos.md b/sites/docs/src/content/platform-integration/android/chromeos.md index e6f01513dc..8998f41dc4 100644 --- a/sites/docs/src/content/platform-integration/android/chromeos.md +++ b/sites/docs/src/content/platform-integration/android/chromeos.md @@ -1,19 +1,28 @@ --- -title: Targeting ChromeOS with Android -description: Platform-specific considerations for building for ChromeOS with Flutter. +# title: Targeting ChromeOS with Android +title: 使用 Android 面向 ChromeOS +# description: Platform-specific considerations for building for ChromeOS with Flutter. +description: 使用 Flutter 为 ChromeOS 构建时的平台相关注意事项。 +ai-translated: true --- This page discusses considerations unique to building Android apps that support ChromeOS with Flutter. +本页讨论使用 Flutter 构建支持 ChromeOS 的 Android 应用时的特有注意事项。 + ## Flutter & ChromeOS tips & tricks +## Flutter 与 ChromeOS 技巧 + For the current versions of ChromeOS, only certain ports from Linux are exposed to the rest of the environment. Here's an example of how to launch Flutter DevTools for an Android app with ports that will work: +在当前版本的 ChromeOS 上,只有部分来自 Linux 的端口会暴露给环境的其余部分。以下示例演示如何为 Android 应用启动 Flutter DevTools,并使用可用的端口: + ```console $ flutter pub global run devtools --port 8000 $ cd path/to/your/app @@ -28,8 +37,12 @@ of `http://127.0.0.1:8080/auth_code=/`. Use this URL and select "Connect" to start the Flutter DevTools for your Android app. +然后,在 Chrome 浏览器中访问 http://127.0.0.1:8000/#,并输入你的应用 URL。你刚运行的最后一次 `flutter run` 命令应输出类似 `http://127.0.0.1:8080/auth_code=/` 格式的 URL。使用该 URL 并选择「Connect」即可为你的 Android 应用启动 Flutter DevTools。 + #### Flutter ChromeOS lint analysis +#### Flutter ChromeOS lint 分析 + Flutter has ChromeOS-specific lint analysis checks to make sure that the app that you're building works well on ChromeOS. It looks for things @@ -39,12 +52,18 @@ permissions that imply requests for unsupported hardware, as well as other properties or code that would bring a lesser experience on these devices. +Flutter 提供面向 ChromeOS 的 lint 分析检查,确保你构建的应用在 ChromeOS 上运行良好。它会检查 Android Manifest 中在 ChromeOS 设备上不可用的必需硬件、暗示请求不受支持硬件的权限,以及会在这些设备上降低体验的其他属性或代码。 + To activate these, you need to create a new analysis_options.yaml file in your project folder to include these options. (If you have an existing analysis_options.yaml file, you can update it) +要启用这些检查, +你需要在项目文件夹中创建新的 analysis_options.yaml 文件以包含这些选项。 +(若已有 analysis_options.yaml 文件,可更新它) + ```yaml include: package:flutter/analysis_options_user.yaml analyzer: @@ -54,12 +73,16 @@ analyzer: To run these from the command line, use the following command: +要从命令行运行这些检查,请使用以下命令: + ```console $ flutter analyze ``` Sample output for this command might look like: +该命令的示例输出可能如下: + ```console Analyzing ... warning • This hardware feature is not supported on ChromeOS • diff --git a/sites/docs/src/content/platform-integration/android/compose-activity.md b/sites/docs/src/content/platform-integration/android/compose-activity.md index 1b51f5c4c2..5942efe4ea 100644 --- a/sites/docs/src/content/platform-integration/android/compose-activity.md +++ b/sites/docs/src/content/platform-integration/android/compose-activity.md @@ -1,8 +1,13 @@ --- -title: Launching a Jetpack Compose activity from your Flutter application -shortTitle: Native Android activities +# title: Launching a Jetpack Compose activity from your Flutter application +title: 从 Flutter 应用启动 Jetpack Compose Activity +# shortTitle: Native Android activities +shortTitle: 原生 Android Activity +# description: >- +# Learn how to launch native Android activities in your Flutter app. description: >- - Learn how to launch native Android activities in your Flutter app. + 了解如何在你的 Flutter 应用中启动原生 Android Activity。 +ai-translated: true --- @@ -13,6 +18,8 @@ You will only write Kotlin code in those views (though they might pass messages to and receive messages from your Dart code) and you will have access to the full breadth of native Android functionality. +原生 Android Activity 让你启动完全由 Android 平台运行且在其上运行的全屏 UI。你只需在这些视图中编写 Kotlin 代码(尽管它们可能与 Dart 代码收发消息),并可使用原生 Android 功能的全部能力。 + Adding this functionality requires making several changes to your Flutter app and its internal, generated Android app. On the Flutter side, you will need to create a new @@ -24,11 +31,17 @@ an Android activity that is completely consumed by the Flutter app. Thus, as you will see in the code sample, the job of the native `MethodChannel` callback is to launch a second activity. +添加此功能需要对你的 Flutter 应用及其内部生成的 Android 应用进行多处修改。在 Flutter 侧,你需要创建新的平台 method channel 并调用其 `invokeMethod` 方法。在 Android 侧,你需要注册匹配的原生 `MethodChannel` 以接收来自 Dart 的信号,然后启动新的 Activity。请记住,所有 Flutter 应用(在 Android 上运行时)都存在于被 Flutter 应用完全占用的 Android Activity 中。因此,如代码示例所示,原生 `MethodChannel` 回调的任务是启动第二个 Activity。 + :::note This page discusses how to launch native Android activities within a Flutter app. If you'd like to host native Android views in your Flutter app, check out [Hosting native Android views][]. + +本页讨论如何在 Flutter 应用内启动原生 Android Activity。 +若要在 Flutter 应用中托管原生 Android 视图, +请参阅[托管原生 Android 视图][Hosting native Android views]。 ::: [Hosting native Android views]: /platform-integration/android/platform-views @@ -36,11 +49,17 @@ check out [Hosting native Android views][]. Not all Android activities use Jetpack Compose, but this tutorial assumes you want to use Compose. +并非所有 Android Activity 都使用 Jetpack Compose,但本教程假定你想使用 Compose。 + ## On the Dart side +## 在 Dart 侧 + On the Dart side, create a method channel and invoke it from a specific user interaction, like tapping a button. +在 Dart 侧,创建 method channel,并在特定用户交互(如点击按钮)时调用它。 + ```dart import 'package:flutter/material.dart'; @@ -97,23 +116,42 @@ class MainApp extends StatelessWidget { There are 3 important values that must match across your Dart and Kotlin code: +Dart 与 Kotlin 代码中有 3 个重要值必须一致: + 1. The channel name (in this sample, the value is `"com.example.flutter_android_activity"`). + + channel 名称(本示例中为 `"com.example.flutter_android_activity"`)。 + 2. The method name (in this sample, the value is `"launchActivity"`). + + 方法名称(本示例中为 `"launchActivity"`)。 + 3. The structure of the data which Dart passes and the structure of the data which Kotlin expects to receive. In this case, the data is a map with a single `"message"` key. + Dart 传递的数据结构与 Kotlin 期望接收的数据结构。 + 本例中,数据为仅含 `"message"` 键的 map。 + ## On the Android side +## 在 Android 侧 + You must make changes to 4 files in the generated Android app to ready it for launching fresh Compose activities. +你必须修改生成 Android 应用中的 4 个文件,以便启动新的 Compose Activity。 + The first file requiring modifications is `android/app/build.gradle`. +第一个需要修改的文件是 `android/app/build.gradle`。 + 1. Add the following to the existing `android` block: + 在现有 `android` 块中添加以下内容: + @@ -157,10 +195,16 @@ The first file requiring modifications is `android/app/build.gradle`. receive errors during `flutter run` and those errors tell you which versions are installed on your machine. + 访问代码片段中的 [developer.android.com][] 链接, + 并按需调整 `kotlinCompilerExtensionVersion`。 + 仅当你在 `flutter run` 期间收到错误且错误提示你机器上已安装的版本时,才需要这样做。 + [developer.android.com]: {{site.android-dev}}/jetpack/androidx/releases/compose-kotlin 2. Next, add the following block at the bottom of the file, at the root level: + 接下来,在文件底部根级别添加以下块: + @@ -215,8 +259,12 @@ The first file requiring modifications is `android/app/build.gradle`. The second file requiring modifications is `android/build.gradle`. + 第二个需要修改的文件是 `android/build.gradle`。 + 1. Add the following buildscript block at the top of the file: + 在文件顶部添加以下 buildscript 块: + @@ -255,8 +303,12 @@ The first file requiring modifications is `android/app/build.gradle`. The third file requiring modifications is `android/app/src/main/AndroidManifest.xml`. + 第三个需要修改的文件是 `android/app/src/main/AndroidManifest.xml`。 + 1. In the root application block, add the following `` declaration: + 在根 application 块中添加以下 `` 声明: + ```xml title="android/app/src/main/AndroidManifest.xml" - +# Learn how to add the predictive back gesture to your Android app. description: >- - Learn how to add the predictive back gesture to your Android app. + 了解如何在你的 Android 应用中添加预测性返回手势。 +ai-translated: true --- This feature has landed in Flutter, but it's not enabled by default in Android itself yet. You can try it out using the following instructions. +该功能已在 Flutter 中落地, +但 Android 本身尚未默认启用。 +你可以按以下说明试用。 + ## Configure your app +## 配置你的应用 + Make sure your app supports Android API 33 or higher, as predictive back won't work on older versions of Android. Then, set the flag `android:enableOnBackInvokedCallback="true"` in `android/app/src/main/AndroidManifest.xml`. +确保你的应用支持 Android API 33 或更高版本, +因为预测性返回在更低版本的 Android 上无效。 +然后在 `android/app/src/main/AndroidManifest.xml` 中设置标志 `android:enableOnBackInvokedCallback="true"`。 + ## Configure your device +## 配置你的设备 + You need to enable Developer Mode and set a flag on your device, so you can't yet expect predictive back to work on most users' Android devices. If you want to try it out on your own device though, @@ -25,13 +42,25 @@ make sure it's running API 33 or higher, and then in **Settings => System => Developer** options, make sure the switch is enabled next to **Predictive back animations**. +你需要启用开发者模式并在设备上设置标志, +因此尚不能指望大多数用户的 Android 设备上都能使用预测性返回。若你想在自己的设备上试用, +请确保运行 API 33 或更高版本,然后在 +**Settings => System => Developer** 选项中, +确保 **Predictive back animations** 旁的开关已启用。 + ## Set up your app +## 设置你的应用 + The predictive back route transitions are currently not enabled by default, so for now you'll need to enable them manually in your app. Typically, you do this by setting them in your theme: +预测性返回的路由过渡目前 +默认未启用,因此暂时需要在你的应用中手动启用。 +通常通过在主题中设置即可: + ```dart MaterialApp( theme: ThemeData( @@ -48,14 +77,26 @@ MaterialApp( ## Run your app +## 运行你的应用 + Lastly, just make sure you're using at least Flutter version 3.22.2 to run your app, which is the latest stable release at the time of this writing. +最后,请确保运行应用时至少使用 +Flutter 3.22.2 版本, +撰写本文时这是最新的稳定版。 + ## For more information +## 更多信息 + You can find more information at the following link: +你可以在以下链接找到更多信息: + * [Android predictive back][] breaking change + [Android predictive back][] 破坏性变更 + [Android predictive back]: /release/breaking-changes/android-predictive-back diff --git a/sites/docs/src/content/platform-integration/android/restore-state-android.md b/sites/docs/src/content/platform-integration/android/restore-state-android.md index 762bbed8de..e43345d74a 100644 --- a/sites/docs/src/content/platform-integration/android/restore-state-android.md +++ b/sites/docs/src/content/platform-integration/android/restore-state-android.md @@ -1,6 +1,9 @@ --- -title: "Restore state on Android" -description: "How to restore the state of your Android app after it's been killed by the OS." +# title: "Restore state on Android" +title: "在 Android 上恢复状态" +# description: "How to restore the state of your Android app after it's been killed by the OS." +description: "如何在操作系统终止应用后恢复 Android 应用的状态。" +ai-translated: true --- When a user runs a mobile app and then selects another @@ -9,6 +12,8 @@ or _backgrounded_. The operating system (both iOS and Android) might kill the backgrounded app to release memory and improve performance for the app running in the foreground. +当用户运行移动应用后选择运行另一个应用时,第一个应用会进入后台,或称 _被置于后台_。操作系统(iOS 与 Android 均如此)可能会终止后台应用以释放内存,并提升前台应用的性能。 + When the user selects the app again, bringing it back to the foreground, the OS relaunches it. But, unless you've set up a way to save the @@ -19,10 +24,14 @@ which is clearly not ideal. (Imagine filling out a lengthy form and being interrupted by a phone call _before_ clicking **Submit**.) +当用户再次选择该应用、将其带回前台时,操作系统会重新启动它。但除非你在应用被终止前设置了保存状态的方式,否则状态会丢失,应用会从头开始。用户会失去他们期望的连续性,这显然不理想。(想象在点击 **Submit** _之前_ 填写冗长表单时被电话打断。) + So, how can you restore the state of the app so that it looks like it did before it was sent to the background? +那么,如何恢复应用状态,使其看起来与进入后台之前一样? + Flutter has a solution for this with the [`RestorationManager`][] (and related classes) in the [services][] library. @@ -32,6 +41,10 @@ changes_, so that the app is ready when the OS signals that it's about to kill the app, giving the app only moments to prepare. +Flutter 在 [services][] 库中通过 +[`RestorationManager`][](及相关类)提供解决方案。 +使用 `RestorationManager` 时,Flutter 框架会在 _状态变化时_ 将状态数据提供给引擎,以便在操作系统发出即将终止应用的信号时应用已就绪,而应用只有片刻时间准备。 + :::secondary Instance state vs long-lived state When should you use the `RestorationManager` and when should you save state to long term storage? @@ -42,22 +55,38 @@ moments to prepare. limited to 1 MB and, if the app exceeds this, it crashes with a `TransactionTooLargeException` error in the native code. + + 实例状态与长期状态 + 何时应使用 `RestorationManager`, + 何时应将状态保存到长期存储? + _实例状态_ + (也称 _短期_ 或 _临时_ 状态), + 包括未提交的表单字段值、当前选中的标签页等。在 Android 上,这限制为 1 MB;若应用超出此限制, + 原生代码会因 `TransactionTooLargeException` 错误而崩溃。 ::: [state]: /data-and-backend/state-mgmt/ephemeral-vs-app ## Overview +## 概述 + You can enable state restoration with just a few tasks: +只需完成几项任务即可启用状态恢复: + 1. Define a `restorationScopeId` for classes like `CupertinoApp`, `MaterialApp`, or `WidgetsApp`. +1. 为 `CupertinoApp`、`MaterialApp` 或 `WidgetsApp` 等类定义 `restorationScopeId`。 + 2. Define a `restorationId` for widgets that support it, such as [`TextField`][] and [`ScrollView`][]. This automatically enables built-in state restoration for those widgets. +2. 为支持它的 widget(如 [`TextField`][] 和 [`ScrollView`][])定义 `restorationId`。这会自动为这些 widget 启用内置状态恢复。 + 3. For custom widgets, you must decide what state you want to restore and hold that state in a [`RestorableProperty`][]. @@ -68,13 +97,27 @@ You can enable state restoration with just a few tasks: Register those widgets with the mixin in a `restoreState` method. +3. 对于自定义 widget, + 你必须决定要恢复哪些状态, + 并在 [`RestorableProperty`][] 中保存该状态。 + (Flutter API 为不同数据类型提供多种子类。) + 在使用 [`RestorationMixin`][] 的 `State` 类中定义这些 `RestorableProperty` widget。 + 在 `restoreState` 方法中向 mixin 注册这些 widget。 + 4. If you use any Navigator API (like `push`, `pushNamed`, and so on) migrate to the API that has "restorable" in the name (`restorablePush`, `restorablePushNamed`, and so on) to restore the navigation stack. +4. 若使用任何 Navigator API(如 `push`、`pushNamed` 等), + 请迁移到名称中包含 `restorable` 的 API + (`restorablePush`、`restorablePushNamed` 等) + 以恢复导航栈。 + Other considerations: +其他注意事项: + * Providing a `restorationScopeId` to `MaterialApp`, `CupertinoApp`, or `WidgetsApp` automatically enables state restoration by @@ -82,6 +125,8 @@ Other considerations: If you need to restore state _above_ the app class, inject a `RootRestorationScope` manually. + 向 `MaterialApp`、`CupertinoApp` 或 `WidgetsApp` 提供 `restorationScopeId` 会通过注入 `RootRestorationScope` 自动启用状态恢复。若需要在应用类 _之上_ 恢复状态,请手动注入 `RootRestorationScope`。 + * **The difference between a `restorationId` and a `restorationScopeId`:** Widgets that take a `restorationScopeId` create a new `restorationScope` @@ -89,6 +134,9 @@ Other considerations: store their state. A `restorationId` means the widget (and its children) store the data in the surrounding bucket. + + **`restorationId` 与 `restorationScopeId` 的区别:** 接受 `restorationScopeId` 的 widget 会创建新的 `restorationScope`(新的 `RestorationBucket`),所有子级在其中保存状态。`restorationId` 表示该 widget(及其子级)在周围的 bucket 中保存数据。 + [a bit of extra setup]: {{site.api}}/flutter/services/RestorationManager-class.html#state-restoration-on-ios [`restorationId`]: {{site.api}}/flutter/widgets/RestorationScope/restorationId.html [`restorationScopeId`]: {{site.api}}/flutter/widgets/RestorationScope/restorationScopeId.html @@ -99,28 +147,42 @@ Other considerations: ## Restoring navigation state +## 恢复导航状态 + If you want your app to return to a particular route that the user was most recently viewing (the shopping cart, for example), then you must implement restoration state for navigation, as well. +若希望应用返回到用户最近查看的特定路由(例如购物车),则还必须为导航实现状态恢复。 + If you use the Navigator API directly, migrate the standard methods to restorable methods (that have "restorable" in the name). For example, replace `push` with [`restorablePush`][]. +若直接使用 Navigator API, +请将标准方法迁移为可恢复的方法(名称中包含 `restorable`)。 +例如,将 `push` 替换为 [`restorablePush`][]。 + ## Testing state restoration +## 测试状态恢复 + To test state restoration, set up your mobile device so that it doesn't save state once an app is backgrounded. To learn how to do this for both iOS and Android, check out [Testing state restoration][] on the [`RestorationManager`][] page. +要测试状态恢复,请将移动设备配置为应用进入后台后不保存状态。要了解如何在 iOS 与 Android 上操作,请参阅 [`RestorationManager`][] 页面上的[测试状态恢复][Testing state restoration]。 + :::warning Don't forget to reenable storing state on your device once you are finished with testing! + +测试结束后,别忘了在设备上重新启用状态存储! ::: [Testing state restoration]: {{site.api}}/flutter/services/RestorationManager-class.html#testing-state-restoration @@ -130,20 +192,30 @@ finished with testing! ## Other resources +## 其他资源 + For further information on state restoration, check out the following resources. +有关状态恢复的更多信息,请参阅以下资源。 + * To learn more about short term and long term state, check out [Differentiate between ephemeral state and app state][state]. + 要了解短期与长期状态的更多信息,请参阅[区分临时状态与应用状态][state]。 + * You might want to check out packages on pub.dev that perform state restoration, such as [`statePersistence`][]. + 你可能想查看 pub.dev 上执行状态恢复的 package,例如 [`statePersistence`][]。 + * For more information on navigation and the [`go_router`][] package, check out [Navigation and routing][] and the [State restoration][] topic on pub.dev. + 有关导航与 [`go_router`][] package 的更多信息,请参阅[导航与路由][Navigation and routing]以及 pub.dev 上的[状态恢复][State restoration]主题。 + [`go_router`]: {{site.pub}}/packages/go_router [State restoration]: {{site.pub-api}}/go_router/latest/topics/State%20restoration-topic.html [Navigation and routing]: /ui/navigation diff --git a/sites/docs/src/content/platform-integration/android/sensitive-content.md b/sites/docs/src/content/platform-integration/android/sensitive-content.md index c2f0175c04..5a4bc155e5 100644 --- a/sites/docs/src/content/platform-integration/android/sensitive-content.md +++ b/sites/docs/src/content/platform-integration/android/sensitive-content.md @@ -1,8 +1,13 @@ --- -title: Protect your app's sensitive content -shortTitle: Sensitive content +# title: Protect your app's sensitive content +title: 保护应用的敏感内容 +# shortTitle: Sensitive content +shortTitle: 敏感内容 +# description: >- +# Learn how to protect sensitive content in your Flutter app. description: >- - Learn how to protect sensitive content in your Flutter app. + 了解如何保护 Flutter 应用中的敏感内容。 +ai-translated: true --- The [`SensitiveContent`] widget allows you to prevent @@ -10,39 +15,58 @@ screens that contain sensitive content (such as passwords) from being projected. To learn more, check out the following two-minute Widget of the Week video: +[`SensitiveContent`] widget 可阻止包含敏感内容(如密码)的屏幕被投射。要了解更多信息, +请观看以下两分钟「每周 widget」视频: + ## About the `SensitiveContent` widget +## 关于 `SensitiveContent` widget + You can use the `SensitiveContent` widget in your app to set the content sensitivity of a child `Widget` to one of the following [`ContentSensitivity`] values: `notSensitive`, `sensitive`, or `autoSensitive`. Your chosen mode determines if the device screen should be obscured (blacked out) during media projection to protect sensitive data. +你可以在应用中使用 `SensitiveContent` widget,将子 widget 的内容敏感度设置为以下 [`ContentSensitivity`] 值之一:`notSensitive`、`sensitive` 或 `autoSensitive`。所选模式决定在媒体投射期间是否应遮挡(黑屏)设备屏幕以保护敏感数据。 + You can have as many `SensitiveContent` widgets in your app as you wish, but if _any_ one of those widgets has a `sensitive` content value, then the entire screen is obscured during media projection. Thus, for most use cases, using multiple `SensitiveContent` widgets provides no advantage over having one `SensitiveContent` widget in your app’s widget tree. +你可以在应用中放置任意数量的 `SensitiveContent` widget, +但若 _任一_ widget 的内容值为 `sensitive`,则媒体投射期间整个屏幕都会被遮挡。因此,在大多数用例中,使用多个 `SensitiveContent` widget 相比在应用的 widget 树中只放一个并无优势。 + This feature is available on Android API 35+ and has no effect on lower API versions or other platforms. +该功能在 Android API 35+ 上可用, +在更低 API 版本或其他平台上无效。 + :::note The `autoSensitive` value isn't supported as of Flutter 3.35 and behaves the same as `notSensitive`. See [Issue #160879][] for more information. + +自 Flutter 3.35 起不支持 `autoSensitive` 值,其行为与 `notSensitive` 相同。更多信息请参阅 [Issue #160879][]。 ::: [Issue #160879]: {{site.github}}/flutter/flutter/issues/160879 ## Using the `SensitiveContent` widget +## 使用 `SensitiveContent` widget + Given some content that you want to protect from media screen share (for example, a `MySensitiveContent()` widget), you can wrap it with the `SensitiveContent` widget as shown in the following example: +对于希望防止媒体屏幕共享的内容(例如 `MySensitiveContent()` widget),可按以下示例用 `SensitiveContent` widget 包裹: + ```dart class MyWidget extends StatelessWidget { ... @@ -60,10 +84,16 @@ during media projection. The widget will exist in the tree but has no other effect, and you don't need to avoid usages of `SensitiveContent` on platforms that don't support this feature. +在 Android API 34 及以下版本运行时,媒体投射期间屏幕不会被遮挡。该 widget 会存在于树中但无其他效果,你无需在不支持此功能的平台上避免使用 `SensitiveContent`。 + ## For more information +## 更多信息 + For more information, visit the [`SensitiveContent`][] and [`ContentSensitivity`][] API docs. +更多信息请访问 [`SensitiveContent`][] 与 [`ContentSensitivity`][] API 文档。 + [`SensitiveContent`]: {{site.api}}/flutter/widgets/SensitiveContent-class.html [`ContentSensitivity`]: {{site.api}}/flutter/services/ContentSensitivity.html diff --git a/sites/docs/src/content/platform-integration/android/setup.md b/sites/docs/src/content/platform-integration/android/setup.md index 0da1bbae3b..4e97b2e508 100644 --- a/sites/docs/src/content/platform-integration/android/setup.md +++ b/sites/docs/src/content/platform-integration/android/setup.md @@ -1,13 +1,19 @@ --- -title: Set up Android development +# title: Set up Android development +title: 设置 Android 开发环境 +# description: >- +# Configure your development environment to +# run, build, and deploy Flutter apps for Android devices. description: >- - Configure your development environment to - run, build, and deploy Flutter apps for Android devices. + 配置开发环境,以便在 Android 设备上运行、构建和部署 Flutter 应用。 +ai-translated: true --- Learn how to set up your development environment to run, build, and deploy Flutter apps for Android devices. +了解如何配置开发环境,以便在 Android 设备上运行、构建和部署 Flutter 应用。 + :::warning If you haven't set up Flutter already, visit and follow [Install Flutter][] first. @@ -15,11 +21,21 @@ visit and follow [Install Flutter][] first. Installing the Flutter plugin for Android Studio is **not** enough; you must also install the Flutter SDK and add its `bin` directory to your PATH to use the `flutter` command. + +若你尚未设置 Flutter, +请先访问并遵循[安装 Flutter][Install Flutter]。 + +仅安装 Android Studio 的 Flutter 插件 **不够**; +你还必须安装 Flutter SDK 并将其 `bin` 目录加入 PATH, +才能使用 `flutter` 命令。 ::: :::note If you've already installed Flutter, ensure that it's [up to date][]. + +若你已安装 Flutter, +请确保其[为最新版本][up to date]。 ::: [Install Flutter]: /install @@ -27,78 +43,135 @@ ensure that it's [up to date][]. ## Choose your development platform {: #dev-platform} +## 选择你的开发平台 {: #dev-platform} + The instructions on this page are configured to cover setting up Android development on a **Windows**{:.selected-os-text} device. +本页说明默认面向在 **Windows**{:.selected-os-text} 设备上设置 Android 开发。 + If you'd like to follow the instructions for a different OS, please select one of the following. +若要为其他操作系统遵循说明,请选择以下之一。 + ## Set up Android tooling {: #set-up-tooling} +## 设置 Android 工具链 {: #set-up-tooling} + With Android Studio, you can run Flutter apps on a physical Android device or an Android Emulator. +借助 Android Studio,你可以在实体 Android 设备或 Android 模拟器上运行 Flutter 应用。 + If you haven't done so already, install and set up the latest stable version of [Android Studio][]. +若尚未完成, +请安装并设置最新稳定版 [Android Studio][]。 + 1.

Install prerequisites libraries

+

安装必备库

+ If you're developing on Linux, first install the [prerequisite collection of 32-bit libraries][64bit-libs] that Android Studio requires. {: .linux-only} + 若在 Linux 上开发,请先安装 Android Studio 所需的 [32 位库必备集合][64bit-libs]。 + {: .linux-only} + 1.

Install Android Studio

+

安装 Android Studio

+ If you haven't done so already, [install and set up][as-install] the latest stable version of [Android Studio][]. + 若尚未完成,请[安装并设置][as-install]最新稳定版 [Android Studio][]。 + If you already have Android Studio installed, ensure that it's [up to date][as-update]. + 若已安装 Android Studio,请确保其[为最新版本][as-update]。 + 1.

Install Android SDK and tools

+

安装 Android SDK 与工具

+ 1. Launch **Android Studio**. + 启动 **Android Studio**。 + 1. Open the **SDK Manager** settings dialog. + 打开 **SDK Manager** 设置对话框。 + 1. If the **Welcome to Android Studio** dialog is open, click the **More Actions** button that follows the **New Project** and **Open** buttons, then click **SDK Manager** from the dropdown menu. + 若 **Welcome to Android Studio** 对话框已打开, + 点击 **New Project** 和 **Open** 按钮后的 **More Actions** 按钮, + 然后从下拉菜单点击 **SDK Manager**。 + 1. If you have a project open, go to **Tools** > **SDK Manager**. + 若已打开项目, + 前往 **Tools** > **SDK Manager**。 + {: type="a"} 1. If the **SDK Platforms** tab is not open, switch to it. + 若 **SDK Platforms** 标签页未打开,请切换到该标签页。 + 1. Verify that the first entry with an **API Level** of **36** has been selected. + 确认已选中 **API Level** 为 **36** 的第一项。 + If the **Status** column displays **Update available** or **Not installed**: + 若 **Status** 列显示 **Update available** 或 **Not installed**: + 1. Select the checkbox for that entry or row. + 选中该项或该行的复选框。 + 1. Click **Apply**. + 点击 **Apply**。 + 1. When the **Confirm Change** dialog displays, click **OK**. + 当 **Confirm Change** 对话框出现时,点击 **OK**。 + The **SDK Component Installer** dialog displays with a progress indicator. + **SDK Component Installer** 对话框会显示进度指示器。 + 1. When the installation finishes, click **Finish**. + 安装完成后,点击 **Finish**。 + {: type="a"} 1. Switch to the **SDK Tools** tab. + 切换到 **SDK Tools** 标签页。 + 1. Verify that the following SDK Tools have been selected: + 确认已选中以下 SDK Tools: + - **Android SDK Build-Tools** - **Android SDK Command-line Tools** - **Android Emulator** @@ -109,43 +182,74 @@ install and set up the latest stable version of [Android Studio][]. 1. If the **Status** column for any of the preceding tools displays **Update available** or **Not installed**: + 若上述任一工具的 **Status** 列显示 **Update available** 或 **Not installed**: + 1. Select the checkbox for the necessary tools. + 选中所需工具的复选框。 + 1. Click **Apply**. + 点击 **Apply**。 + 1. When the **Confirm Change** dialog displays, click **OK**. + 当 **Confirm Change** 对话框出现时,点击 **OK**。 + The **SDK Component Installer** dialog displays with a progress indicator. + **SDK Component Installer** 对话框会显示进度指示器。 + 1. When the installation finishes, click **Finish**. + 安装完成后,点击 **Finish**。 + {: type="a"} 1.

Agree to the Android licenses

+

同意 Android 许可

+ Before you can use Flutter and after you install all prerequisites, agree to the licenses of the Android SDK platform. + 在安装所有必备项后、使用 Flutter 之前, + 请同意 Android SDK 平台的许可。 + 1. Open your preferred terminal. + 打开你常用的终端。 + 1. Run the following command to review and sign the SDK licenses. + 运行以下命令以查看并签署 SDK 许可。 + ```console $ flutter doctor --android-licenses ``` 1. Read and accept any necessary licenses. + 阅读并接受所有必要的许可。 + If you haven't accepted each of the SDK licenses previously, you'll need to review and agree to them before developing for Android. + 若你此前未接受各项 SDK 许可, + 在针对 Android 开发前需要查看并同意它们。 + Before agreeing to the terms of each license, read each with care. + 在同意每项许可条款前,请仔细阅读。 + Once you've accepted all the necessary licenses successfully, you should see output similar to the following: + 成功接受所有必要许可后, + 你应看到类似以下的输出: + ```console All SDK package licenses accepted. ``` @@ -159,84 +263,151 @@ install and set up the latest stable version of [Android Studio][]. ## Set up an Android device {: #set-up-devices} +## 设置 Android 设备 {: #set-up-devices} + You can debug Flutter apps on physical Android devices or by running them on an Android emulator. +你可以在实体 Android 设备上调试 Flutter 应用, +或在 Android 模拟器上运行它们。 + To set up your development environment to run a Flutter app on an Android emulator, follow these steps: +要配置开发环境以在 Android 模拟器上运行 Flutter 应用,请按以下步骤操作: + 1.

Set up your development device

+

设置你的开发设备

+ Enable [VM acceleration][] on your development computer. + 在开发计算机上启用 [VM 加速][VM acceleration]。 + 1.

Set up a new emulator

+

设置新模拟器

+ 1. Start **Android Studio**. + 启动 **Android Studio**。 + 1. Open the **Device Manager** settings dialog. + 打开 **Device Manager** 设置对话框。 + 1. If the **Welcome to Android Studio** dialog is open, click the **More Actions** button that follows the **New Project** and **Open** buttons, then select **Virtual Device Manager** from the dropdown menu. + 若 **Welcome to Android Studio** 对话框已打开, + 点击 **New Project** 和 **Open** 按钮后的 **More Actions** 按钮, + 然后从下拉菜单选择 **Virtual Device Manager**。 + 1. If you have a project open, go to **Tools** > **Device Manager**. + 若已打开项目, + 前往 **Tools** > + **Device Manager**。 + {: type="a"} 1. Click the **Create Virtual Device** button that appears as a `+` icon. + 点击显示为 `+` 图标的 **Create Virtual Device** 按钮。 + The **Virtual Device Configuration** dialog displays. + **Virtual Device Configuration** 对话框会出现。 + 1. Select either **Phone** or **Tablet** under **Form Factor**. + 在 **Form Factor** 下选择 **Phone** 或 **Tablet**。 + 1. Select a device definition. You can browse or search for the device. + 选择设备定义。你可以浏览或搜索设备。 + 1. Click **Next**. + 点击 **Next**。 + 1. If the option is provided, select either **x86 Images** or **ARM Images** depending on if your development computer is an x64 or Arm64 device. + 若提供该选项, + 根据开发计算机是 x64 还是 Arm64 设备,选择 **x86 Images** 或 **ARM Images**。 + 1. Select one system image for the Android version you want to emulate. + 为要模拟的 Android 版本选择一个系统镜像。 + 1. If the desired image has a **Download** icon to the left of the system image name, click it. + 若所需镜像在系统镜像名称左侧有 **Download** 图标,请点击它。 + The **SDK Component Installer** dialog displays with a progress indicator. + **SDK Component Installer** 对话框会显示进度指示器。 + 1. When the download completes, click **Finish**. + 下载完成后,点击 **Finish**。 + {: type="a"} 1. Click **Additional settings** in the top tab bar and scroll to **Emulated Performance**. + 点击顶部标签栏中的 **Additional settings**, + 滚动到 **Emulated Performance**。 + 1. From the **Graphics acceleration** dropdown menu, select an option that mentions **Hardware**. + 在 **Graphics acceleration** 下拉菜单中, + 选择提及 **Hardware** 的选项。 + This enables [hardware acceleration][], improving render performance. + 这将启用[硬件加速][hardware acceleration],提升渲染性能。 + 1. Verify your virtual device configuration. If it is correct, click **Finish**. + 验证虚拟设备配置。 + 若正确,点击 **Finish**。 + To learn more about virtual devices, check out [Create and manage virtual devices][]. + 要了解更多虚拟设备信息, + 请参阅[创建和管理虚拟设备][Create and manage virtual devices]。 + 1.

Try running the emulator

+

尝试运行模拟器

+ In the **Device Manager** dialog, click the **Run** icon to the right of your desired virtual device. + 在 **Device Manager** 对话框中, + 点击所需虚拟设备右侧的 **Run** 图标。 + The emulator should start up and display the default canvas for your selected Android OS version and device. + 模拟器应启动并显示所选 Android 操作系统版本和设备的默认画布。 + {: .steps} [VM acceleration]: {{site.android-dev}}/studio/run/emulator-acceleration#accel-vm @@ -249,41 +420,70 @@ run a Flutter app on an Android emulator, follow these steps: To set up your development environment to run a Flutter app on a physical Android device, follow these steps: +要配置开发环境以在实体 Android 设备上运行 Flutter 应用,请按以下步骤操作: + 1.

Configure your device

+

配置你的设备

+ Enable **Developer options** and **USB debugging** on your device as described in [Configure on-device developer options][]. + 按[配置设备端开发者选项][Configure on-device developer options]中的说明,在设备上启用 **Developer options** 和 **USB debugging**。 + 1.

Enable wireless debugging

+

启用无线调试

+ To leverage wireless debugging, enable **Wireless debugging** on your device as described in [Connect to your device using Wi-Fi][]. {: .windows-only} + 要使用无线调试, + 请按[使用 Wi-Fi 连接设备][Connect to your device using Wi-Fi]中的说明,在设备上启用 **Wireless debugging**。 + {: .windows-only} + 1.

Install platform prerequisites

+

安装平台必备项

+ If you're developing on Windows, first install the necessary USB driver for your particular device as described in [Install OEM USB drivers][]. + 若在 Windows 上开发,请先按[安装 OEM USB 驱动][Install OEM USB drivers]中的说明,为特定设备安装必要的 USB 驱动。 + 1.

Connect your device

+

连接你的设备

+ Plug your device into your computer. If your device prompts you, authorize your computer to access your Android device. + 将设备插入计算机。 + 若设备提示你, + 请授权计算机访问你的 Android 设备。 + 1.

Verify the device connection

+

验证设备连接

+ To verify that Flutter recognizes your connected Android device, run `flutter devices` in your preferred terminal: + 要验证 Flutter 是否识别已连接的 Android 设备, + 请在你常用的终端运行 `flutter devices`: + ```console $ flutter devices ``` Your device should be found and show up as a connected device. + 应能找到你的设备并将其显示为已连接设备。 + {: .steps} [Configure on-device developer options]: {{site.android-dev}}/studio/debug/dev-options @@ -295,11 +495,18 @@ run a Flutter app on a physical Android device, follow these steps: ## Validate your setup {: #validate-setup} +## 验证你的设置 {: #validate-setup} + 1.

Check for toolchain issues

+

检查工具链问题

+ To check for any issues with your Android development setup, run the `flutter doctor` command in your preferred terminal: + 要检查 Android 开发设置是否存在问题, + 请在你常用的终端运行 `flutter doctor` 命令: + ```console $ flutter doctor ``` @@ -307,14 +514,23 @@ run a Flutter app on a physical Android device, follow these steps: If you see any errors or tasks to complete under the **Android toolchain** or **Android Studio** sections, + 若在 **Android toolchain** 或 **Android Studio** 部分看到任何错误或待完成任务, + Complete any mentioned tasks and then run `flutter doctor` again to verify any changes. + 请完成所有提到的任务,然后再次运行 `flutter doctor` 以验证变更。 + 1.

Check for Android devices

+

检查 Android 设备

+ To ensure you set up your emulator and/or physical Android device correctly, run `flutter emulators` and `flutter devices` in your preferred terminal: + 为确保你正确设置了模拟器和/或实体 Android 设备, + 请在你常用的终端运行 `flutter emulators` 和 `flutter devices`: + ```console $ flutter emulators && flutter devices ``` @@ -322,14 +538,25 @@ run a Flutter app on a physical Android device, follow these steps: Depending on if you set up an emulator or a device, at least one should output an entry with the platform marked as **android**. + 根据你设置的是模拟器还是设备, + 至少应有一项输出平台标记为 **android** 的条目。 + 1.

Troubleshoot setup issues

+

排查设置问题

+ If you need help resolving any setup issues, check out [Install and setup troubleshooting][]. + 若需要帮助解决任何设置问题, + 请参阅[安装与设置故障排除][Install and setup troubleshooting]。 + If you still have issues or questions, reach out on one of the Flutter [community][] channels. + 若仍有问题或疑问, + 可通过 Flutter [社区][community] 渠道联系。 + {: .steps} [Install and setup troubleshooting]: /install/troubleshoot#android-setup @@ -337,11 +564,18 @@ run a Flutter app on a physical Android device, follow these steps: ## Start developing for Android {: #start-developing} +## 开始为 Android 开发 {: #start-developing} + Congratulations! Now that you've set up Android development for Flutter, you can continue your Flutter learning journey while testing on Android or begin improving integration with Android. +恭喜! +既然你已为 Flutter 设置好 Android 开发, +你可以在 Android 上测试的同时继续 Flutter 学习之旅, +或开始改进与 Android 的集成。 + + + diff --git a/sites/docs/src/content/platform-integration/android/splash-screen.md b/sites/docs/src/content/platform-integration/android/splash-screen.md index 95bc19f23d..a2116f5263 100644 --- a/sites/docs/src/content/platform-integration/android/splash-screen.md +++ b/sites/docs/src/content/platform-integration/android/splash-screen.md @@ -22,7 +22,7 @@ while allowing time for the app engine to load and your app to initialize. 闪屏页(也称为启动页)是你的应用在启动时给用户的第一印象。 -它就像是你的应用的基础,同时允许你在它展示的时间里, +它就像是你的应用的基础,同时让你在它展示的时间里, 加载你的引擎和初始化你的应用。 本指南将展示如何在 Flutter 编写的移动应用中恰当地使用闪屏页。 diff --git a/sites/docs/src/content/platform-integration/bind-native-code.md b/sites/docs/src/content/platform-integration/bind-native-code.md index 3f3fb39532..e29a2798fa 100644 --- a/sites/docs/src/content/platform-integration/bind-native-code.md +++ b/sites/docs/src/content/platform-integration/bind-native-code.md @@ -1,30 +1,50 @@ --- -title: Bind to native code using FFI -description: To use native code in your Flutter program, use the dart:ffi library with the package_ffi template. +# title: Bind to native code using FFI +title: 使用 FFI 绑定原生代码 +# description: To use native code in your Flutter program, use the dart:ffi library with the package_ffi template. +description: 若要在 Flutter 程序中使用原生代码,请使用 dart:ffi 库及 package_ffi 模板。 +ai-translated: true --- Flutter apps can use the [dart:ffi][] library to call native APIs. _FFI_ stands for [_foreign function interface_][FFI]. Other terms for similar functionality include _native interface_ and _language bindings._ +Flutter 应用可以使用 [dart:ffi][] 库调用原生 API。_FFI_ 表示 +[_foreign function interface_][FFI](外部函数接口)。功能相近的其他说法还包括 +_原生接口_ 和 _语言绑定_。 + Since Flutter 3.38, the recommended way to bind to native code is to use the `flutter create --template=package_ffi` command. This template uses [build hooks][] to configure the native build in a `build.dart` script, and no longer requires OS-specific build files. This approach works for both Flutter and Dart standalone projects. +自 Flutter 3.38 起,推荐通过 +`flutter create --template=package_ffi` 命令绑定原生代码。该模板使用 +[build hooks][] 在 +`build.dart` 脚本中配置原生构建,不再需要面向各操作系统的专用构建文件。此方式同时适用于 Flutter 与独立的 Dart 项目。 + If you need to use the Flutter Plugin API, or if you need to configure a Google Play services runtime on Android, use the standard plugin template (`flutter create --template=plugin`). +若你需要使用 Flutter Plugin API,或在 Android 上配置 Google +Play services 运行时,请使用标准插件模板(`flutter create +--template=plugin`)。 + [build hooks]: https://dart.dev/tools/hooks [dart:ffi]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html [FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface ## Create an FFI package +## 创建 FFI 包 + To create an FFI package, run the following command: +要创建 FFI 包,请运行以下命令: + ```console $ flutter create --template=package_ffi native_add $ cd native_add @@ -32,84 +52,153 @@ $ cd native_add This creates a package with the following specialized content: +这将创建一个包含以下专用内容的包: + - **`lib/native_add.dart`**: The Dart code that defines the API of the package. + + **`lib/native_add.dart`**:定义该包 API 的 Dart 代码。 + - **`lib/native_add_bindings_generated.dart`**: The generated Dart bindings for the native code. + + **`lib/native_add_bindings_generated.dart`**:为原生代码生成的 Dart 绑定。 + - **`src/native_add.c`**: The native C source code. + + **`src/native_add.c`**:原生 C 源代码。 + - **`src/native_add.h`**: The C header file for the native code. + + **`src/native_add.h`**:原生代码的 C 头文件。 + - **`hook/build.dart`**: A script that is run by the Flutter SDK to compile the native code. + + **`hook/build.dart`**:由 Flutter SDK 运行以编译原生代码的脚本。 + - **`ffigen.yaml`**: The configuration file for [`package:ffigen`][] to generate the Dart bindings. + + **`ffigen.yaml`**:供 [`package:ffigen`][] 生成 Dart 绑定的配置文件。 + - **`pubspec.yaml`**: The package definition, which enables the `build.dart` hook. + **`pubspec.yaml`**:包定义文件,用于启用 `build.dart` hook。 + [`package:ffigen`]: {{site.pub-pkg}}/ffigen ## The native code +## 原生代码 + The native code is located in `src/native_add.c` and `src/native_add.h`. The C function `sum` is defined in the `.c` file and its signature is in the header file. The function is marked to be exported so that it can be called from Dart. +原生代码位于 `src/native_add.c` 与 `src/native_add.h`。C 函数 `sum` 定义在 `.c` 文件中,其签名在头文件中。该函数被标记为导出,以便从 Dart 调用。 + ## The build hook +## 构建 hook + The native code is compiled and bundled with your app automatically. This is done by the `hook/build.dart` script, which is a [build hook][build hooks]. +原生代码会自动编译并打包进你的应用。这由 `hook/build.dart` 脚本完成,它是一个 [build hook][build hooks]。 + This means you no longer need to write OS-specific build files (like `CMakeLists.txt` for Linux/Windows, `.podspec` for iOS/macOS, or `build.gradle` for Android) to compile your native code. +这意味着你不再需要编写面向各操作系统的构建文件(例如 Linux/Windows 的 `CMakeLists.txt`、iOS/macOS 的 `.podspec`,或 Android 的 `build.gradle`)来编译原生代码。 + The build hook uses `package:native_toolchain_c` to compile the C code into a dynamic library. You can customize this file to build other native languages or to download pre-compiled binaries. +构建 hook 使用 `package:native_toolchain_c` 将 C 代码编译为动态库。你可以自定义该文件以构建其他原生语言,或下载预编译二进制文件。 + ## The Dart code +## Dart 代码 + The Dart code defines the public API of the package. +Dart 代码定义该包的公共 API。 + ### Generating the bindings +### 生成绑定 + To bind to the native code, the template uses [`package:ffigen`][] to generate bindings from the header file (`src/native_add.h`). The generation is configured in `ffigen.yaml`. +要绑定原生代码,模板使用 [`package:ffigen`][] 从头文件(`src/native_add.h`)生成绑定。生成配置在 `ffigen.yaml` 中。 + This generates `lib/native_add_bindings_generated.dart`. +这将生成 `lib/native_add_bindings_generated.dart`。 + ### Calling the native function +### 调用原生函数 + The generated bindings in `lib/native_add_bindings_generated.dart` contain `@Native() external` functions. These functions are automatically resolved at runtime against the code asset outputted by the build hook (which runs at build time). This means there is no OS-specific logic required for `dlopen`-ing the dynamic libraries, making the Dart code truly cross-platform. +`lib/native_add_bindings_generated.dart` 中的生成绑定包含 +`@Native() external` 函数。这些函数在运行时自动解析为构建 hook(在构建时运行)输出的 code asset。这意味着无需为 `dlopen` 动态库编写面向各操作系统的逻辑,使 Dart 代码真正跨平台。 + The main library file `lib/native_add.dart` exposes these functions. Your app can then call these functions by importing `package:native_add/native_add.dart`. +主库文件 `lib/native_add.dart` 对外暴露这些函数。你的应用随后可通过导入 `package:native_add/native_add.dart` 调用它们。 + ## Testing +## 测试 + The generated package includes a unit test in `test/native_add_test.dart` that shows how to test the native function. +生成的包在 `test/native_add_test.dart` 中包含单元测试,演示如何测试原生函数。 + ## Other use cases +## 其他用例 + ### System libraries +### 系统库 + To link against a system library, you modify the `build.dart` hook to specify the linking mode. Instead of compiling source code, you create a `CodeAsset` and set its `linkMode`. +要链接系统库,请修改 `build.dart` hook 以指定链接模式。不再编译源代码,而是创建 `CodeAsset` 并设置其 `linkMode`。 + For many system libraries on Android, iOS, Linux, and macOS, you can use `LookupInProcess()` to find symbols in the main process. +在 Android、iOS、Linux 和 macOS 上,对许多系统库可使用 +`LookupInProcess()` 在主进程中查找符号。 + For Windows, you often use `DynamicLoadingSystem()` and provide the name of the DLL. +在 Windows 上,通常使用 `DynamicLoadingSystem()` 并提供 +DLL 名称。 + Here is an example `build.dart` that links against system libraries to get the host name: +以下是一个链接系统库以获取主机名的 `build.dart` 示例: + ```dart // hook/build.dart import 'package:hooks/hooks.dart'; @@ -145,37 +234,63 @@ void main(List args) async { The Dart files (`unix.dart`, `windows.dart`) would then contain the `external` functions that use the symbols from these system libraries. +随后 Dart 文件(`unix.dart`、`windows.dart`)将包含使用这些系统库符号的 `external` 函数。 + #### Bundling `libc++_shared.so` on Android +#### 在 Android 上打包 `libc++_shared.so` + Although `libc++_shared.so` ships with the Android NDK, it isn't a system library. If your app or package uses the [C++ standard library][libcpp-support], or includes [multiple shared libraries][shared-libraries] that depend on it, your app needs to bundle `libc++_shared.so`. +尽管 `libc++_shared.so` 随 Android NDK 提供, +它并非系统库。 +若你的应用或包使用 [C++ 标准库][libcpp-support], +或包含依赖它的 [多个共享库][shared-libraries], +你的应用需要打包 `libc++_shared.so`。 + To bundle the library in your app, add a dependency on [`package:android_libcpp_shared`][libcpp-shared], which uses its own build hook to bundle `libc++_shared.so` from the locally installed NDK for each target architecture. +要在应用中打包该库, +请添加对 [`package:android_libcpp_shared`][libcpp-shared] 的依赖; +该包使用自己的 build hook,从本地安装的 NDK 为各目标架构打包 `libc++_shared.so`。 + [libcpp-support]: {{site.android-dev}}/ndk/guides/cpp-support#cs [shared-libraries]: {{site.android-dev}}/ndk/guides/cpp-support#shared_runtimes [libcpp-shared]: {{site.pub}}/packages/android_libcpp_shared ### Closed-source libraries +### 闭源库 + You can also use build hooks to link against pre-compiled, closed-source libraries. The recommended approach is to download the pre-compiled binaries at build time and verify their integrity with a file hash. +你也可以使用 build hook 链接预编译的闭源库。推荐做法是在构建时下载预编译二进制文件,并通过文件哈希校验其完整性。 + In your `build.dart` hook, you would: 1. Download the library from a URL. 2. Verify the hash of the downloaded file. 3. Place the library in the build output directory. 4. Create a `CodeAsset` with `DynamicLoading` pointing to the library. +在 `build.dart` hook 中,你需要: +1. 从 URL 下载库。 +2. 校验已下载文件的哈希。 +3. 将库放入构建输出目录。 +4. 创建指向该库的 `DynamicLoading` 的 `CodeAsset`。 + Here is a simplified example of the `CodeAsset` creation: +以下是创建 `CodeAsset` 的简化示例: + ```dart // hook/build.dart import 'package:hooks/hooks.dart'; @@ -201,30 +316,50 @@ void main(List args) async { You would need to handle different architectures and platforms by having different versions of your pre-compiled library. +你需要为不同架构和平台准备不同版本的预编译库。 + For more examples, see the [code_assets package examples](https://pub.dev/packages/code_assets/example). +更多示例请参阅 [code_assets 包示例](https://pub.dev/packages/code_assets/example)。 + ## Dynamic library naming guidelines +## 动态库命名指南 + When implementing `build.dart` hooks for packages that bundle code assets, it is critical to ensure consistent naming of your dynamic libraries across all target architectures and SDKs. +为打包 code asset 的包实现 `build.dart` hook 时, +务必在所有目标架构和 SDK 上为动态库保持一致的命名。 + On Apple platforms (iOS and macOS), dynamic libraries are bundled into frameworks. Flutter's build system relies on these names to generate metadata and package distributable formats like XCFrameworks. +在 Apple 平台(iOS 和 macOS)上,动态库被打包进 +framework。Flutter 的构建系统依赖这些名称生成元数据, +并打包 XCFrameworks 等可分发格式。 + ### Consistency across architectures +### 跨架构一致性 + For a given asset ID, your hook will be invoked multiple times, once per architecture. Your hook must produce the same filename regardless of the target architecture (for example, `arm64` vs. `x64`). +对于给定的 asset ID,你的 hook 会被多次调用,每个架构一次。无论目标架构如何(例如 `arm64` 与 `x64`),hook 必须生成相同的文件名。 + * **Why?** Within a single SDK build, Flutter combines architecture-specific binaries into a single universal (fat) binary using `lipo`. If architectures have different filenames, the tool will pick one non-deterministically and issue a warning. Furthermore, error messages at runtime will be confusing for your users if dynamic libraries are renamed. + + **原因?** 在单次 SDK 构建中,Flutter 使用 `lipo` 将各架构的二进制合并为单个通用(fat)二进制。若各架构文件名不同,工具会非确定性地选取其一并发出警告。此外,若动态库被重命名,运行时错误信息会让用户困惑。 + * **Recommended action**: Avoid adding architecture suffixes to your filenames (for example, use `libsqlite3.dylib` instead of `libsqlite3_arm64.dylib`). Instead, write the file to @@ -232,18 +367,27 @@ architecture (for example, `arm64` vs. `x64`). architecture-specific subdirectory of `input.outputDirectoryShared` (for example, `input.outputDirectoryShared.resolve('$architecture/')`). + **建议做法**:避免在文件名中添加架构后缀(例如使用 `libsqlite3.dylib` 而非 `libsqlite3_arm64.dylib`)。改为将文件写入 `input.outputDirectory`(每个架构唯一),或写入 `input.outputDirectoryShared` 下按架构划分的子目录(例如 `input.outputDirectoryShared.resolve('$architecture/')`)。 + ### Consistency across SDKs (iOS) +### 跨 SDK 一致性(iOS) + When building for iOS, your hook will be invoked multiple times with different values for the SDK and architecture. Both physical device (`iphoneos`) and simulator (`iphonesimulator`) invocations must produce the same framework name for the same asset ID. +为 iOS 构建时,你的 hook 会针对不同的 SDK 和架构被多次调用。真机(`iphoneos`)与模拟器(`iphonesimulator`)的调用必须为同一 asset ID 生成相同的 framework 名称。 + * **Why?** Flutter uses `xcodebuild -create-xcframework` to combine these outputs. Xcode requires that all platform slices within an XCFramework share the same framework name to allow seamless linking. If filenames differ, the Flutter tool cannot create a correct XCFramework, and commands like `flutter build ios-framework` will fail. + + **原因?** Flutter 使用 `xcodebuild -create-xcframework` 合并这些输出。Xcode 要求 XCFramework 内所有平台 slice 共享同一 framework 名称以实现无缝链接。若文件名不同,Flutter 工具无法创建正确的 XCFramework,`flutter build ios-framework` 等命令会失败。 + * **Recommended action**: Do not use suffixes like `_sim` or `_simulator` for the simulator build. The XCFramework structure already handles the platform separation internally (for example, @@ -251,11 +395,17 @@ for the same asset ID. write the file to `input.outputDirectory` (which is unique per SDK) or to an SDK-specific subdirectory of `input.outputDirectoryShared`. + **建议做法**:模拟器构建不要使用 `_sim` 或 `_simulator` 等后缀。XCFramework 结构已在内部处理平台分离(例如 `MyLib.xcframework/ios-arm64_x86_64-simulator/MyLib.framework`)。改为将文件写入 `input.outputDirectory`(每个 SDK 唯一),或写入 `input.outputDirectoryShared` 下按 SDK 划分的子目录。 + ### Consistency in the set of assets +### asset 集合的一致性 + Your hook must produce the same set of Asset IDs across all SDKs for a given target platform. +对于给定目标平台,你的 hook 必须在所有 SDK 上生成相同的 Asset ID 集合。 + * **Why?** Apple's build system and App Store validation require that all frameworks included in an application are compatible with the target device. If you produce an asset for the simulator (`iphonesimulator`) but @@ -263,7 +413,12 @@ target platform. contain a slice that has no counterpart for the device. This can lead to build failures or Apple rejecting the application for including simulator-only binaries in a device build. + + **原因?** Apple 的构建系统与 App Store 校验要求应用内所有 framework 与目标设备兼容。若你为模拟器(`iphonesimulator`)生成 asset 但未为真机(`iphoneos`)生成,得到的 XCFramework 会包含在设备上无对应项的 slice。这可能导致构建失败,或 Apple 因设备构建包含仅模拟器二进制而拒绝应用。 + * **Recommended action**: Ensure that your `build.dart` hook logic handles all supported SDKs consistently. If you produce an asset for one SDK, you must produce a corresponding asset for all other SDKs for that platform. For SDK-specific code, you can use stub implementations for other SDKs. + + **建议做法**:确保 `build.dart` hook 逻辑一致处理所有受支持的 SDK。若为一个 SDK 生成 asset,必须为该平台所有其他 SDK 生成对应 asset。对于 SDK 专用代码,可为其他 SDK 使用桩实现。 diff --git a/sites/docs/src/content/platform-integration/ios/app-extensions.md b/sites/docs/src/content/platform-integration/ios/app-extensions.md index 96d5330872..e42c2e070d 100644 --- a/sites/docs/src/content/platform-integration/ios/app-extensions.md +++ b/sites/docs/src/content/platform-integration/ios/app-extensions.md @@ -1,22 +1,33 @@ --- -title: Adding iOS app extensions -description: Learn how to add app extensions to your Flutter apps +# title: Adding iOS app extensions +title: 添加 iOS App Extension +# description: Learn how to add app extensions to your Flutter apps +description: 了解如何向 Flutter 应用添加 App Extension +ai-translated: true --- This guide shows you how to use iOS app extensions with a Flutter app. -## Overview {: #overview } +本指南介绍如何在你的 Flutter 应用中使用 iOS App Extension。 + +## Overview {: #overview} + +## 概述 {: #overview} [iOS app extensions][] allow you to expand functionality outside of your iOS app. Your app could appear as a home screen widget, or you can make portions of your app available within other apps. +[iOS app extensions][] 让你能在 iOS 应用之外扩展功能。你的应用可以显示为主屏幕 widget,也可以让应用的部分功能在其他应用中可用。 + In the following example, when a user selects a photo to share in the iOS Photo app, a Flutter app called `Example App With Extension` is displayed in the Photo apps share sheet: +在下面的示例中,当用户在 iOS「照片」应用中选择要分享的照片时,名为 `Example App With Extension` 的 Flutter 应用会出现在照片应用的分享表单中: +
Share sheet with a Flutter app in it. @@ -25,7 +36,9 @@ Photo apps share sheet: [iOS app extensions]: {{site.apple-dev}}/app-extensions/ -## Add an iOS app extension to your Flutter app {: #add-extension } +## Add an iOS app extension to your Flutter app {: #add-extension} + +## 向 Flutter 应用添加 iOS App Extension {: #add-extension} If you want to integrate your Flutter app with the iOS operating system, you can add iOS app extensions @@ -35,9 +48,13 @@ app extension to a new Flutter app called `example_app_with_extension`, but you can always start with an existing project. +若要将 Flutter 应用与 iOS 操作系统集成,可以向 Flutter 项目添加 iOS App Extension。为便于操作,以下步骤演示如何向名为 `example_app_with_extension` 的新 Flutter 应用添加 [Share][] App Extension;你也可以从现有项目开始。 + 1. In the console, create a new Flutter project called `example_app_with_extension`. + 在控制台中,创建一个名为 `example_app_with_extension` 的新 Flutter 项目。 + ```console $ flutter create example_app_with_extension ``` @@ -45,6 +62,8 @@ an existing project. 1. In the console, open the Xcode workspace for the `example_app_with_extension` project. + 在控制台中,打开 `example_app_with_extension` 项目的 Xcode 工作区。 + ```console $ cd example_app_with_extension && open ios/Runner.xcworkspace ``` @@ -52,129 +71,200 @@ an existing project. 1. In Xcode, add an app extension called `Share` and call it `ShareExtension`. + 在 Xcode 中,添加名为 `Share` 的 App Extension,并将其命名为 `ShareExtension`。 + * In the Xcode menu bar, select **File** > **New** > **Target**. + 在 Xcode 菜单栏中,选择 **File** > **New** > **Target**。 + * Add **Share Extension**. + 添加 **Share Extension**。 + * In the **Name field**, enter **ShareExtension**. + 在 **Name** 字段中输入 **ShareExtension**。 + * Click **Finish**. + 点击 **Finish**。 + * In the **Activate … Scheme** dialog box that appears, select **Activate**. + 在出现的 **Activate … Scheme** 对话框中,选择 **Activate**。 + 1. In Xcode, change the order of the build process. + 在 Xcode 中,调整构建过程的顺序。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the **project navigator**, at the top, select **Runner**. + 在 **项目导航器** 顶部,选择 **Runner**。 + * In the main window under **TARGETS**, select **Runner**. + 在主窗口的 **TARGETS** 下,选择 **Runner**。 + * Open the **Build Phases** tab. + 打开 **Build Phases** 标签页。 + * Drag **Embed Foundation Extensions** above **Run Script**. + 将 **Embed Foundation Extensions** 拖到 **Run Script** 上方。 + 1. Make sure your **Minimum Deployments** iOS value is properly set and matches in both **Runner** and **ShareExtension** + 确保 **Runner** 与 **ShareExtension** 的 **Minimum Deployments** iOS 值已正确设置且一致 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the **project navigator**, at the top, select **Runner**. + 在 **项目导航器** 顶部,选择 **Runner**。 + * In the main window under **TARGETS**, select **Runner**. + 在主窗口的 **TARGETS** 下,选择 **Runner**。 + * On the **General** tab check your **Minimum Deployments** dropdown value to match the one you have on **ShareExtension** > **General** tab. + 在 **General** 标签页中,检查 **Minimum Deployments** 下拉值是否与 **ShareExtension** > **General** 标签页中的值一致。 + 1. In the console, run the following command to rebuild your iOS app: + 在控制台中,运行以下命令以重新构建 iOS 应用: + ```console $ flutter build ios --config-only ``` 1. [Test your app with the simulator][]. + [使用模拟器测试应用][]。 + When you add a new app extension, Xcode generates sample code based on the template you selected. For more information about the generated code and WidgetKit, see [Apple's app extension documentation][]. +添加新的 App Extension 时,Xcode 会根据所选模板生成示例代码。有关生成代码与 WidgetKit 的更多信息,请参阅 [Apple 的 App Extension 文档][Apple's app extension documentation]。 + [Apple's app extension documentation]: {{site.apple-dev}}/app-extensions/ [Test your app with the simulator]: #test-extensions [Share]: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html +[使用模拟器测试应用]: #test-extensions + +## Test an iOS app extension {: #test-extensions} -## Test an iOS app extension {: #test-extensions } +## 测试 iOS App Extension {: #test-extensions} After you've added an app extension to your Flutter project, you can test it, using a simulator or physical device. If you are testing you extension in debug mode, you must use the iOS simulator. +向 Flutter 项目添加 App Extension 后,可使用模拟器或真机进行测试。若在 debug 模式下测试扩展,必须使用 iOS 模拟器。 + The following steps assume you're using the sample application and Share extension from [Adding iOS app extensions][]. +以下步骤假定你使用的是 [添加 iOS App Extension][] 中的示例应用与 Share 扩展。 + 1. In Xcode, [add an app extension to your project][]. + 在 Xcode 中,[向项目添加 App Extension][]。 + 1. In the console, use the following command to run your iOS app: + 在控制台中,使用以下命令运行 iOS 应用: + ```console $ flutter run ``` 1. In the simulator, test your app extension. + 在模拟器中测试 App Extension。 + * Launch an app that supports the Share extension, such as the Photos app. + 启动支持 Share 扩展的应用(例如「照片」应用)。 + * Select a photo, tap the share button, then tap on the share extension icon of your app. + 选择一张照片,点按分享按钮,再点按你应用的分享扩展图标。 + 1. Add an app extension to your project. + 向项目添加 App Extension。 + 1. In the console, run your Flutter app in release mode: + 在控制台中,以 release 模式运行 Flutter 应用: + ```console $ flutter run --release ``` 1. On your device, test your app extension. + 在设备上测试 App Extension。 + * Launch an app that supports the Share extension, such as the Photos app. + 启动支持 Share 扩展的应用(例如「照片」应用)。 + * Select a photo, tap the share button, then tap on the share extension icon of your app. + 选择一张照片,点按分享按钮,再点按你应用的分享扩展图标。 + [Adding iOS app extensions]: #add-extension [add an app extension to your project]: #add-extension +[添加 iOS App Extension]: #add-extension +[向项目添加 App Extension]: #add-extension -## Additional ways to interact with iOS app extensions {: #interact-app-extensions } +## Additional ways to interact with iOS app extensions {: #interact-app-extensions} + +## 与 iOS App Extension 交互的其他方式 {: #interact-app-extensions} Flutter apps interact with iOS app extensions using the same techniques as UIKit or SwiftUI apps. @@ -185,7 +275,11 @@ The app and your extension can read and write to shared resources or use higher-level APIs to communicate with each other. -### Use higher-level APIs {: #using-higher-level-apis } +Flutter 应用与 iOS App Extension 的交互方式与 UIKit 或 SwiftUI 应用相同。宿主应用与 App Extension 不直接通信;用户在设备上与扩展交互时,宿主应用可能并未运行。应用与扩展可通过读写共享资源或使用更高级 API 相互通信。 + +### Use higher-level APIs {: #using-higher-level-apis} + +### 使用更高级的 API {: #using-higher-level-apis} Some extensions have APIs. For example, the [Core Spotlight][] framework indexes your app, @@ -193,98 +287,160 @@ allowing users to search from Spotlight and Safari. The [WidgetKit][] framework can trigger an update of your home screen widget. +部分扩展提供 API。例如,[Core Spotlight][] 框架会为你的应用建立索引,使用户可从 Spotlight 和 Safari 搜索;[WidgetKit][] 框架可触发主屏幕 widget 的更新。 + To simplify how your app communicates with extensions, Flutter plugins wrap these APIs. To find plugins that wrap extension APIs, check out [Leveraging Apple's System APIs and Frameworks][leverage] or search [pub.dev][]. +为简化应用与扩展的通信,Flutter 插件封装了这些 API。要查找封装扩展 API 的插件,请参阅 [利用 Apple 系统 API 与框架][leverage],或在 [pub.dev][] 上搜索。 + [Core Spotlight]: {{site.apple-dev}}/documentation/corespotlight [leverage]: /platform-integration/ios/apple-frameworks +[利用 Apple 系统 API 与框架]: /platform-integration/ios/apple-frameworks [pub.dev]: {{site.pub-pkg}} [WidgetKit]: {{site.apple-dev}}/documentation/widgetkit -### Share resources {: #sharing-resources } +### Share resources {: #sharing-resources} + +### 共享资源 {: #sharing-resources} To share resources between your Flutter app and your app extension, put the `Runner` app target and the extension target in the same [App Group][]. +要在 Flutter 应用与 App Extension 之间共享资源,将 `Runner` 应用 target 与扩展 target 置于同一 [App Group][] 中。 + :::note You must be signed in to your Apple Developer account. + +你必须登录 Apple Developer 账户。 ::: To add a target to an App Group: +要将 target 添加到 App Group: + 1. Open the target settings in Xcode. + + 在 Xcode 中打开 target 设置。 + 1. Navigate to the **Signing & Capabilities** tab. + + 进入 **Signing & Capabilities** 标签页。 + 1. Select **+ Capability** then **App Groups**. + + 选择 **+ Capability**,然后选择 **App Groups**。 + 1. Choose which App Group you want to add the target from one of two options: + 从以下两种方式之一选择要添加 target 的 App Group: + {: type="a"} 1. Select an App Group from the list. + + 从列表中选择一个 App Group。 + 1. Click **+** to add a new App Group. + 点击 **+** 添加新的 App Group。 + When two targets belong to the same App Group, they can read from and write to the same source. Choose one of the following sources for your data. +当两个 target 属于同一 App Group 时,它们可读写同一数据源。请为数据选择以下数据源之一: + * **Key/value:** Use the [`shared_preference_app_group`][] plugin to read or write to `UserDefaults` within the same App Group. + + **键值对:** 使用 [`shared_preference_app_group`][] 插件在同一 App Group 内读写 `UserDefaults`。 + * **File:** Use the App Group container path from the [`path_provider`][] plugin to [read and write files][]. + + **文件:** 使用 [`path_provider`][] 插件获取 App Group 容器路径,以 [读写文件][]。 + * **Database:** Use the App Group container path from the [`path_provider`][] plugin to create a database with the [`sqflite`][] plugin. + **数据库:** 使用 [`path_provider`][] 插件获取 App Group 容器路径,并用 [`sqflite`][] 插件创建数据库。 + [App Group]: {{site.apple-dev}}/documentation/xcode/configuring-app-groups [`path_provider`]: {{site.pub-pkg}}/path_provider [read and write files]: /cookbook/persistence/reading-writing-files +[读写文件]: /cookbook/persistence/reading-writing-files [`shared_preference_app_group`]: {{site.pub-pkg}}/shared_preference_app_group [`sqflite`]: {{site.pub-pkg}}/sqflite -### Schedule background updates {: #background-updates } +### Schedule background updates {: #background-updates} + +### 安排后台更新 {: #background-updates} Background tasks provide a means to update your extension through code regardless of the status of your app. +后台任务让你能在应用处于任何状态时通过代码更新扩展。 + To schedule background work from your Flutter app, use the [`workmanager`][] plugin. +要从 Flutter 应用安排后台工作,请使用 [`workmanager`][] 插件。 + [`workmanager`]: {{site.pub-pkg}}/workmanager -### Add deep linking {: #deep-linking } +### Add deep linking {: #deep-linking} + +### 添加深层链接 {: #deep-linking} You might want to direct users from an app extension to a specific page in your Flutter app. To open a specific route in your app, you can use [Deep Linking][]. +你可能希望将用户从 App Extension 引导至 Flutter 应用中的特定页面。要在应用中打开特定路由,可使用 [深层链接][]。 + [Deep Linking]:/ui/navigation/deep-linking +[深层链接]:/ui/navigation/deep-linking -### Add a scrollable list {: #advanced-scrolling-behavior } +### Add a scrollable list {: #advanced-scrolling-behavior} + +### 添加可滚动列表 {: #advanced-scrolling-behavior} By default, flutter view does not handle scroll gestures in a [Share][] extension. To support a scrollable list in the Share extension, follow [the instructions on GitHub][issue-164670]. +默认情况下,[Share][] 扩展中的 Flutter 视图不处理滚动手势。若要在 Share 扩展中支持可滚动列表,请按 [GitHub 上的说明][issue-164670] 操作。 + [Share]: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html [issue-164670]: {{site.repo.flutter}}/issues/164670#issuecomment-2762124121 +[GitHub 上的说明]: {{site.repo.flutter}}/issues/164670#issuecomment-2762124121 -### Open a Flutter app in an iOS app extension {: #creating-app-extension-uis-with-flutter } +### Open a Flutter app in an iOS app extension {: #creating-app-extension-uis-with-flutter} + +### 在 iOS App Extension 中打开 Flutter 应用 {: #creating-app-extension-uis-with-flutter} You can open your Flutter app directly in some iOS app extensions, such as the [Share][] extension, with a `FlutterViewController`. +你可以通过 `FlutterViewController` 在某些 iOS App Extension(例如 [Share][] 扩展)中直接打开 Flutter 应用。 + In the following example, a Flutter app called `Example App With Extension` is opened in the Share extension, which lets users share content between apps: +在下面的示例中,名为 `Example App With Extension` 的 Flutter 应用在 Share 扩展中打开,用户可在应用间分享内容: +
An example of an entry added to the share menu by a Flutter app @@ -297,113 +453,190 @@ scheme is called `ShareExtension`, the Flutter app scheme is called `Runner`, and the Flutter app is called `Example App With Extension`: +按以下步骤在 [Share][] App Extension 中显示 Flutter 应用。本示例中,App Extension 的 scheme 为 `ShareExtension`,Flutter 应用的 scheme 为 `Runner`,应用名为 `Example App With Extension`: + 1. [Add an extension to your Flutter app][] if you haven't already done so. + 若尚未添加,请先 [向 Flutter 应用添加扩展][]。 + 1. In the console, navigate to your Flutter project directory and then open your project in Xcode with the following command: + 在控制台中,进入 Flutter 项目目录,然后用以下命令在 Xcode 中打开项目: + ```console open ios/Runner.xcworkspace ``` 1. In Xcode, disable user script sandboxing. + 在 Xcode 中,禁用用户脚本沙盒(User Script Sandboxing)。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the main window under **TARGETS**, select **ShareExtension**. + 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 + * Open the **Build Settings** tab. + 打开 **Build Settings** 标签页。 + * Navigate to **Build Options**. + 找到 **Build Options**。 + * Set **User Script Sandboxing** as **No**. + 将 **User Script Sandboxing** 设为 **No**。 + 1. In Xcode, add the pre-action to the `ShareExtension` scheme. + 在 Xcode 中,为 `ShareExtension` scheme 添加预操作(pre-action)。 + * Open the **Manage Schemes** window (**Product** > **Scheme** > **Manage Schemes**). + 打开 **Manage Schemes** 窗口(**Product** > **Scheme** > **Manage Schemes**)。 + * Select the **ShareExtension** scheme and edit it. + 选择 **ShareExtension** scheme 并编辑。 + * Expand the **Build** tab. + 展开 **Build** 标签页。 + * Select **Pre-actions**. + 选择 **Pre-actions**。 + * Click **+** and select **New Run Script Action**. + 点击 **+**,选择 **New Run Script Action**。 + * In the **Provide build settings from** drop-down list, select **ShareExtension**. + 在 **Provide build settings from** 下拉列表中选择 **ShareExtension**。 + * In the **Shell** text field, enter: + 在 **Shell** 文本框中输入: + ```console /bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ``` * Click **Close**. + 点击 **Close**。 + 1. In Xcode, share the build configurations. + 在 Xcode 中,共享构建配置。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the main window under **PROJECT**, select **Runner**. + 在主窗口的 **PROJECT** 下,选择 **Runner**。 + * Open the **Info** tab. + 打开 **Info** 标签页。 + * Expand **Configuration**. + 展开 **Configuration**。 + * Expand **Debug** and update the value for **ShareExtension** to match the value for **Runner**. + 展开 **Debug**,将 **ShareExtension** 的值更新为与 **Runner** 一致。 + * Repeat the previous step for **Profile**, and **Release**. + 对 **Profile** 和 **Release** 重复上一步。 + * When you are finished, make sure that the configurations look similar to the following: + 完成后,确认配置与下图类似: + ![Xcode configurations](/assets/images/docs/development/platform-integration/app-extensions/xcode-configurations.png) 1. (Optional) In Xcode, replace any storyboard files with an extension class, if needed. + (可选)如有需要,在 Xcode 中用扩展类替换 storyboard 文件。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * Select **Runner** > **ShareExtension** > **Info**. + 选择 **Runner** > **ShareExtension** > **Info**。 + * Expand **Information Property List**. + 展开 **Information Property List**。 + * Delete the **NSExtensionMainStoryboard** key. + 删除 **NSExtensionMainStoryboard** 键。 + * Add the **NSExtensionPrincipalClass** key. + 添加 **NSExtensionPrincipalClass** 键。 + * Add one of these values for the `NSExtensionPrincipalClass` key: + 为 `NSExtensionPrincipalClass` 键添加以下值之一: + * (Swift) **ShareExtension.ShareViewController** + * (Swift)**ShareExtension.ShareViewController** * (Objective-C) **ShareViewController** + * (Objective-C)**ShareViewController** 1. In Xcode, update the `ShareViewController` to use the `FlutterViewController`. + 在 Xcode 中,将 `ShareViewController` 更新为使用 `FlutterViewController`。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * Select **Runner** > **ShareExtension** > **ShareViewController**. + 选择 **Runner** > **ShareExtension** > **ShareViewController**。 + * Update `ShareViewController` to use the `FlutterViewController` class: + 将 `ShareViewController` 更新为使用 `FlutterViewController` 类: + + ```swift title="ShareViewController.swift" @@ -479,70 +712,116 @@ class ShareViewController: UIViewController { 8. [Test your app with the simulator][]. +8. [使用模拟器测试应用][]。 + [Add an extension to your Flutter app]: #add-extension [Share]: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html [Test your app with the simulator]: #test-extensions +[向 Flutter 应用添加扩展]: #add-extension +[使用模拟器测试应用]: #test-extensions ### Register plugins +### 注册插件 + Use the following steps to register plugins for an app extension. In this example, the app extension scheme is called `ShareExtension`, the Flutter app scheme is called `Runner`, and the Flutter app is called `Example App With Extension`: +按以下步骤为 App Extension 注册插件。本示例中,App Extension 的 scheme 为 `ShareExtension`,Flutter 应用的 scheme 为 `Runner`,应用名为 `Example App With Extension`: + 1. [Add an extension to your Flutter app][] if you haven't already done so. + 若尚未添加,请先 [向 Flutter 应用添加扩展][]。 + 1. In Xcode, add `GeneratedPluginRegistrant.m` to the app extension target. + 在 Xcode 中,将 `GeneratedPluginRegistrant.m` 添加到 App Extension target。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the main window under **TARGETS**, select **ShareExtension**. + 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 + * Open the **Build Phases** tab. + 打开 **Build Phases** 标签页。 + * Expand **Compile Sources**. + 展开 **Compile Sources**。 + * Click **+**. + 点击 **+**。 + * From the list in the _Choose item to add_ dialog box, select **GeneratedPluginRegistrant.m**. + 在 _Choose item to add_ 对话框的列表中选择 **GeneratedPluginRegistrant.m**。 + * Click **Add**. + 点击 **Add**。 + 1. (Swift only) In Xcode, update the `SWIFT_OBJC_BRIDGING_HEADER` build setting. + (仅 Swift)在 Xcode 中,更新 `SWIFT_OBJC_BRIDGING_HEADER` 构建设置。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * In the main window under **TARGETS**, select **ShareExtension**. + 在主窗口的 **TARGETS** 下,选择 **ShareExtension**。 + * Open the **Build Settings** tab. + 打开 **Build Settings** 标签页。 + * Select the **All** filter. + 选择 **All** 筛选器。 + * Navigate to **Swift Compiler - General** and change the value for the **Objective-C Bridging Header** key to **Runner/Runner-Bridging-Header.h**. + 找到 **Swift Compiler - General**,将 **Objective-C Bridging Header** 的值改为 **Runner/Runner-Bridging-Header.h**。 + 1. In Xcode, update the code for `ShareViewController` to register `GeneratedPluginRegistrant.h`. + 在 Xcode 中,更新 `ShareViewController` 代码以注册 `GeneratedPluginRegistrant.h`。 + * Open the **project navigator** (**View** > **Navigators** > **Project**). + 打开 **项目导航器**(**View** > **Navigators** > **Project**)。 + * Select **Runner** > **ShareExtension** > **ShareViewController**. + 选择 **Runner** > **ShareExtension** > **ShareViewController**。 + * Update the `ShareViewController` file to use the `GeneratedPluginRegistrant.h`: + 更新 `ShareViewController` 文件以使用 `GeneratedPluginRegistrant.h`: + @@ -572,50 +851,83 @@ GeneratedPluginRegistrant.register(with: flutterEngine) 5. (Xcode) [Test your app with the simulator][]. +5. (Xcode)[使用模拟器测试应用][]。 + [Add an extension to your Flutter app]: #add-extension [Share]: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html [Test your app with the simulator]: #test-extensions +[向 Flutter 应用添加扩展]: #add-extension +[使用模拟器测试应用]: #test-extensions + +## Constraints {: #constraints} -## Constraints {: #constraints } +## 限制 {: #constraints} * You must use an iOS simulator to test your extension in debug mode. + 在 debug 模式下测试扩展时,必须使用 iOS 模拟器。 + * Flutter doesn't fully support running app extensions in debug mode on physical devices when used to build extension UIs because a physical device might run out of memory. + 在真机上以 debug 模式构建扩展 UI 时,Flutter 对 App Extension 的支持并不完整,因为真机可能内存不足。 + * iOS app extensions have limited memory. It is advisable to only modify an app extension's UI if the app extension supports at least 100MB of memory. + iOS App Extension 内存有限。建议仅在扩展至少支持 100MB 内存时才修改其 UI。 + ## Call Dart code / render Flutter content in iOS app extensions +## 在 iOS App Extension 中调用 Dart 代码 / 渲染 Flutter 内容 + The [home_widget][] package provides a large amount of functionality, which includes allowing the following: +[home_widget][] 插件提供大量功能,包括: + * [Respond to user input][] in app extensions using Dart Code. + 在 App Extension 中用 Dart 代码 [响应用户输入][]。 + * [Render Flutter widgets][] in an app extension as an image. + 在 App Extension 中将 [Flutter widget 渲染][] 为图像。 + * [Save and retrieve data][] from `UserDefaults` on iOS. -## Other resources {: #other-resources } + 在 iOS 上从 `UserDefaults` [保存和读取数据][]。 + +## Other resources {: #other-resources} + +## 其他资源 {: #other-resources} For step-by-step instruction for using app extensions with your Flutter iOS app, check out the [Adding a Home Screen Widget to your Flutter app][lab] codelab. +要在 Flutter iOS 应用中使用 App Extension,请参阅 Codelab [向 Flutter 应用添加主屏幕 Widget][lab]。 + To learn more about the various ways you can add a Flutter Screen to an iOS app, see [Adding a Flutter Screen to an iOS app][]. +要了解将 Flutter 界面添加到 iOS 应用的各种方式,请参阅 [向 iOS 应用添加 Flutter 界面][]。 + [Adding a Flutter Screen to an iOS app]: /add-to-app/ios/add-flutter-screen +[向 iOS 应用添加 Flutter 界面]: /add-to-app/ios/add-flutter-screen [lab]: {{site.codelabs}}/flutter-home-screen-widgets +[向 Flutter 应用添加主屏幕 Widget]: {{site.codelabs}}/flutter-home-screen-widgets [home_widget]: https://pub.dev/packages/home_widget [Save and retrieve data]: https://docs.page/abausg/home_widget/usage/sync-data +[保存和读取数据]: https://docs.page/abausg/home_widget/usage/sync-data [Render Flutter widgets]: https://docs.page/abausg/home_widget/features/render-flutter-widgets +[Flutter widget 渲染]: https://docs.page/abausg/home_widget/features/render-flutter-widgets [Respond to user input]: https://docs.page/abausg/home_widget/features/interactive-widgets +[响应用户输入]: https://docs.page/abausg/home_widget/features/interactive-widgets diff --git a/sites/docs/src/content/platform-integration/ios/apple-frameworks.md b/sites/docs/src/content/platform-integration/ios/apple-frameworks.md index f78f665f09..55882bab58 100644 --- a/sites/docs/src/content/platform-integration/ios/apple-frameworks.md +++ b/sites/docs/src/content/platform-integration/ios/apple-frameworks.md @@ -1,8 +1,12 @@ --- -title: Leveraging Apple's system APIs and frameworks +# title: Leveraging Apple's system APIs and frameworks +title: 利用 Apple 系统 API 与框架 +# description: >- +# Learn about Flutter plugins that offer equivalent +# functionalities to Apple's frameworks. description: >- - Learn about Flutter plugins that offer equivalent - functionalities to Apple's frameworks. + 了解提供与 Apple 框架等价功能的 Flutter 插件。 +ai-translated: true --- When you come from iOS development, you might need to find @@ -10,21 +14,31 @@ Flutter plugins that offer the same abilities as Apple's system libraries. This might include accessing device hardware or interacting with specific frameworks like `HealthKit`. +若你来自 iOS 开发背景,可能需要找到与 Apple 系统库能力相当的 Flutter 插件,例如访问设备硬件或与 `HealthKit` 等特定框架交互。 + For an overview of how the SwiftUI framework compares to Flutter, see [Flutter for SwiftUI developers][]. +要了解 SwiftUI 框架与 Flutter 的对比,请参阅 [面向 SwiftUI 开发者的 Flutter 指南][]。 + ## Introducing Flutter plugins +## Flutter 插件简介 + Dart calls libraries that contain platform-specific code _plugins_, short for "plugin package". When developing an app with Flutter, you use _plugins_ to interact with system libraries. +Dart 将包含平台相关代码的库称为 _plugins_(plugin package 的缩写)。使用 Flutter 开发应用时,通过 _plugins_ 与系统库交互。 + In your Dart code, you use the plugin's Dart API to call the native code from the system library being used. This means that you can write the code to call the Dart API. The API then makes it work for all platforms that the plugin supports. +在 Dart 代码中,你使用插件的 Dart API 调用所用系统库中的原生代码。也就是说,你只需编写调用 Dart API 的代码,API 会在插件支持的所有平台上生效。 + To learn more about plugins, see [Using packages][]. Though this page links to some popular plugins, you can find thousands more, along with examples, @@ -35,59 +49,102 @@ you can create your own or use platform channels directly in your project. To learn more, check out [Writing platform-specific code][]. +有关插件的更多信息,请参阅 [使用 package][]。本页虽链接了一些常用插件,你仍可在 [pub.dev][] 上找到数千个插件及示例。下表不代表对任何特定插件的背书。若找不到满足需求的 package,可自行创建或在项目中直接使用 platform channel。详情请参阅 [编写平台相关代码][]。 + ## Adding a plugin to your project +## 向项目添加插件 + To use an Apple framework within your native project, import it into your Swift or Objective-C file. +要在原生项目中使用 Apple 框架,请将其导入 Swift 或 Objective-C 文件。 + To add a Flutter plugin, run `flutter pub add package_name` from the root of your project. This adds the dependency to your [`pubspec.yaml`][] file. After you add the dependency, add an `import` statement for the package in your Dart file. +要添加 Flutter 插件,在项目根目录运行 `flutter pub add package_name`。这会将依赖写入 [`pubspec.yaml`][]。添加依赖后,在 Dart 文件中 `import` 该 package。 + You might need to change app settings or initialization logic. If that's needed, the package's "Readme" page on [pub.dev][] should provide details. +你可能需要修改应用设置或初始化逻辑;如有需要,[pub.dev][] 上该 package 的「Readme」页面会提供说明。 + ### Flutter Plugins and Apple Frameworks +### Flutter 插件与 Apple 框架 + | Use Case | Apple Framework or Class | Flutter Plugin | |------------------------------------------------|---------------------------------------------------------------------------------------|------------------------------| +| 使用场景 | Apple 框架或类 | Flutter 插件 | | Access the photo library | `PhotoKit` using the `Photos` and `PhotosUI ` frameworks and `UIImagePickerController`| [`image_picker`][] | +| 访问照片库 | 使用 `Photos`、`PhotosUI` 框架及 `UIImagePickerController` 的 `PhotoKit` | [`image_picker`][] | | Access the camera | `UIImagePickerController` using the `.camera` `sourceType` | [`image_picker`][] | +| 访问相机 | 使用 `.camera` `sourceType` 的 `UIImagePickerController` | [`image_picker`][] | | Use advanced camera features | `AVFoundation` | [`camera`][] | +| 使用高级相机功能 | `AVFoundation` | [`camera`][] | | Offer In-app purchases | `StoreKit` | [`in_app_purchase`][][^1] | +| 提供应用内购买 | `StoreKit` | [`in_app_purchase`][][^1] | | Process payments | `PassKit` | [`pay`][][^2] | +| 处理支付 | `PassKit` | [`pay`][][^2] | | Send push notifications | `UserNotifications` | [`firebase_messaging`][][^3] | +| 发送推送通知 | `UserNotifications` | [`firebase_messaging`][][^3] | | Access GPS coordinates | `CoreLocation` | [`geolocator`][] | -| Access sensor data[^4] | `CoreMotion` | [`sensors_plus`][] | +| 获取 GPS 坐标 | `CoreLocation` | [`geolocator`][] | +| Access sensor data | `CoreMotion` | [`sensors_plus`][] | +| 访问传感器数据[^4] | `CoreMotion` | [`sensors_plus`][] | | Make network requests | `URLSession` | [`http`][] | +| 发起网络请求 | `URLSession` | [`http`][] | | Store key-values | `@AppStorage` property wrapper and `NSUserDefaults` | [`shared_preferences`][] | +| 存储键值对 | `@AppStorage` 属性包装器与 `NSUserDefaults` | [`shared_preferences`][] | | Persist to a database | `CoreData` or SQLite | [`sqflite`][] | +| 持久化到数据库 | `CoreData` 或 SQLite | [`sqflite`][] | | Access health data | `HealthKit` | [`health`][] | +| 访问健康数据 | `HealthKit` | [`health`][] | | Use machine learning | `CoreML` | [`google_ml_kit`][][^5] | +| 使用机器学习 | `CoreML` | [`google_ml_kit`][][^5] | | Recognize text | `VisionKit` | [`google_ml_kit`][][^5] | +| 识别文字 | `VisionKit` | [`google_ml_kit`][][^5] | | Recognize speech | `Speech` | [`speech_to_text`][] | +| 语音识别 | `Speech` | [`speech_to_text`][] | | Use augmented reality | `ARKit` | [`ar_flutter_plugin`][] | +| 使用增强现实 | `ARKit` | [`ar_flutter_plugin`][] | | Access weather data | `WeatherKit` | [`weather`][][^6] | +| 访问天气数据 | `WeatherKit` | [`weather`][][^6] | | Access and manage contacts | `Contacts` | [`contacts_service`][] | +| 访问和管理联系人 | `Contacts` | [`contacts_service`][] | | Expose quick actions on the home screen | `UIApplicationShortcutItem` | [`quick_actions`][] | +| 在主屏幕暴露快捷操作 | `UIApplicationShortcutItem` | [`quick_actions`][] | | Index items in Spotlight search | `CoreSpotlight` | [`flutter_core_spotlight`][] | +| 在 Spotlight 搜索中建立索引 | `CoreSpotlight` | [`flutter_core_spotlight`][] | | Configure, update and communicate with Widgets | `WidgetKit` | [`home_widget`][] | +| 配置、更新 widget 并与之通信 | `WidgetKit` | [`home_widget`][] | | Automate app actions with Siri/Shortcuts | `AppIntents` | [`intelligence`][] | +| 通过 Siri/快捷指令自动化应用操作 | `AppIntents` | [`intelligence`][] | {:.table .table-striped .nowrap} [^1]: Supports both Google Play Store on Android and Apple App Store on iOS. + 同时支持 Android 的 Google Play 商店与 iOS 的 Apple App Store。 [^2]: Adds Google Pay payments on Android and Apple Pay payments on iOS. + 在 Android 上添加 Google Pay,在 iOS 上添加 Apple Pay。 [^3]: Uses Firebase Cloud Messaging and integrates with APNs. + 使用 Firebase Cloud Messaging 并与 APNs 集成。 [^4]: Includes sensors like accelerometer, gyroscope, etc. + 包括加速度计、陀螺仪等传感器。 [^5]: Uses Google's ML Kit and supports various features like text recognition, face detection, image labeling, landmark recognition, and barcode scanning. You can also create a custom model with Firebase. To learn more, see [Use a custom TensorFlow Lite model with Flutter][]. + 使用 Google ML Kit,支持文字识别、人脸检测、图像标注、地标识别、条码扫描等。也可通过 Firebase 创建自定义模型。详情请参阅 [在 Flutter 中使用自定义 TensorFlow Lite 模型][]。 [^6]: Uses the [OpenWeatherMap API][]. Other packages exist that can pull from different weather APIs. + 使用 [OpenWeatherMap API][]。另有 package 可从其他天气 API 拉取数据。 [Flutter for SwiftUI developers]: /flutter-for/swiftui-devs +[面向 SwiftUI 开发者的 Flutter 指南]: /flutter-for/swiftui-devs [Using packages]: /packages-and-plugins/using-packages +[使用 package]: /packages-and-plugins/using-packages [pub.dev]: {{site.pub-pkg}} [`shared_preferences`]: {{site.pub-pkg}}/shared_preferences [`http`]: {{site.pub-pkg}}/http @@ -101,6 +158,7 @@ should provide details. [`firebase_messaging`]: {{site.pub-pkg}}/firebase_messaging [`google_ml_kit`]: {{site.pub-pkg}}/google_ml_kit [Use a custom TensorFlow Lite model with Flutter]: {{site.firebase}}/docs/ml/flutter/use-custom-models +[在 Flutter 中使用自定义 TensorFlow Lite 模型]: {{site.firebase}}/docs/ml/flutter/use-custom-models [`speech_to_text`]: {{site.pub-pkg}}/speech_to_text [`ar_flutter_plugin`]: {{site.pub-pkg}}/ar_flutter_plugin [`weather`]: {{site.pub-pkg}}/weather @@ -109,6 +167,7 @@ should provide details. [OpenWeatherMap API]: https://openweathermap.org/api [`sqflite`]: {{site.pub-pkg}}/sqflite [Writing platform-specific code]: /platform-integration/platform-channels +[编写平台相关代码]: /platform-integration/platform-channels [`camera`]: {{site.pub-pkg}}/camera [`flutter_core_spotlight`]: {{site.pub-pkg}}/flutter_core_spotlight [`home_widget`]: {{site.pub-pkg}}/home_widget diff --git a/sites/docs/src/content/platform-integration/ios/index.md b/sites/docs/src/content/platform-integration/ios/index.md index 1a35414ab4..9f62bdfa16 100644 --- a/sites/docs/src/content/platform-integration/ios/index.md +++ b/sites/docs/src/content/platform-integration/ios/index.md @@ -1,5 +1,9 @@ --- -title: iOS +# layout: toc layout: toc -description: Content covering integration with iOS in Flutter apps. +# title: iOS +title: iOS +# description: Content covering integration with iOS in Flutter apps. +description: Flutter 应用与 iOS 集成的相关内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/platform-integration/ios/ios-latest.md b/sites/docs/src/content/platform-integration/ios/ios-latest.md index 94a9f9cd00..2342285270 100644 --- a/sites/docs/src/content/platform-integration/ios/ios-latest.md +++ b/sites/docs/src/content/platform-integration/ios/ios-latest.md @@ -1,28 +1,68 @@ --- -title: Flutter on latest iOS +# title: Flutter on latest iOS +title: Flutter 在最新 iOS 上 +# description: >- +# Learn about Flutter's support for and compatibility with +# the latest releases of iOS. description: >- - Learn about Flutter's support for and compatibility with - the latest releases of iOS. + 了解 Flutter 对最新 iOS 版本的支持与兼容性。 +ai-translated: true --- Flutter supports iOS 26. +Flutter 支持 iOS 26。 + For information on the status of specific iOS features that Flutter hasn't yet fully implemented, visit the following issues in the flutter/flutter repo. +有关 Flutter 尚未完全实现的特定 iOS 功能的状态,请访问 flutter/flutter 仓库中的以下 issue: + * Flutter support for liquid glass: [Issue 170310][] + + Flutter 对 liquid glass 的支持:[Issue 170310][] + * Eye tracking of a Flutter app: [Issue 153573][] + + Flutter 应用的眼动追踪:[Issue 153573][] + * Hover typing feature: [Issue 152715][] + + 悬停输入功能:[Issue 152715][] + * iOS formatting menu: [Issue 150068][] + + iOS 格式化菜单:[Issue 150068][] + * iOS-style zoom page transition: [Issue 150588][] + + iOS 风格缩放页面转场:[Issue 150588][] + * iPad-style tab bar: [Issue 150590][] + + iPad 风格标签栏:[Issue 150590][] + * iPhone mirroring when viewing a Flutter app: [Issue 152711][] + + 查看 Flutter 应用时的 iPhone 镜像:[Issue 152711][] + * Large content viewer: [Issue 152715][] + + 大内容查看器:[Issue 152715][] + * Add "Translate" button to the context edit menu: [Issue 150392][] + + 在上下文编辑菜单中添加「翻译」按钮:[Issue 150392][] + * Virtual trackpad feature: [Issue 152715][] + + 虚拟触控板功能:[Issue 152715][] + * Writing tools text input feature: [Issue 150965][], [Issue 150452][] + 书写工具文本输入功能:[Issue 150965][]、[Issue 150452][] + [Issue 150068]: {{site.repo.flutter}}/issues/150068 [Issue 150392]: {{site.repo.flutter}}/issues/150392 [Issue 150452]: {{site.repo.flutter}}/issues/150452 diff --git a/sites/docs/src/content/platform-integration/ios/launch-screen.md b/sites/docs/src/content/platform-integration/ios/launch-screen.md index 0145458bbe..ca5bef3d1e 100644 --- a/sites/docs/src/content/platform-integration/ios/launch-screen.md +++ b/sites/docs/src/content/platform-integration/ios/launch-screen.md @@ -1,8 +1,13 @@ --- -title: Adding a launch screen to your iOS app -shortTitle: Launch screen -description: Learn how to add a launch screen to your iOS app. +# title: Adding a launch screen to your iOS app +title: 向 iOS 应用添加启动屏幕 +# shortTitle: Launch screen +shortTitle: 启动屏幕 +# description: Learn how to add a launch screen to your iOS app. +description: 了解如何向 iOS 应用添加启动屏幕。 +# showToc: false showToc: false +ai-translated: true --- {% comment %} @@ -14,14 +19,22 @@ https://github.com/flutter/website/issues/8357 They set the stage for your application, while allowing time for the app engine to load and your app to initialize. +[启动屏幕][Launch screens] 在 iOS 应用加载时提供简洁的初始体验,为应用做好铺垫,同时为应用引擎加载与应用初始化争取时间。 + [Launch screens]: {{site.apple-dev}}/design/human-interface-guidelines/launching#Launch-screens All apps submitted to the Apple App Store [must provide a launch screen][apple-requirement] with an Xcode storyboard. +提交到 Apple App Store 的所有应用 +[必须提供][apple-requirement] +基于 Xcode storyboard 的启动屏幕。 + ## Customize the launch screen +## 自定义启动屏幕 + The default Flutter template includes an Xcode storyboard named `LaunchScreen.storyboard` that can be customized with your own assets. @@ -34,8 +47,13 @@ Then select `Runner/Assets.xcassets` from the Project Navigator and drop in the desired images to the `LaunchImage` image set. +默认 Flutter 模板包含名为 `LaunchScreen.storyboard` 的 Xcode storyboard,可用你自己的资源进行自定义。默认 storyboard 显示空白图像,你可以修改。在应用目录根目录执行 `open ios/Runner.xcworkspace` 打开 Flutter 应用的 Xcode 项目,然后在项目导航器中选择 `Runner/Assets.xcassets`,将所需图像拖入 `LaunchImage` 图像集。 + Apple provides detailed guidance for launch screens as part of the [Human Interface Guidelines][]. +Apple 在 [人机界面指南][] 中提供了启动屏幕的详细指导。 + [apple-requirement]: {{site.apple-dev}}/documentation/xcode/specifying-your-apps-launch-screen [Human Interface Guidelines]: {{site.apple-dev}}/design/human-interface-guidelines/patterns/launching#launch-screens +[人机界面指南]: {{site.apple-dev}}/design/human-interface-guidelines/patterns/launching#launch-screens diff --git a/sites/docs/src/content/platform-integration/ios/restore-state-ios.md b/sites/docs/src/content/platform-integration/ios/restore-state-ios.md index 7dec3b5718..b466700277 100644 --- a/sites/docs/src/content/platform-integration/ios/restore-state-ios.md +++ b/sites/docs/src/content/platform-integration/ios/restore-state-ios.md @@ -1,6 +1,9 @@ --- -title: "Restore state on iOS" -description: "How to restore the state of your iOS app after it's been killed by the OS." +# title: "Restore state on iOS" +title: "在 iOS 上恢复状态" +# description: "How to restore the state of your iOS app after it's been killed by the OS." +description: "如何在操作系统终止 iOS 应用后恢复其状态。" +ai-translated: true --- When a user runs a mobile app and then selects another @@ -9,14 +12,22 @@ or _backgrounded_. The operating system (both iOS and Android) often kills the backgrounded app to release memory or improve performance for the app running in the foreground. +用户运行移动应用后再切换到其他应用时,第一个应用会进入后台(_backgrounded_)。操作系统(iOS 与 Android)常会终止后台应用以释放内存或提升前台应用的性能。 + You can use the [`RestorationManager`][] (and related) classes to handle state restoration. An iOS app requires [a bit of extra setup][] in Xcode, but the restoration classes otherwise work the same on both iOS and Android. +你可以使用 [`RestorationManager`][](及相关类)处理状态恢复。iOS 应用需要在 Xcode 中 [做一些额外设置][],除此之外恢复类在 iOS 与 Android 上的用法相同。 + For more information, check out [State restoration on Android][]. +更多信息请参阅 [Android 上的状态恢复][]。 + [a bit of extra setup]: {{site.api}}/flutter/services/RestorationManager-class.html#state-restoration-on-ios +[做一些额外设置]: {{site.api}}/flutter/services/RestorationManager-class.html#state-restoration-on-ios [`RestorationManager`]: {{site.api}}/flutter/services/RestorationManager-class.html [State restoration on Android]: /platform-integration/android/restore-state-android +[Android 上的状态恢复]: /platform-integration/android/restore-state-android diff --git a/sites/docs/src/content/platform-integration/linux/building.md b/sites/docs/src/content/platform-integration/linux/building.md index 79d0e8196a..24032a1107 100644 --- a/sites/docs/src/content/platform-integration/linux/building.md +++ b/sites/docs/src/content/platform-integration/linux/building.md @@ -1,14 +1,21 @@ --- -title: Build Linux apps with Flutter -description: Platform-specific considerations when building for Linux with Flutter. -shortTitle: Linux development +# title: Build Linux apps with Flutter +title: 使用 Flutter 构建 Linux 应用 +# description: Platform-specific considerations when building for Linux with Flutter. +description: 使用 Flutter 为 Linux 构建应用时的平台相关注意事项。 +# shortTitle: Linux development +shortTitle: Linux 开发 +ai-translated: true --- This page discusses considerations unique to building Linux apps with Flutter, including shell integration and preparation of apps for distribution. +本页讨论使用 Flutter 构建 Linux 应用时的特有注意事项,包括 shell 集成以及为分发做准备。 + ## Integrate with Linux +## 与 Linux 集成 The Linux programming interface, comprising library functions and system calls, @@ -16,16 +23,30 @@ is designed around the C language and ABI. Fortunately, Dart provides the `dart:ffi` package, which enables Dart programs to call into C libraries. +Linux 编程接口由库函数和系统调用组成,围绕 C 语言与 ABI 设计。幸运的是,Dart 提供了 `dart:ffi` 包,使 Dart 程序能够调用 C 库。 + Foreign Function Interfaces (FFI) allow Flutter apps to perform the following with native libraries: +外部函数接口(FFI)允许 Flutter 应用通过原生库执行以下操作: + * allocate native memory with `malloc` or `calloc` + + 使用 `malloc` 或 `calloc` 分配原生内存 + * support pointers, structs, and callbacks + + 支持指针、结构体与回调 + * support Application Binary Interface (ABI) types like `long` and `size_t` + 支持 `long`、`size_t` 等应用程序二进制接口(ABI)类型 + To learn more about calling C libraries from Flutter, consult [C interop using `dart:ffi`][]. +要了解如何从 Flutter 调用 C 库,请参阅[使用 `dart:ffi` 的 C 互操作][]。 + Many apps benefit from using a package that wraps the underlying library calls in a more convenient, idiomatic Dart API. [Canonical has built a series of packages][Canonical] @@ -33,11 +54,17 @@ with a focus on enabling Dart and Flutter on Linux, including support for desktop notifications, dbus, network management, and Bluetooth. +许多应用受益于使用将底层库调用封装为更便捷、符合 Dart 习惯的 API 的包。[Canonical 构建了一系列包][Canonical],专注于在 Linux 上启用 Dart 与 Flutter,包括对桌面通知、dbus、网络管理和 Bluetooth 的支持。 + In general, many other [packages support creating Linux apps][support-linux], including common packages such as [`url_launcher`], [`shared_preferences`], [`file_selector`], and [`path_provider`]. +一般而言,还有许多其他[支持创建 Linux 应用的包][support-linux],包括 [`url_launcher`]、[`shared_preferences`]、[`file_selector`] 和 [`path_provider`] 等常用包。 + [C interop using `dart:ffi`]: {{site.dart-site}}/guides/libraries/c-interop +[使用 `dart:ffi` 的 C 互操作]: {{site.dart-site}}/guides/libraries/c-interop +[构建并将 Linux 应用发布到 Snap Store]: /deployment/linux [Canonical]: {{site.pub}}/publishers/canonical.com/packages [support-linux]: {{site.pub}}/packages?q=platform%3Alinux [`url_launcher`]: {{site.pub-pkg}}/url_launcher @@ -46,24 +73,36 @@ including common packages such as [`url_launcher`], [`path_provider`]: {{site.pub-pkg}}/path_provider ## Prepare Linux apps for distribution +## 为 Linux 应用分发做准备 The executable binary can be found in your project under `build/linux/x64//bundle/`. Alongside your executable binary in the `bundle` directory, you can find two directories: +可执行二进制文件位于项目的 `build/linux/x64//bundle/` 下。在 `bundle` 目录中,可执行文件旁还有两个目录: + * `lib` contains the required `.so` library files + + `lib` 包含所需的 `.so` 库文件 + * `data` contains the application's data assets, such as fonts or images + `data` 包含应用的数据资源,例如字体或图片 + In addition to these files, your application also relies on various operating system libraries against which it's been compiled. To see the full list of libraries, use the `ldd` command on your application's directory. +除这些文件外,应用还依赖编译时链接的各类操作系统库。要查看完整库列表,请对应用目录运行 `ldd` 命令。 + For example, to create a new Flutter desktop application called `linux_desktop_test`, build it, and inspect its system library dependencies, run the following commands: +例如,要创建名为 `linux_desktop_test` 的新 Flutter 桌面应用、构建它并检查其系统库依赖,请运行以下命令: + ```console $ flutter create linux_desktop_test $ cd linux_desktop_test @@ -75,8 +114,12 @@ To wrap up this application for distribution, include everything in the `bundle` directory and verify the target Linux system has all required system libraries. +要打包该应用以便分发,请包含 `bundle` 目录中的全部内容,并确认目标 Linux 系统已安装所有必需的系统库。 + This might only require using the following command. +可能只需使用以下命令: + ```console $ sudo apt-get install libgtk-3-0 libblkid1 liblzma5 ``` @@ -84,12 +127,17 @@ $ sudo apt-get install libgtk-3-0 libblkid1 liblzma5 To learn how to publish a Linux application to the [Snap Store], consult [Build and release a Linux application to the Snap Store][]. +要了解如何将 Linux 应用发布到 [Snap Store],请参阅[构建并将 Linux 应用发布到 Snap Store][Build and release a Linux application to the Snap Store]。 + ## Additional resources +## 其他资源 To learn how to create Linux Debian (`.deb`) and RPM (`.rpm`) builds of your Flutter desktop app, consult the step-by-step [Linux packaging guide][linux_packaging_guide]. +要了解如何为 Flutter 桌面应用创建 Linux Debian(`.deb`)与 RPM(`.rpm`)构建,请参阅分步 [Linux 打包指南][linux_packaging_guide]。 + [Snap Store]: https://snapcraft.io/store [Build and release a Linux application to the Snap Store]: /deployment/linux [linux_packaging_guide]: https://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-3-linux-24ef8d30a5b4 diff --git a/sites/docs/src/content/platform-integration/linux/index.md b/sites/docs/src/content/platform-integration/linux/index.md index bc994a4c79..fcc9868b88 100644 --- a/sites/docs/src/content/platform-integration/linux/index.md +++ b/sites/docs/src/content/platform-integration/linux/index.md @@ -1,5 +1,9 @@ --- +# layout: toc layout: toc +# title: Linux title: Linux -description: Content covering integration with Linux in Flutter apps. +# description: Content covering integration with Linux in Flutter apps. +description: 涵盖在 Flutter 应用中与 Linux 集成的内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/platform-integration/linux/setup.md b/sites/docs/src/content/platform-integration/linux/setup.md index ac462e89e9..8c4dea0d90 100644 --- a/sites/docs/src/content/platform-integration/linux/setup.md +++ b/sites/docs/src/content/platform-integration/linux/setup.md @@ -1,32 +1,47 @@ --- -title: Set up Linux development +# title: Set up Linux development +title: 配置 Linux 开发环境 +# description: >- +# Configure your development environment to +# run, build, and deploy Flutter apps for Linux desktop. description: >- - Configure your development environment to - run, build, and deploy Flutter apps for Linux desktop. + 配置开发环境,以便在 Linux 桌面上运行、构建和部署 Flutter 应用。 +ai-translated: true --- Learn how to set up your development environment to run, build, and deploy Flutter apps for the Linux desktop platform. +了解如何配置开发环境,以便在 Linux 桌面平台上运行、构建和部署 Flutter 应用。 + :::note If you haven't set up Flutter already, visit and follow [Install Flutter][] first. +如果你尚未配置 Flutter,请先访问并遵循[安装 Flutter][Install Flutter]。 + If you've already installed Flutter, ensure that it's [up to date][]. + +如果你已安装 Flutter,请确保其[为最新版本][up to date]。 ::: [Install Flutter]: /install [up to date]: /install/upgrade ## Set up tooling {: #set-up-tooling} +## 配置工具链 {: #set-up-tooling} To run and debug desktop Flutter apps on Linux, download and install the prerequisite packages. +要在 Linux 上运行和调试桌面 Flutter 应用,请下载并安装必备软件包。 + Using your preferred package manager or mechanism, install the latest versions of the following packages: +使用你偏好的包管理器或方式,安装以下软件包的最新版本: + - `clang` - `cmake` - `ninja-build` @@ -37,18 +52,25 @@ install the latest versions of the following packages: On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: +在基于 Debian、使用 `apt-get` 的发行版(如 Ubuntu)上,可使用以下命令安装这些包: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev ``` ## Validate your setup {: #validate-setup} +## 验证你的配置 {: #validate-setup} 1.

Check for toolchain issues

+

检查工具链问题

+ To check for any issues with your Linux development setup, run the `flutter doctor` command in your preferred terminal: + 要检查 Linux 开发配置是否存在问题,请在常用终端中运行 `flutter doctor` 命令: + ```console $ flutter doctor -v ``` @@ -58,11 +80,17 @@ $ sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstd complete and resolve them, then run `flutter doctor -v` again to verify any changes. + 若在 **Linux toolchain** 部分看到错误或待完成任务,请完成并解决它们,然后再次运行 `flutter doctor -v` 以验证更改。 + 1.

Check for Linux devices

+

检查 Linux 设备

+ To ensure Flutter can find and connect to your Linux device correctly, run `flutter devices` in your preferred terminal: + 为确保 Flutter 能正确找到并连接你的 Linux 设备,请在常用终端中运行 `flutter devices`: + ```console $ flutter devices ``` @@ -70,26 +98,38 @@ $ sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstd If you set everything up correctly, there should be at least one entry with the platform marked as **linux**. + 若一切配置正确,应至少有一条平台标记为 **linux** 的条目。 + 1.

Troubleshoot setup issues

+

排查配置问题

+ If you need help resolving any setup issues, check out [Install and setup troubleshooting][]. + 若需要解决配置问题,请参阅[安装与配置故障排除][Install and setup troubleshooting]。 + If you still have issues or questions, reach out on one of the Flutter [community][] channels. + 若仍有问题或疑问,可通过 Flutter [社区][community] 渠道联系。 + {: .steps} [Install and setup troubleshooting]: /install/troubleshoot [community]: {{site.main-url}}/community ## Start developing for Linux {: #start-developing} +## 开始为 Linux 开发 {: #start-developing} Congratulations! Now that you've set up Linux desktop development for Flutter, you can continue your Flutter learning journey while testing on Linux or begin expanding integration with Linux. +恭喜! +你已为 Flutter 配置好 Linux 桌面开发,可以在 Linux 上测试的同时继续学习 Flutter,或开始扩展与 Linux 的集成。 + + + diff --git a/sites/docs/src/content/platform-integration/macos/index.md b/sites/docs/src/content/platform-integration/macos/index.md index 0e89040c23..f908acbacd 100644 --- a/sites/docs/src/content/platform-integration/macos/index.md +++ b/sites/docs/src/content/platform-integration/macos/index.md @@ -1,5 +1,9 @@ --- +# layout: toc layout: toc +# title: macOS title: macOS -description: Content covering integration with macOS in Flutter apps. +# description: Content covering integration with macOS in Flutter apps. +description: Flutter 应用与 macOS 集成的相关内容。 +ai-translated: true --- diff --git a/sites/docs/src/content/platform-integration/macos/platform-views.md b/sites/docs/src/content/platform-integration/macos/platform-views.md index a2ddb4fea9..f48145d05a 100644 --- a/sites/docs/src/content/platform-integration/macos/platform-views.md +++ b/sites/docs/src/content/platform-integration/macos/platform-views.md @@ -1,7 +1,11 @@ --- -title: Hosting native macOS views in your Flutter app with Platform Views -shortTitle: macOS platform-views -description: Learn how to host native macOS views in your Flutter app with Platform Views. +# title: Hosting native macOS views in your Flutter app with Platform Views +title: 在 Flutter 应用中通过平台视图嵌入原生 macOS 视图 +# shortTitle: macOS platform-views +shortTitle: macOS 平台视图 +# description: Learn how to host native macOS views in your Flutter app with Platform Views. +description: 了解如何在 Flutter 应用中通过平台视图嵌入原生 macOS 视图。 +ai-translated: true --- @@ -9,9 +13,13 @@ description: Learn how to host native macOS views in your Flutter app with Platf Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart. +平台视图让你能在 Flutter 应用中嵌入原生视图,从而从 Dart 对原生视图应用变换、裁剪和透明度。 + This allows you, for example, to use the native web views directly inside your Flutter app. +例如,你可以直接在 Flutter 应用中使用原生 Web 视图。 + :::note This page discusses how to host your own native macOS views within a Flutter app. @@ -19,10 +27,14 @@ If you'd like to embed native Android views in your Flutter app, see [Hosting native Android views][]. If you'd like to embed native iOS views in your Flutter app, see [Hosting native iOS views][]. + +本页介绍如何在 Flutter 应用中托管你自己的原生 macOS 视图。若要在 Flutter 应用中嵌入原生 Android 视图,请参阅 [托管原生 Android 视图][]。若要在 Flutter 应用中嵌入原生 iOS 视图,请参阅 [托管原生 iOS 视图][]。 ::: [Hosting native Android views]: /platform-integration/android/platform-views +[托管原生 Android 视图]: /platform-integration/android/platform-views [Hosting native iOS views]: /platform-integration/ios/platform-views +[托管原生 iOS 视图]: /platform-integration/ios/platform-views :::version-note Platform view support on macOS isn't fully functional as of the current release. @@ -30,21 +42,37 @@ For example, gesture support isn't yet available on macOS. Stay tuned for a future stable release. ::: +:::version-note +截至当前版本,macOS 上的平台视图支持尚未完全可用。例如,macOS 上尚不支持手势。请关注后续稳定版发布。 +::: + macOS uses Hybrid composition, which means that the native `NSView` is appended to the view hierarchy. +macOS 使用混合合成(Hybrid composition),即原生 `NSView` 会追加到视图层级中。 + To create a platform view on macOS, use the following instructions: +要在 macOS 上创建平台视图,请按以下说明操作: + ## On the Dart side +## Dart 端 + On the Dart side, create a `Widget` and add the build implementation, as shown in the following steps: +在 Dart 端,创建一个 widget 并添加 `build` 实现,如下列步骤所示: + In the Dart widget file, make changes similar to those shown in `native_view_example.dart`: +在 Dart widget 文件中,进行与 `native_view_example.dart` 中类似的修改: + 1. Add the following imports: + 添加以下 import: + ```dart import 'package:flutter/foundation.dart'; @@ -53,6 +81,8 @@ shown in `native_view_example.dart`: 1. Implement a `build()` method: + 实现 `build()` 方法: + ```dart Widget build(BuildContext context) { @@ -72,15 +102,21 @@ shown in `native_view_example.dart`: For more information, check out the [`AppKitView`][] API docs. +更多信息请参阅 [`AppKitView`][] API 文档。 + [`AppKitView`]: {{site.api}}/flutter/widgets/AppKitView-class.html ## On the platform side +## 平台端 + Implement the factory and the platform view. The `NativeViewFactory` creates the platform view, and the platform view provides a reference to the `NSView`. For example, `NativeView.swift`: +实现工厂与平台视图。`NativeViewFactory` 创建平台视图,平台视图提供对 `NSView` 的引用。例如 `NativeView.swift`: + ```swift title="NativeView.swift" import Cocoa import FlutterMacOS @@ -146,8 +182,12 @@ class NativeView: NSView { Finally, register the platform view. This can be done in an app or a plugin. +最后注册平台视图,可在应用或插件中完成。 + For app registration, modify the App's `MainFlutterWindow.swift`: +应用注册:修改应用的 `MainFlutterWindow.swift`: + ```swift title="MainFlutterWindow.swift" import Cocoa import FlutterMacOS @@ -167,6 +207,8 @@ class MainFlutterWindow: NSWindow { For plugin registration, modify the plugin's main file: +插件注册:修改插件主文件: + ```swift title="Plugin.swift" import Cocoa import FlutterMacOS @@ -181,6 +223,8 @@ public class Plugin: NSObject, FlutterPlugin { For more information, check out the API docs for: +更多信息请参阅以下 API 文档: + * [`FlutterPlatformViewFactory`][] * [`FlutterPlatformView`][] * [`PlatformView`][] @@ -191,10 +235,14 @@ For more information, check out the API docs for: ## Putting it together +## 整合 + When implementing the `build()` method in Dart, you can use [`defaultTargetPlatform`][] to detect the platform, and decide which widget to use: +在 Dart 中实现 `build()` 时,可使用 [`defaultTargetPlatform`][] 检测平台并决定使用哪个 widget: + ```dart Widget build(BuildContext context) { @@ -220,15 +268,23 @@ Widget build(BuildContext context) { ## Performance +## 性能 + Platform views in Flutter come with performance trade-offs. +Flutter 中的平台视图存在性能权衡。 + For example, in a typical Flutter app, the Flutter UI is composed on a dedicated raster thread. This allows Flutter apps to be fast, as this thread is rarely blocked. +例如,在典型的 Flutter 应用中,Flutter UI 在专用光栅线程上合成,该线程很少阻塞,因此应用能保持较快响应。 + When a platform view is rendered with hybrid composition, the Flutter UI continues to be composed from the dedicated raster thread, but the platform view performs graphics operations on the platform thread. To rasterize the combined contents, Flutter performs synchronization between its raster thread and the platform thread. As such, any slow or blocking operations on the platform thread can negatively impact Flutter graphics performance. + +使用混合合成渲染平台视图时,Flutter UI 仍由专用光栅线程合成,但平台视图在平台线程上执行图形操作。为光栅化合并内容,Flutter 会在光栅线程与平台线程之间同步;因此平台线程上的缓慢或阻塞操作会影响 Flutter 图形性能。 diff --git a/sites/docs/src/content/platform-integration/macos/setup.md b/sites/docs/src/content/platform-integration/macos/setup.md index a97ab4c906..2289f7b049 100644 --- a/sites/docs/src/content/platform-integration/macos/setup.md +++ b/sites/docs/src/content/platform-integration/macos/setup.md @@ -1,44 +1,70 @@ --- -title: Set up macOS development +# title: Set up macOS development +title: 配置 macOS 开发环境 +# description: >- +# Configure your development environment to +# run, build, and deploy Flutter apps for macOS devices. description: >- - Configure your development environment to - run, build, and deploy Flutter apps for macOS devices. + 配置开发环境,以便在 macOS 设备上运行、构建和部署 Flutter 应用。 +ai-translated: true --- Learn how to set up your development environment to run, build, and deploy Flutter apps for the macOS desktop platform. +了解如何配置开发环境,以便在 macOS 桌面平台上运行、构建和部署 Flutter 应用。 + :::note If you haven't set up Flutter already, visit and follow [Install Flutter][] first. If you've already installed Flutter, ensure that it's [up to date][]. + +若尚未配置 Flutter,请先访问并遵循 [安装 Flutter][]。 + +若已安装 Flutter,请确保其 [为最新版本][]。 ::: [Install Flutter]: /install +[安装 Flutter]: /install [up to date]: /install/upgrade +[为最新版本]: /install/upgrade ## Set up tooling {: #set-up-tooling} +## 配置工具链 {: #set-up-tooling} + With Xcode, you can run Flutter apps on macOS as well as compile and debug native Swift and Objective-C code. +借助 Xcode,你可以在 macOS 上运行 Flutter 应用,并编译、调试原生 Swift 与 Objective-C 代码。 + 1.

Install Xcode

+

安装 Xcode

+ If you haven't done so already, [install and set up the latest version of Xcode][xcode]. + 若尚未安装,请 [安装并配置最新版 Xcode][xcode]。 + If you've already installed Xcode, update it to the latest version using the same installation method you used originally. + 若已安装 Xcode,请用当初的安装方式将其更新到最新版本。 + 1.

Set up Xcode command-line tools

+

配置 Xcode 命令行工具

+ To configure the Xcode command-line tools to use the version of Xcode you installed, run the following command in your preferred terminal: + 将 Xcode 命令行工具配置为使用已安装的 Xcode 版本,在你常用的终端中运行: + ```console $ sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch' ``` @@ -46,53 +72,86 @@ compile and debug native Swift and Objective-C code. If you downloaded Xcode elsewhere or need to use a different version, replace `/Applications/Xcode.app` with the path to there instead. + 若 Xcode 安装在其他位置或需使用其他版本,请将 `/Applications/Xcode.app` 替换为对应路径。 + 1.

Agree to the Xcode licenses

+

同意 Xcode 许可协议

+ After you've set up Xcode and configured its command-line tools, agree to the Xcode licenses. + 配置好 Xcode 及其命令行工具后,请同意 Xcode 许可协议。 + 1. Open your preferred terminal. + 打开你常用的终端。 + 1. Run the following command to review and sign the Xcode licenses. + 运行以下命令以查看并签署 Xcode 许可协议。 + ```console $ sudo xcodebuild -license ``` 1. Read and agree to all necessary licenses. + 阅读并同意所有必要的许可协议。 + Before agreeing to the terms of each license, read each with care. + 同意每项许可前,请仔细阅读条款。 + Once you've accepted all the necessary licenses successfully, the command should output how to review the licenses. + 成功接受所有必要许可后,命令会输出如何查看许可的说明。 + 1.

Install CocoaPods

+

安装 CocoaPods

+ To support [Flutter plugins][] that use native macOS code, install the latest version of [CocoaPods][]. + 为支持使用原生 macOS 代码的 [Flutter 插件][],请安装最新版 [CocoaPods][]。 + Install CocoaPods following the [CocoaPods installation guide][]. + 按 [CocoaPods 安装指南][] 安装 CocoaPods。 + If you've already installed CocoaPods, update it following the [CocoaPods update guide][]. + 若已安装 CocoaPods,请按 [CocoaPods 更新指南][] 更新。 + {: .steps} [xcode]: https://developer.apple.com/xcode/ [Flutter plugins]: /packages-and-plugins/developing-packages#types +[Flutter 插件]: /packages-and-plugins/developing-packages#types [CocoaPods]: https://cocoapods.org/ [CocoaPods installation guide]: https://guides.cocoapods.org/using/getting-started.html#installation +[CocoaPods 安装指南]: https://guides.cocoapods.org/using/getting-started.html#installation [CocoaPods update guide]: https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods +[CocoaPods 更新指南]: https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods ## Validate your setup {: #validate-setup} +## 验证配置 {: #validate-setup} + 1.

Check for toolchain issues

+

检查工具链问题

+ To check for any issues with your macOS development setup, run the `flutter doctor` command in your preferred terminal: + 检查 macOS 开发环境是否有问题,在你常用的终端中运行 `flutter doctor`: + ```console $ flutter doctor -v ``` @@ -102,11 +161,17 @@ compile and debug native Swift and Objective-C code. complete and resolve them, then run `flutter doctor -v` again to verify any changes. + 若 **Xcode** 部分有错误或待办项,请完成并解决后再次运行 `flutter doctor -v` 验证。 + 1.

Check for macOS devices

+

检查 macOS 设备

+ To ensure Flutter can find and connect to your macOS device correctly, run `flutter devices` in your preferred terminal: + 确保 Flutter 能正确发现 macOS 设备,在你常用的终端中运行 `flutter devices`: + ```console $ flutter devices ``` @@ -114,26 +179,40 @@ compile and debug native Swift and Objective-C code. If you set everything up correctly, there should be at least one entry with the platform marked as **macos**. + 若配置正确,应至少有一条平台的 **macos** 条目。 + 1.

Troubleshoot setup issues

+

排查配置问题

+ If you need help resolving any setup issues, check out [Install and setup troubleshooting][]. + 若需帮助解决配置问题,请参阅 [安装与配置故障排除][]。 + If you still have issues or questions, reach out on one of the Flutter [community][] channels. + 若仍有问题或疑问,可通过 Flutter [社区][] 渠道联系。 + {: .steps} [Install and setup troubleshooting]: /install/troubleshoot +[安装与配置故障排除]: /install/troubleshoot [community]: {{site.main-url}}/community +[社区]: {{site.main-url}}/community ## Start developing for macOS {: #start-developing} +## 开始为 macOS 开发 {: #start-developing} + Congratulations! Now that you've set up macOS desktop development for Flutter, you can continue your Flutter learning journey while testing on macOS or begin expanding integration with macOS. +恭喜!你已完成 Flutter 的 macOS 桌面开发配置,可在 macOS 上继续学习 Flutter,或开始扩展与 macOS 的集成。 +
Product flavorsBuild typesResulting build variantsProduct flavors产品 flavorBuild types构建类型Resulting build variants生成的构建变体
+ + + + @@ -172,6 +333,17 @@ investigate other resources that our community recommended. [rive][]
[spritewidget][] + + + + + @@ -181,6 +353,14 @@ investigate other resources that our community recommended. [app_review][] + + + + + @@ -191,6 +371,15 @@ investigate other resources that our community recommended. [audioplayers][]
[flutter_soloud][]
+ + + + + @@ -200,6 +389,14 @@ investigate other resources that our community recommended. [User Authentication using Firebase][firebase-auth] + + + + + @@ -209,6 +406,14 @@ investigate other resources that our community recommended. [Add Firebase to your Flutter game][] + + + + + @@ -219,6 +424,15 @@ investigate other resources that our community recommended. [Firebase Crashlytics overview][firebase-crashlytics]
[firebase_crashlytics][] + + + + + @@ -228,6 +442,14 @@ investigate other resources that our community recommended. [win32_gamepad][] + + + + + @@ -239,6 +461,16 @@ investigate other resources that our community recommended. [Game Developer Studio][]
[GIMP][] + + + + + @@ -250,6 +482,16 @@ investigate other resources that our community recommended. [Bonfire][bonfire-pkg]
[forge2d][] + + + + + @@ -260,6 +502,15 @@ investigate other resources that our community recommended. [Add achievements and leaderboards to your game][leaderboard-recipe]
[Add multiplayer support to your game][multiplayer-recipe] + + + + + @@ -269,6 +520,14 @@ investigate other resources that our community recommended. [games_services][game-svc-pkg] + + + + + @@ -278,6 +537,14 @@ investigate other resources that our community recommended. [Tiled][] + + + + + @@ -290,6 +557,17 @@ investigate other resources that our community recommended. [Add in-app purchases to your Flutter app][iap-recipe]
[Gaming UX and Revenue Optimizations for Apps][] (PDF)
+ + + + + @@ -301,6 +579,16 @@ investigate other resources that our community recommended. [sqflite][]
[cbl_flutter][] (Couchbase Lite)
+ + + + + @@ -311,6 +599,15 @@ investigate other resources that our community recommended. [Paint API][]
[Special effects][] + + + + + @@ -320,6 +617,14 @@ investigate other resources that our community recommended. [Best practices for optimizing Flutter web loading speed][] + + + + +
Feature Resources
功能资源
Animation and sprites
动画与精灵 + + [Special effects][]
+ [Spriter Pro][]
+ [rive][]
+ [spritewidget][] +
应用评价 + + [app_review][] +
音频 + + [audioplayers][]
+ [flutter_soloud][]
+
认证 + + [User Authentication using Firebase][firebase-auth] +
云服务 + + [Add Firebase to your Flutter game][] +
调试 + + [Firebase Crashlytics overview][firebase-crashlytics]
+ [firebase_crashlytics][] +
驱动 + + [win32_gamepad][] +
游戏资源
与资源工具
+ + [CraftPix][]
+ [Game Developer Studio][]
+ [GIMP][] +
游戏引擎 + + [Flame][flame-pkg]
+ [Bonfire][bonfire-pkg]
+ [forge2d][] +
游戏功能 + + [Add achievements and leaderboards to your game][leaderboard-recipe]
+ [Add multiplayer support to your game][multiplayer-recipe] +
游戏服务集成 + + [games_services][game-svc-pkg] +
关卡编辑器 + + [Tiled][] +
变现 + + [Add advertising to your Flutter game][ads-recipe]
+ [Add AdMob ads to a Flutter app][]
+ [Add in-app purchases to your Flutter app][iap-recipe]
+ [Gaming UX and Revenue Optimizations for Apps][] (PDF)
+
持久化 + + [shared_preferences][]
+ [sqflite][]
+ [cbl_flutter][] (Couchbase Lite)
+
特效 + + [Paint API][]
+ [Special effects][] +
用户体验 + + [Best practices for optimizing Flutter web loading speed][] +
@@ -385,13 +690,22 @@ investigate other resources that our community recommended. ## Other resources +## 其他资源 + Check out the following videos: +请观看以下视频: + * [Building multiplatform games with Flutter][gdc-talk], a talk given at the [Game Developer Conference (GDC)][] 2024. + + [Building multiplatform games with Flutter][gdc-talk](使用 Flutter 构建多平台游戏),[Game Developer Conference (GDC)][] 2024 演讲。 + * [How to build a physics-based game with Flutter and Flame's Forge2D][forge2d-video], from Google I/O 2024. + [How to build a physics-based game with Flutter and Flame's Forge2D][forge2d-video](如何使用 Flutter 与 Flame 的 Forge2D 构建物理游戏),来自 Google I/O 2024。 + [Game Developer Conference (GDC)]: https://gdconf.com/ [forge2d-video]: {{site.youtube-site}}/watch?v=nsnQJrYHHNQ [gdc-talk]: {{site.youtube-site}}/watch?v=7mG_sW40tsw diff --git a/sites/docs/src/content/resources/in-app-purchases-overview.md b/sites/docs/src/content/resources/in-app-purchases-overview.md index c50a70d2e4..72b8261870 100644 --- a/sites/docs/src/content/resources/in-app-purchases-overview.md +++ b/sites/docs/src/content/resources/in-app-purchases-overview.md @@ -1,9 +1,13 @@ --- -title: In-app purchases overview +# title: In-app purchases overview +title: 应用内购买概览 +# description: > +# Learn about the resources available for adding +# in-app purchases to your Flutter app. description: > - Learn about the resources available for adding - in-app purchases to your Flutter app. + 了解可用于为 Flutter 应用添加应用内购买的资源。 showBreadcrumbs: false +ai-translated: true --- ![adding ads](/assets/images/docs/add-in-app-purchases.png) @@ -11,9 +15,15 @@ showBreadcrumbs: false Build in-app revenue with Google Play and App Store support for Flutter +借助 Google Play 和 App Store 支持,在 Flutter 中构建应用内收入 + The following resources can help get you started: +以下资源可帮助你入门: + * [Add in-app purchases to your Flutter app][] (codelab) + [为 Flutter 应用添加应用内购买][Add in-app purchases to your Flutter app](codelab) + [Add in-app purchases to your Flutter app]: {{site.codelabs}}/codelabs/flutter-in-app-purchases#0 diff --git a/sites/docs/src/content/resources/index.md b/sites/docs/src/content/resources/index.md index e008f5cf3e..e7911b013d 100644 --- a/sites/docs/src/content/resources/index.md +++ b/sites/docs/src/content/resources/index.md @@ -1,7 +1,11 @@ --- -title: Resources +# title: Resources +title: 资源 layout: toc showBreadcrumbs: false +# description: >- +# Discover helpful resources covering the Flutter framework. description: >- - Discover helpful resources covering the Flutter framework. + 发现涵盖 Flutter 框架的有用资源。 +ai-translated: true --- diff --git a/sites/docs/src/content/resources/news-toolkit.md b/sites/docs/src/content/resources/news-toolkit.md index 620d352345..b9ecf7dbf8 100644 --- a/sites/docs/src/content/resources/news-toolkit.md +++ b/sites/docs/src/content/resources/news-toolkit.md @@ -1,12 +1,20 @@ --- +# title: Flutter News Toolkit title: Flutter News Toolkit -description: Learn more about creating a newsfeed app in Flutter. +# description: Learn more about creating a newsfeed app in Flutter. +description: 了解如何在 Flutter 中创建新闻流应用。 showBreadcrumbs: false +ai-translated: true --- The Flutter News Toolkit is now maintained by Very Good Ventures (VGV). +Flutter News Toolkit 现由 Very Good Ventures(VGV)维护。 + For the latest information, see the [news_toolkit repository on GitHub][news-toolkit]. +如需最新信息,请参阅 GitHub 上的 +[news_toolkit 仓库][news-toolkit]。 + [news-toolkit]: https://github.com/VGVentures/news_toolkit diff --git a/sites/docs/src/content/resources/payments-overview.md b/sites/docs/src/content/resources/payments-overview.md index 2cb91e1dec..a1fa953000 100644 --- a/sites/docs/src/content/resources/payments-overview.md +++ b/sites/docs/src/content/resources/payments-overview.md @@ -1,9 +1,14 @@ --- -title: Payments overview +# title: Payments overview +title: 支付概览 +# description: > +# Learn about the resources available for Google pay +# and other services to your Flutter app. description: > - Learn about the resources available for Google pay - and other services to your Flutter app. + 了解可用于为 Flutter 应用集成 Google Pay + 及其他支付服务的资源。 showBreadcrumbs: false +ai-translated: true --- ![adding ads](/assets/images/docs/add-payments.png) @@ -11,9 +16,15 @@ showBreadcrumbs: false Seamlessly accept payments with multiple providers in your Flutter app +在 Flutter 应用中通过多个支付服务商无缝收款 + The following resource can help to get you started: +以下资源可帮助你入门: + * [Add ads to your mobile Flutter app or game][] (cookbook recipe) + [为移动 Flutter 应用或游戏添加广告][Add ads to your mobile Flutter app or game](cookbook 食谱) + [Add ads to your mobile Flutter app or game]: /cookbook/plugins/google-mobile-ads diff --git a/sites/docs/src/content/resources/support.md b/sites/docs/src/content/resources/support.md index ac1e6a66f8..690ac89a67 100644 --- a/sites/docs/src/content/resources/support.md +++ b/sites/docs/src/content/resources/support.md @@ -1,11 +1,16 @@ --- -title: Flutter support -description: Where can you get support when developing with Flutter. +# title: Flutter support +title: Flutter 支持 +# description: Where can you get support when developing with Flutter. +description: 使用 Flutter 开发时可以在哪里获得支持。 showBreadcrumbs: false +ai-translated: true --- Welcome! +欢迎! + There are many different ways to get support when developing with Flutter. As Flutter is an open source project, @@ -15,13 +20,22 @@ to connecting with your local community, to browsing and identifying a list of commercial agencies and consultants who offer support services. +使用 Flutter 开发时有多种获取支持的方式。Flutter 是开源项目,你可以从在软件中遇到 bug 时提交 issue,到联系本地社区,再到浏览并筛选提供支持服务的商业机构与顾问。 + ## I want to report an issue with Flutter +## 我想报告 Flutter 的问题 + Visit how to [create a useful bug report](/resources/bug-reports). Note that filing bugs is _not_ an efficient way to get tech support. +请参阅如何 [创建有用的 bug 报告](/resources/bug-reports)。 +请注意,提交 bug _不是_ 获取技术支持的高效方式。 + ## I want to ask questions about how to use Flutter +## 我想咨询如何使用 Flutter + Your best bet is to consult [Flutter Forum](https://forum.itsallwidgets.com/), [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter), @@ -30,14 +44,28 @@ While the community is active in these channels, there is no official SLA on responding to new posts or chats in these channels. +建议你查阅 +[Flutter Forum](https://forum.itsallwidgets.com/)、 +[Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) +或 [Discord](https://discord.com/invite/rflutterdev)。 +虽然这些渠道社区活跃,但对新帖子或聊天并无官方 SLA(服务级别协议)响应承诺。 + ## I want professional services support with Flutter +## 我需要 Flutter 专业服务支持 + Check out the [Flutter consultants]({{site.main-url}}/consultants) directory. +请查看 +[Flutter consultants]({{site.main-url}}/consultants)(顾问) +目录。 + ## I want support from a Flutter expert +## 我想获得 Flutter 专家的支持 + Google has a cadre of [Google Developer Experts][] (GDEs), non-Google employees with expertise in various areas of Google technologies, such as Flutter and Dart. @@ -46,11 +74,22 @@ lectures, host meetups, run workshops, or make videos. As part of the Dart and Flutter world-wide community, they are a great resource. +Google 有一批 [Google Developer Experts][](GDE), +他们是非 Google 员工,在 Flutter、Dart 等 Google 技术领域具备专长。 +GDE _不是_ 免费顾问,但常会举办讲座、聚会、工作坊或制作视频。 +作为 Dart 和 Flutter 全球社区的一部分,他们是很好的资源。 + [Google Developer Experts]: https://developers.google.com/community/experts/directory?specialization=dart%2Cflutter ## I want to get paid support from Google or the Flutter project +## 我想从 Google 或 Flutter 项目获得付费支持 + As an open source project, we're not set up to offer paid support. We recommend checking out the [consultancy program]({{site.main-url}}/consultants). + +作为开源项目,我们未设置付费支持。 +建议你查看 +[consultancy program]({{site.main-url}}/consultants)(顾问计划)。 diff --git a/sites/docs/src/content/testing/code-debugging.md b/sites/docs/src/content/testing/code-debugging.md index aebee1c46a..2e71ceb1a3 100644 --- a/sites/docs/src/content/testing/code-debugging.md +++ b/sites/docs/src/content/testing/code-debugging.md @@ -8,6 +8,7 @@ description: > 如何通过在代码中加入 log 输出。 tags: Flutter测试 keywords: Flutter调试,Flutter加log,Flutter输出 +ai-translated: true --- @@ -16,20 +17,30 @@ This guide describes which debugging features you can enable in your code. For a full list of debugging and profiling tools, check out the [Debugging][] page. +本指南说明可在代码中启用哪些调试功能。完整的调试与分析工具列表请参阅[调试][Debugging]页面。 + ## Add logging to your application +## 为应用添加日志 + The following list contains a few statements that you can use to log the behavior of your application. You can view your logs in DevTools' [Logging view][] or in your system console. +以下列出可用于记录应用行为的若干语句。可在 DevTools 的[日志视图][Logging view]或系统控制台中查看日志。 + * [`print()`][]: Prints a `stdout` (standard output) message. Part of the `dart:io` library. + [`print()`][]:打印一条 `stdout`(标准输出)消息。属于 `dart:io` library。 + * [`stderr.method_to_invoke()`][]: Prints a `stderr` (standard error) message. Replace `method_to_invoke()` with a method supported by the `stderr` property, such as `writeln()` or `write()`. Often used in a `try...catch` block. Part of the `dart:io` library. + [`stderr.method_to_invoke()`][]:打印一条 `stderr`(标准错误)消息。将 `method_to_invoke()` 替换为 `stderr` 属性支持的方法,例如 `writeln()` 或 `write()`。常用于 `try...catch` 块中。属于 `dart:io` library。 + ```dart stderr.writeln('print me'); @@ -38,12 +49,18 @@ behavior of your application. You can view your logs in DevTools' * [`log()`][]: Includes greater granularity and more information in the logging output. Part of the `dart:developer` library. + [`log()`][]:在日志输出中提供更细的粒度与更多信息。属于 `dart:developer` library。 + * [`debugPrint()`][]: If too much output results in discarded log lines, use this to keep those lines. Will print messages in release mode unless part of a debug mode check or an assert. Part of the `foundations` library. + [`debugPrint()`][]:若输出过多导致日志行被丢弃,可用它保留这些行。除非处于 debug 模式检查或 assert 中,否则在 release 模式下也会打印消息。属于 `foundations` library。 + ### Example 1 {:.no_toc} +### 示例 1 {:.no_toc} + ```dart import 'dart:developer' as developer; @@ -62,8 +79,12 @@ parameter on the `log()` call, JSON encode the object you want to send, and pass the encoded string to the error parameter. +你也可以向 log 调用传入应用数据。惯例是在 `log()` 调用上使用 `error:` 命名参数,对要发送的对象进行 JSON 编码,并将编码后的字符串传给 error 参数。 + ### Example 2 {:.no_toc} +### 示例 2 {:.no_toc} + ```dart import 'dart:convert'; @@ -84,22 +105,39 @@ DevTool's logging view interprets the JSON encoded error parameter as a data object. DevTool renders in the details view for that log entry. +DevTools 的日志视图将 JSON 编码的 error 参数解析为数据对象,并在该日志条目的详情视图中渲染。 + ## Set breakpoints +## 设置断点 + You can set breakpoints in DevTools' [Debugger][] or in the built-in debugger of your IDE. +你可以在 DevTools 的[调试器][Debugger]或 IDE 内置调试器中设置断点。 + To set programmatic breakpoints: +要设置编程式断点: + 1. Import the `dart:developer` package into the relevant file. + + 在相关文件中 import `dart:developer` package。 + 1. Insert programmatic breakpoints using the `debugger()` statement. This statement takes an optional `when` argument. This boolean argument sets a break when the given condition resolves to true. + 使用 `debugger()` 语句插入编程式断点。该语句接受可选的 `when` 参数;当给定条件为 true 时,该布尔参数会触发断点。 + **Example 3** illustrates this. + **示例 3** 演示了这一点。 + ### Example 3 {:.no_toc} +### 示例 3 {:.no_toc} + ```dart import 'dart:developer'; @@ -112,40 +150,68 @@ void someFunction(double offset) { ## Debug app layers using flags +## 使用标志调试应用各层 + Each layer of the Flutter framework provides a function to dump its current state or events to the console using the `debugPrint` property. +Flutter 框架的每一层都提供函数,可通过 `debugPrint` 属性将当前状态或事件 dump 到控制台。 + :::note All of the following examples were run as macOS native apps on a MacBook Pro M1. These will differ from any dumps your development machine prints. + +以下示例均在 MacBook Pro M1 上以 macOS 原生应用运行。你的开发机打印的 dump 内容可能不同。 ::: :::tip Each render object in any tree includes the first five hexadecimal digits of its [`hashCode`][]. This hash serves as a unique identifier for that render object. + +任意树中的每个 render object 都包含其 [`hashCode`][] 的前五位十六进制数字。该哈希值作为该 render object 的唯一标识符。 ::: [`hashCode`]: {{site.api}}/flutter/rendering/TextSelectionPoint/hashCode.html ### Print the widget tree +### 打印 widget 树 + To dump the state of the Widgets library, call the [`debugDumpApp()`][] function. +要 dump Widgets library 的状态,请调用 [`debugDumpApp()`][] 函数。 + 1. Open your source file. + + 打开源文件。 + 1. Import `package:flutter/rendering.dart`. + + import `package:flutter/rendering.dart`。 + 1. Call the [`debugDumpApp()`][] function from within the `runApp()` function. You need your app in debug mode. You cannot call this function inside a `build()` method when the app is building. + + 在 `runApp()` 函数内调用 [`debugDumpApp()`][]。应用须处于 debug 模式。应用正在 build 时,不能在 `build()` 方法内调用此函数。 + 1. If you haven't started your app, debug it using your IDE. + + 若尚未启动应用,请用 IDE 进行调试。 + 1. If you have started your app, save your source file. Hot reload re-renders your app. + 若已启动应用,保存源文件。热重载会重新渲染应用。 + #### Example 4: Call `debugDumpApp()` +#### 示例 4:调用 `debugDumpApp()` + ```dart import 'package:flutter/material.dart'; @@ -176,18 +242,29 @@ class AppHome extends StatelessWidget { This function recursively calls the `toStringDeep()` method starting with the root of the widget tree. It returns a "flattened" tree. +该函数从 widget 树根节点起递归调用 `toStringDeep()` 方法,返回「扁平化」的树。 + **Example 4** produces the following widget tree. It includes: +**示例 4** 会生成如下 widget 树,其中包括: + * All the widgets projected through their various build functions. + + 通过各自 build 函数投射出来的所有 widget。 + * Many widgets that don't appear in your app's source. The framework's widgets' build functions insert them during the build. + 许多未出现在应用源码中的 widget。框架 widget 的 build 函数在 build 过程中插入它们。 + The following tree, for example, shows [`_InkFeatures`][]. That class implements part of the [`Material`][] widget. It doesn't appear anywhere in the code in **Example 4**. + 例如下面的树中出现了 [`_InkFeatures`][]。该类实现 [`Material`][] widget 的一部分,在 **示例 4** 的代码中并未出现。 +
-Expand to view the widget tree for Example 4 +Expand to view the widget tree for Example 4 / 展开查看示例 4 的 widget 树 {% render "docs/testing/trees/widget-tree.md" -%} @@ -200,6 +277,8 @@ and thus marking itself dirty. This explains why a Flutter marks a specific object as "dirty". When you review the widget tree, look for a line that resembles the following: +当按钮从按下变为释放时,会调用 `debugDumpApp()`。此时 [`TextButton`][] 对象也会调用 [`setState()`][] 并将自身标记为 dirty。这解释了 Flutter 为何将特定对象标为「dirty」。查看 widget 树时,请寻找类似下面的一行: + ```plaintext └TextButton(dirty, dependencies: [MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#5880d]], state: _ButtonStyleState#ab76e) ``` @@ -210,22 +289,41 @@ Add [DiagnosticsProperty][] objects to the method's argument and call the superclass method. The `toString` method uses this function to fill in the widget's description. +若编写自定义 widget,可重写 [`debugFillProperties()`][widget-fill] 方法以添加信息。向方法参数添加 [DiagnosticsProperty][] 对象并调用超类方法。`toString` 方法会用该函数填充 widget 的描述。 + ### Print the render tree +### 打印 render 树 + When debugging a layout issue, the Widgets layer's tree might lack detail. The next level of debugging might require a render tree. To dump the render tree: +调试布局问题时,Widgets 层的树可能不够详细,下一层调试可能需要 render 树。要 dump render 树: + 1. Open your source file. + + 打开源文件。 + 1. Call the [`debugDumpRenderTree()`][] function. You can call this any time except during a layout or paint phase. Consider calling it from a [frame callback][] or an event handler. + + 调用 [`debugDumpRenderTree()`][]。除 layout 或 paint 阶段外均可调用;可考虑在[帧回调][frame callback]或事件处理器中调用。 + 1. If you haven't started your app, debug it using your IDE. + + 若尚未启动应用,请用 IDE 调试。 + 1. If you have started your app, save your source file. Hot reload re-renders your app. + 若已启动应用,保存源文件。热重载会重新渲染应用。 + #### Example 5: Call `debugDumpRenderTree()` +#### 示例 5:调用 `debugDumpRenderTree()` + ```dart import 'package:flutter/material.dart'; @@ -256,8 +354,10 @@ class AppHome extends StatelessWidget { When debugging layout issues, look at the `size` and `constraints` fields. The constraints flow down the tree and the sizes flow back up. +调试布局时,请关注 `size` 和 `constraints` 字段。constraints 沿树向下传递,size 向上回传。 +
-Expand to view the render tree for Example 5 +Expand to view the render tree for Example 5 / 展开查看示例 5 的 render 树 {% render "docs/testing/trees/render-tree.md" -%} @@ -265,26 +365,38 @@ The constraints flow down the tree and the sizes flow back up. In the render tree for **Example 5**: +**示例 5** 的 render 树中: + * The `RenderView`, or window size, limits all render objects up to and including [`RenderPositionedBox`][]`#dc1df` render object to the size of the screen. This example sets the size to `Size(800.0, 600.0)` + `RenderView`(窗口尺寸)将直至 [`RenderPositionedBox`][]`#dc1df` render object 在内的所有 render object 限制为屏幕大小。本示例将尺寸设为 `Size(800.0, 600.0)`。 + * The `constraints` property of each render object limits the size of each child. This property takes the [`BoxConstraints`][] render object as a value. Starting with the `RenderSemanticsAnnotations#fe6b5`, the constraint equals `BoxConstraints(w=800.0, h=600.0)`. + 每个 render object 的 `constraints` 属性限制其子节点尺寸。该属性取值为 [`BoxConstraints`][] render object。从 `RenderSemanticsAnnotations#fe6b5` 起,constraint 为 `BoxConstraints(w=800.0, h=600.0)`。 + * The [`Center`][] widget created the `RenderPositionedBox#dc1df` render object under the `RenderSemanticsAnnotations#8187b` subtree. + [`Center`][] widget 在 `RenderSemanticsAnnotations#8187b` 子树下创建了 `RenderPositionedBox#dc1df` render object。 + * Each child under this render object has `BoxConstraints` with both minimum and maximum values. For example, `RenderSemanticsAnnotations#a0a4b` uses `BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)`. + 该 render object 下每个子节点都有带最小值与最大值的 `BoxConstraints`。例如 `RenderSemanticsAnnotations#a0a4b` 使用 `BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)`。 + * All children of the `RenderPhysicalShape#8e171` render object use `BoxConstraints(BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0))`. + `RenderPhysicalShape#8e171` render object 的所有子节点使用 `BoxConstraints(BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0))`。 + * The child `RenderPadding#8455f` sets a `padding` value of `EdgeInsets(8.0, 0.0, 8.0, 0.0)`. This sets a left and right padding of 8 to all subsequent children of @@ -292,12 +404,16 @@ In the render tree for **Example 5**: They now have new constraints: `BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0)`. + 子节点 `RenderPadding#8455f` 将 `padding` 设为 `EdgeInsets(8.0, 0.0, 8.0, 0.0)`,为该 render object 之后所有子节点设置左右各 8 的 padding,它们的新 constraints 为:`BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0)`。 + This object, which the `creator` field tells us is probably part of the [`TextButton`][]'s definition, sets a minimum width of 88 pixels on its contents and a specific height of 36.0. This is the `TextButton` class implementing the Material Design guidelines regarding button dimensions. +`creator` 字段表明该对象可能属于 [`TextButton`][] 定义的一部分,为其内容设置最小宽度 88 像素、高度 36.0。这是 `TextButton` 类实现 Material Design 按钮尺寸规范的方式。 + `RenderPositionedBox#80b8d` render object loosens the constraints again to center the text within the button. The [`RenderParagraph`][]#59bc2 render object picks its size based on @@ -307,26 +423,40 @@ you see how the size of the text influences the width of all the boxes that form the button. All parents take their child's dimensions to size themselves. +`RenderPositionedBox#80b8d` render object 再次放宽 constraints,使文字在按钮内居中。[`RenderParagraph`][]#59bc2 render object 根据内容确定尺寸。若沿树向上追踪尺寸,可看到文字尺寸如何影响组成按钮的各个 box 的宽度;所有父节点都根据子节点尺寸确定自身大小。 + Another way to notice this is by looking at the `relayoutBoundary` attribute of in the descriptions of each box. This tells you how many ancestors depend on this element's size. +也可查看每个 box 描述中的 `relayoutBoundary` 属性,它表示有多少祖先依赖该元素的尺寸。 + For example, the innermost `RenderPositionedBox` line has a `relayoutBoundary=up13`. This means that when Flutter marks the `RenderConstrainedBox` as dirty, it also marks box's 13 ancestors as dirty because the new dimensions might affect those ancestors. +例如最内层 `RenderPositionedBox` 行的 `relayoutBoundary=up13` 表示:当 Flutter 将 `RenderConstrainedBox` 标为 dirty 时,也会将该 box 的 13 个祖先标为 dirty,因为新尺寸可能影响这些祖先。 + To add information to the dump if you write your own render objects, override [`debugFillProperties()`][render-fill]. Add [DiagnosticsProperty][] objects to the method's argument then call the superclass method. +若编写自定义 render object,可重写 [`debugFillProperties()`][render-fill] 为 dump 添加信息:向方法参数添加 [DiagnosticsProperty][] 对象,再调用超类方法。 + ### Print the layer tree +### 打印 layer 树 + To debug a compositing issue, use [`debugDumpLayerTree()`][]. +调试合成问题时,请使用 [`debugDumpLayerTree()`][]。 + #### Example 6: Call `debugDumpLayerTree()` +#### 示例 6:调用 `debugDumpLayerTree()` + ```dart import 'package:flutter/material.dart'; @@ -355,7 +485,7 @@ class AppHome extends StatelessWidget { ```
-Expand to view the output of layer tree for Example 6 +Expand to view the output of layer tree for Example 6 / 展开查看示例 6 的 layer 树输出 {% render "docs/testing/trees/layer-tree.md" -%} @@ -363,9 +493,13 @@ class AppHome extends StatelessWidget { The `RepaintBoundary` widget creates: +`RepaintBoundary` widget 会创建: + 1. A `RenderRepaintBoundary` RenderObject in the render tree as shown in the **Example 5** results. + render 树中的 `RenderRepaintBoundary` RenderObject,如 **示例 5** 结果所示。 + ```plaintext ╎ └─child: RenderRepaintBoundary#f8f28 ╎ │ needs compositing @@ -385,6 +519,8 @@ The `RepaintBoundary` widget creates: 1. A new layer in the layer tree as shown in the **Example 6** results. + layer 树中的新 layer,如 **示例 6** 结果所示。 + ```plaintext ├─child 1: OffsetLayer#0f766 │ │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ← @@ -393,13 +529,17 @@ The `RepaintBoundary` widget creates: │ │ UnmanagedRestorationScope ← ⋯ │ │ engine layer: OffsetEngineLayer#1768d │ │ handles: 2 - │ │ offset: Offset(0.0, 0.0) + │ │ offset: Offset(0.0, 0.0) ``` This reduces how much needs to be repainted. +这可减少需要重绘的范围。 + ### Print the focus tree +### 打印 focus 树 + To debug a focus or shortcut issue, dump the focus tree using the [`debugDumpFocusTree()`][] function. @@ -416,8 +556,23 @@ property to simplify finding its focus node in the tree. You can also use the [`debugFocusChanges`][] boolean property to enable extensive logging when the focus changes. +调试 focus 或快捷键问题时,请用 [`debugDumpFocusTree()`][] 函数 dump focus 树。 + +`debugDumpFocusTree()` 方法返回应用的 focus 树。 + +focus 树按以下方式标记节点: + +* 获得焦点的节点标记为 `PRIMARY FOCUS`。 +* focus 节点的祖先标记为 `IN FOCUS PATH`。 + +若应用使用 [`Focus`][] widget,可用 [`debugLabel`][] 属性便于在树中定位其 focus 节点。 + +也可将 [`debugFocusChanges`][] 布尔属性设为 true,在 focus 变化时输出详细日志。 + #### Example 7: Call `debugDumpFocusTree()` +#### 示例 7:调用 `debugDumpFocusTree()` + ```dart import 'package:flutter/material.dart'; @@ -446,7 +601,7 @@ class AppHome extends StatelessWidget { ```
-Expand to view the focus tree for Example 7 +Expand to view the focus tree for Example 7 / 展开查看示例 7 的 focus 树 {% render "docs/testing/trees/focus-tree.md" -%} @@ -454,6 +609,8 @@ class AppHome extends StatelessWidget { ### Print the semantics tree +### 打印 semantics 树 + The `debugDumpSemanticsTree()` function prints the semantic tree for the app. The Semantics tree is presented to the system accessibility APIs. @@ -463,8 +620,17 @@ To obtain a dump of the Semantics tree: or the `SemanticsDebugger` 1. Use the [`debugDumpSemanticsTree()`][] function. +`debugDumpSemanticsTree()` 函数会打印应用的 semantic 树。 + +Semantics 树会提供给系统无障碍 API。要获取 Semantics 树的 dump: + +1. 使用系统无障碍工具或 `SemanticsDebugger` 启用无障碍。 +1. 使用 [`debugDumpSemanticsTree()`][] 函数。 + #### Example 8: Call `debugDumpSemanticsTree()` +#### 示例 8:调用 `debugDumpSemanticsTree()` + ```dart import 'package:flutter/foundation.dart'; @@ -503,7 +669,7 @@ class AppHome extends StatelessWidget { ```
-Expand to view the semantic tree for Example 8 +Expand to view the semantic tree for Example 8 / 展开查看示例 8 的 semantic 树 {% render "docs/testing/trees/semantic-tree.md" -%} @@ -511,6 +677,8 @@ class AppHome extends StatelessWidget { ### Print event timings +### 打印事件时序 + If you want to find out where your events happen relative to the frame's begin and end, you can set prints to log these events. To print the beginning and end of the frames to the console, @@ -519,6 +687,10 @@ and the [`debugPrintEndFrameBanner`][]. **The print frame banner log for Example 1** +若想知道事件相对帧起止的位置,可设置 print 记录这些事件。要将帧的开始与结束打印到控制台,请切换 [`debugPrintBeginFrameBanner`][] 与 [`debugPrintEndFrameBanner`][]。 + +**示例 1 的帧横幅日志** + ```plaintext I/flutter : ▄▄▄▄▄▄▄▄ Frame 12 30s 437.086ms ▄▄▄▄▄▄▄▄ I/flutter : Debug print: Am I performing this work more than once per frame? @@ -529,8 +701,12 @@ I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ To print the call stack causing the current frame to be scheduled, use the [`debugPrintScheduleFrameStacks`][] flag. +要打印导致当前帧被调度的调用栈,请使用 [`debugPrintScheduleFrameStacks`][] 标志。 + ## Debug layout issues +## 调试布局问题 + To debug a layout problem using a GUI, set [`debugPaintSizeEnabled`][] to `true`. This flag can be found in the `rendering` library. @@ -539,8 +715,14 @@ Consider adding it to the top of your `void main()` entry point. #### Example 9 +要用 GUI 调试布局问题,将 [`debugPaintSizeEnabled`][] 设为 `true`。该标志位于 `rendering` library,可随时启用,为 `true` 时影响所有绘制。建议放在 `void main()` 入口顶部。 + +#### 示例 9 + See an example in the following code: +参见下面代码中的示例: + ```dart // Add import to the Flutter rendering library. @@ -560,6 +742,13 @@ When enabled, Flutter displays the following changes to your app: * Displays all alignment positioning with yellow arrows. * Displays all spacers in gray, when they have no child. +启用后,Flutter 会在应用中显示以下变化: + +* 所有 box 显示亮青色边框。 +* 所有 padding 显示为带淡蓝填充与蓝色边框的 box,包围子 widget。 +* 所有对齐定位显示黄色箭头。 +* 无子节点的 spacer 显示为灰色。 + The [`debugPaintBaselinesEnabled`][] flag does something similar but for objects with baselines. The app displays the baseline for alphabetic characters in bright green @@ -568,12 +757,16 @@ Alphabetic characters "sit" on the alphabetic baseline, but that baseline "cuts" through the bottom of [CJK characters][cjk]. Flutter positions the ideographic baseline at the very bottom of the text line. +[`debugPaintBaselinesEnabled`][] 标志作用类似,但针对带 baseline 的对象。应用以亮绿色显示字母字符的 baseline,以橙色显示表意字符的 baseline。字母字符「坐」在字母 baseline 上,但该 baseline 会「切过」[CJK 字符][cjk]底部。Flutter 将表意 baseline 放在文本行最底部。 + The [`debugPaintPointersEnabled`][] flag turns on a special mode that highlights any objects that you tap in teal. This can help you determine if an object fails to hit test. This might happen if the object falls outside the bounds of its parent and thus not considered for hit testing in the first place. +[`debugPaintPointersEnabled`][] 标志开启特殊模式,将你点击的对象高亮为青色,便于判断对象是否未通过 hit test——例如对象超出父级边界,因而一开始就不参与 hit test。 + If you're trying to debug compositor layers, consider using the following flags. * Use the [`debugPaintLayerBordersEnabled`][] flag to find the boundaries @@ -582,13 +775,23 @@ If you're trying to debug compositor layers, consider using the following flags. * Use the [`debugRepaintRainbowEnabled`][] flag to display a repainted layer. Whenever a layer repaints, it overlays with a rotating set of colors. +若要调试 compositor layer,可考虑以下标志: + +* 使用 [`debugPaintLayerBordersEnabled`][] 标志查看各 layer 边界;启用后每个 layer 的边界会以橙色描边。 + +* 使用 [`debugRepaintRainbowEnabled`][] 标志显示重绘的 layer;layer 每次重绘时会叠加轮换的一组颜色。 + Any function or method in the Flutter framework that starts with `debug...` only works in [debug mode][]. +Flutter 框架中凡以 `debug...` 开头的函数或方法仅在 [debug 模式][debug mode]下有效。 + [cjk]: https://en.wikipedia.org/wiki/CJK_characters ## Debug animation issues +## 调试动画问题 + :::note To debug animations with the least effort, slow them down. To slow down the animation, @@ -596,6 +799,8 @@ click **Slow Animations** in DevTools' [Inspector view][]. This reduces the animation to 20% speed. If you want more control over the amount of slowness, use the following instructions. + +最省力的动画调试方式是放慢动画。在 DevTools 的[检查器视图][Inspector view]中点击 **Slow Animations**,动画会降至 20% 速度。若需更精细地控制放慢程度,请按下列说明操作。 ::: Set the [`timeDilation`][] variable (from the `scheduler` @@ -606,13 +811,19 @@ animations are running, it's possible that the framework will observe time going backwards, which will probably result in asserts and generally interfere with your efforts. +将(来自 `scheduler` library 的)[`timeDilation`][] 变量设为大于 1.0 的数,例如 50.0。最好在应用启动时只设置一次。若在运行时修改,尤其在动画播放时减小该值,框架可能观察到时间倒流,通常会触发 assert 并干扰调试。 + ## Debug performance issues +## 调试性能问题 + :::note You can achieve similar results to some of these debug flags using [DevTools][]. Some of the debug flags provide little benefit. If you find a flag with functionality you would like to add to [DevTools][], [file an issue][]. + +部分 debug 标志的效果可用 [DevTools][] 实现;有些 debug 标志收益不大。若发现某标志的功能应加入 [DevTools][],请[提交 issue][file an issue]。 ::: Flutter provides a wide variety of top-level properties and functions @@ -623,6 +834,10 @@ To use these features, compile your app in debug mode. The following list highlights some flags and one function from the [rendering library][] for debugging performance issues. +Flutter 在开发周期的各阶段提供大量顶层属性与函数,帮助你调试应用。使用这些功能时,请在 debug 模式下编译应用。 + +下列列表突出 [rendering library][] 中用于调试性能问题的一些标志与一个函数。 + [`debugDumpRenderTree()`][] : To dump the rendering tree to the console, call this function when not in a layout or repaint phase. @@ -633,10 +848,21 @@ The following list highlights some flags and one function from the * Import the module, set the value in your `main()` function, then hot restart. +[`debugDumpRenderTree()`][] +: 在非 layout 或 repaint 阶段调用此函数,将 rendering 树 dump 到控制台。 + + 设置这些标志可: + + * 编辑框架代码。 + * import 对应 module,在 `main()` 中设值,然后热重启。 + [`debugPaintLayerBordersEnabled`][] : To display the boundaries of each layer, set this property to `true`. When set, each layer paints a box around its boundary. +[`debugPaintLayerBordersEnabled`][] +: 将属性设为 `true` 可显示各 layer 边界;启用后每个 layer 会在边界周围绘制 box。 + [`debugRepaintRainbowEnabled`][] : To display a colored border around each widget, set this property to `true`. These borders change color as the app user scrolls in the app. @@ -645,6 +871,9 @@ The following list highlights some flags and one function from the If any static widgets rotate through colors after setting this flag, consider adding repaint boundaries to those areas. +[`debugRepaintRainbowEnabled`][] +: 将属性设为 `true` 可为每个 widget 显示彩色边框;用户滚动时边框会变色。在应用顶层添加 `debugRepaintRainbowEnabled = true;` 即可启用。若启用后某些静态 widget 仍轮换颜色,可考虑在这些区域添加 repaint boundary。 + [`debugPrintMarkNeedsLayoutStacks`][] : To determine if your app creates more layouts than expected, set this property to `true`. @@ -653,20 +882,32 @@ The following list highlights some flags and one function from the When set, the framework outputs stack traces to the console to explain why your app marks each render object to be laid out. +[`debugPrintMarkNeedsLayoutStacks`][] +: 将属性设为 `true` 可判断是否产生了超出预期的 layout。该问题可能出现在时间线、profile 或 layout 方法内的 `print` 中。启用后,框架会向控制台输出堆栈,说明为何将各 render object 标记为需要 layout。 + [`debugPrintMarkNeedsPaintStacks`][] : To determine if your app paints more layouts than expected, set this property to `true`. +[`debugPrintMarkNeedsPaintStacks`][] +: 将属性设为 `true` 可判断是否绘制了超出预期的 layout。 + You can generate stack traces on demand as well. To print your own stack traces, add the `debugPrintStack()` function to your app. +你也可以按需生成堆栈。在应用中加入 `debugPrintStack()` 函数即可打印自定义堆栈。 + ### Trace Dart code performance +### 追踪 Dart 代码性能 + :::note You can use the DevTools [Timeline events tab][] to perform traces. You can also import and export trace files into the Timeline view, but only files generated by DevTools. + +可使用 DevTools [Timeline 事件标签页][Timeline events tab] 进行追踪,也可将 trace 文件导入/导出到 Timeline 视图,但仅支持 DevTools 生成的文件。 ::: To perform custom performance traces and measure wall or CPU time of arbitrary @@ -675,6 +916,11 @@ segments of Dart code, use `dart:developer` [Timeline][] utilities. 1. Open your source code. 1. Wrap the code you want to measure in `Timeline` methods. +要对任意 Dart 代码段做自定义性能追踪并测量墙上时间或 CPU 时间,请使用 `dart:developer` 的 [Timeline][] 工具。 + +1. 打开源代码。 +1. 用 `Timeline` 方法包裹要测量的代码。 + ```dart import 'dart:developer'; @@ -690,16 +936,26 @@ segments of Dart code, use `dart:developer` [Timeline][] utilities. 1. Select the **Dart** recording option in the **Performance settings**. 1. Perform the function you want to measure. +1. 在已连接应用时,打开 DevTools 的 [Timeline 事件标签页][Timeline events tab]。 +1. 在 **Performance settings** 中选择 **Dart** 录制选项。 +1. 执行要测量的函数。 + To ensure that the runtime performance characteristics closely match that of your final product, run your app in [profile mode][]. +为确保运行时性能特征与最终产品接近,请在 [profile 模式][profile mode]下运行应用。 + ### Add performance overlay +### 添加性能叠加层 + :::note You can toggle display of the performance overlay on your app using the **Performance Overlay** button in the [Flutter inspector][]. If you prefer to do it in code, use the following instructions. + +可在 [Flutter 检查器][Flutter inspector]中用 **Performance Overlay** 按钮切换应用上的性能叠加层显示。若更倾向在代码中设置,请按下列说明操作。 ::: To enable the `PerformanceOverlay` widget in your code, @@ -707,8 +963,12 @@ set the `showPerformanceOverlay` property to `true` on the [`MaterialApp`][], [`CupertinoApp`][], or [`WidgetsApp`][] constructor: +要在代码中启用 `PerformanceOverlay` widget,请在 [`MaterialApp`][]、[`CupertinoApp`][] 或 [`WidgetsApp`][] 构造函数上将 `showPerformanceOverlay` 设为 `true`: + #### Example 10 +#### 示例 10 + ```dart import 'package:flutter/material.dart'; @@ -735,18 +995,28 @@ or `WidgetsApp`, you can get the same effect by wrapping your application in a stack and putting a widget on your stack that was created by calling [`PerformanceOverlay.allEnabled()`][].) +(若未使用 `MaterialApp`、`CupertinoApp` 或 `WidgetsApp`,可将应用包在 stack 中,并在 stack 上放置由 [`PerformanceOverlay.allEnabled()`][] 创建的 widget,效果相同。) + To learn how to interpret the graphs in the overlay, check out [The performance overlay][] in [Profiling Flutter performance][]. +要了解如何解读叠加层中的图表,请参阅[分析 Flutter 性能][Profiling Flutter performance]中的[性能叠加层][The performance overlay]。 + ## Add widget alignment grid +## 添加 widget 对齐网格 + To add an overlay to a [Material Design baseline grid][] on your app to help verify alignments, add the `debugShowMaterialGrid` argument in the [`MaterialApp` constructor][]. To add an overlay to non-Material applications, add a [`GridPaper`][] widget. +要在应用上叠加 [Material Design 基线网格][Material Design baseline grid]以校验对齐,请在 [`MaterialApp` 构造函数][`MaterialApp` constructor] 中添加 `debugShowMaterialGrid` 参数。 + +对非 Material 应用,添加 [`GridPaper`][] widget。 + [`_InkFeatures`]: {{site.api}}/flutter/material/InkFeature-class.html [`BoxConstraints`]: {{site.api}}/flutter/rendering/BoxConstraints-class.html [`Center`]: {{site.api}}/flutter/widgets/Center-class.html diff --git a/sites/docs/src/content/testing/common-errors.md b/sites/docs/src/content/testing/common-errors.md index e653dbda88..1914964ac2 100644 --- a/sites/docs/src/content/testing/common-errors.md +++ b/sites/docs/src/content/testing/common-errors.md @@ -5,12 +5,15 @@ title: Flutter 开发中常见的报错 description: 如何识别并解决一些常见的 Flutter 框架的报错。 tags: 测试 keywords: flutter报错,A RenderFlex overflowed,An InputDecorator cannot have an unbounded width +ai-translated: true --- ## Introduction +## 介绍 + This page explains several frequently-encountered Flutter framework errors (including layout errors) and gives suggestions on how to resolve them. @@ -19,43 +22,65 @@ future revisions, and your contributions are welcomed. Feel free to [open an issue][] or [submit a pull request][] to make this page more useful to you and the Flutter community. +本页说明若干常见的 Flutter 框架错误(包括布局错误),并给出解决建议。本文档会持续更新,未来会补充更多错误,欢迎贡献。欢迎[提交 issue][open an issue] 或[提交 pull request][submit a pull request],让本页对你和 Flutter 社区更有帮助。 + [open an issue]: {{site.repo.this}}/issues/new/choose [submit a pull request]: {{site.repo.this}}/pulls ## A solid red or grey screen when running your app +## 运行应用时出现纯红或纯灰屏幕 + Typically called a "red (or grey) screen of death", this is sometimes how Flutter lets you know that there's an error. +通常称为「红(或灰)屏死机」,有时 Flutter 以此告诉你发生了错误。 + The red screen can appear when the app runs in debug or profile mode. The grey screen can appear when the app runs in release mode. +应用在 debug 或 profile 模式下运行时可能出现红屏;在 release 模式下运行时可能出现灰屏。 + Generally, these errors occur when there's an uncaught exception (and you might need another try-catch block), or when there is some rendering error, such as an overflow error. +通常这些错误由未捕获的异常引起(你可能需要额外的 try-catch 块),或由渲染错误(例如溢出错误)引起。 + The following articles provide some useful insights on debugging this sort of error: +以下文章对调试此类错误很有帮助: + * [Flutter errors demystified][] by Abishek * [Understanding and addressing the grey screen in Flutter][] by Christopher Nwosu-Madueke * [Flutter stuck on white screen][] by Kesar Bhimani + Abishek 的 [Flutter errors demystified][] +* Christopher Nwosu-Madueke 的 [Understanding and addressing the grey screen in Flutter][] +* Kesar Bhimani 的 [Flutter stuck on white screen][] + [Flutter errors demystified]: {{site.medium}}/@hpatilabhi10/flutter-errors-demystified-red-screen-errors-vs-debug-console-errors-acb3b8ed2625 [Flutter stuck on white screen]: https://www.dhiwise.com/post/flutter-stuck-on-white-screen-understanding-and-fixing [Understanding and addressing the grey screen in Flutter]: {{site.medium}}/@LordChris/understanding-and-addressing-the-grey-screen-in-flutter-5e72c31f408f ## 'A RenderFlex overflowed…' +## 「A RenderFlex overflowed…」 + RenderFlex overflow is one of the most frequently encountered Flutter framework errors, and you've probably run into it already. +RenderFlex 溢出是最常见的 Flutter 框架错误之一,你可能已经遇到过。 + **What does the error look like?** +**错误是什么样子的?** + When it happens, yellow and black stripes appear, indicating the area of overflow in the app UI. In addition, an error message displays in the debug console: @@ -75,13 +100,19 @@ being too big for the RenderFlex. (Additional lines of this message omitted) ``` +发生时,应用 UI 中会出现黄黑条纹标示溢出区域,调试控制台也会显示错误消息。 + **How might you run into this error?** +**你可能会如何遇到此错误?** + The error often occurs when a `Column` or `Row` has a child widget that isn't constrained in its size. For example, the code snippet below demonstrates a common scenario: +当 `Column` 或 `Row` 含有未约束尺寸的子 widget 时,常会出现此错误。下面代码片段展示一种常见场景: + ```dart Widget build(BuildContext context) { @@ -128,10 +159,18 @@ it needs to display. The self-determined width of the clashes with the maximum amount of horizontal space its parent, the `Row` widget, can provide. +在上述示例中,`Column` 试图比其父级 `Row` 能分配的空间更宽,导致溢出。`Column` 为何会如此?要理解这一布局行为,需要了解 Flutter 如何执行布局: + +「_为执行布局,Flutter 以深度优先遍历渲染树,**自上而下传递尺寸约束**……子节点在父节点建立的约束内**向上回传尺寸**。_」——[Flutter 架构概览][Flutter architectural overview] + +本例中,`Row` 未约束子节点尺寸,`Column` 也未约束。子 widget 缺少父级约束时,第二个 `Text` widget 会尽量按全部字符宽度显示;`Text` 自行决定的宽度被 `Column` 采用,与其父级 `Row` 能提供的最大水平空间冲突。 + [Flutter architectural overview]: /resources/architectural-overview#layout-and-rendering **How to fix it?** +**如何修复?** + Well, you need to make sure the `Column` won't attempt to be wider than it can be. To achieve this, you need to constrain its width. One way to do it is to @@ -159,14 +198,26 @@ To further understand how to use the `Flex` widget in Flutter layouts, check out [this 90-second Widget of the Week video][flexible-video] on the `Flexible` widget. +你需要确保 `Column` 不会试图超出可容纳的宽度。一种做法是用 `Expanded` widget 包裹 `Column` 以约束其宽度。另一种是用 `Flexible` 并指定 `flex` 因子;事实上 `Expanded` 等价于 `flex` 为 1.0 的 `Flexible`,[其源码][its source code]可证。要进一步理解 Flutter 布局中 `Flex` widget 的用法,可观看关于 `Flexible` widget 的 [90 秒 Widget of the Week 视频][flexible-video]。 + **Further information:** +**更多信息:** + The resources linked below provide further information about this error. +以下链接提供更多关于此错误的信息。 + * [Flexible (Flutter Widget of the Week)][flexible-video] * [How to debug layout issues with the Flutter Inspector][medium-article] * [Understanding constraints][] +* [Flexible(Flutter Widget of the Week)][flexible-video] + + [如何使用 Flutter Inspector 调试布局问题][medium-article] + +* [理解约束][Understanding constraints] + [its source code]: {{site.repo.flutter}}/blob/c8e42b47f5ea8b5ff7bf2f2b0a2a8e765f1aa51d/packages/flutter/lib/src/widgets/basic.dart#L5166-L5174 [flexible-video]: {{site.yt.watch}}?v=CI7x0mAZiY0 [medium-article]: {{site.flutter-blog}}/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db#738b @@ -174,12 +225,18 @@ The resources linked below provide further information about this error. ## 'RenderBox was not laid out' +## 「RenderBox was not laid out」 + While this error is pretty common, it's often a side effect of a primary error occurring earlier in the rendering pipeline. +此错误相当常见,但往往是渲染管线中更早发生的主要错误的副作用。 + **What does the error look like?** +**错误是什么样子的?** + The message shown by the error looks like this: ```plaintext @@ -187,29 +244,46 @@ RenderBox was not laid out: RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE ``` +错误显示的消息类似上文。 + **How might you run into this error?** +**你可能会如何遇到此错误?** + Usually, the issue is related to violation of box constraints, and it needs to be solved by providing more information to Flutter about how you'd like to constrain the widgets in question. You can learn more about how constraints work in Flutter on the [Understanding constraints][] page. +通常与 box 约束违反有关,需要向 Flutter 提供更多关于如何约束相关 widget 的信息。可在[理解约束][Understanding constraints]页面了解更多。 + The `RenderBox was not laid out` error is often caused by one of two other errors: * 'Vertical viewport was given unbounded height' * 'An InputDecorator...cannot have an unbounded width' +`RenderBox was not laid out` 错误常由另外两种错误引起: + +* 「Vertical viewport was given unbounded height」 +* 「An InputDecorator...cannot have an unbounded width」 + ## 'Vertical viewport was given unbounded height' +## 「Vertical viewport was given unbounded height」 + This is another common layout error you could run into while creating a UI in your Flutter app. +在 Flutter 应用中构建 UI 时可能遇到的另一种常见布局错误。 + **What does the error look like?** +**错误是什么样子的?** + The message shown by the error looks like this: ```plaintext @@ -223,8 +297,12 @@ scrollable widget is nested inside another scrollable widget. (Additional lines of this message omitted) ``` +错误显示的消息类似上文。 + **How might you run into this error?** +**你可能会如何遇到此错误?** + The error is often caused when a `ListView` (or other kinds of scrollable widgets such as `GridView`) is placed inside a `Column`. A `ListView` takes all @@ -235,6 +313,8 @@ on its children's height by default. The combination of the two behaviors leads to the failure of determining the size of the `ListView`. +当 `ListView`(或其他可滚动 widget,如 `GridView`)放在 `Column` 内时,常会出现此错误。`ListView` 会占用所有可用的垂直空间,除非父 widget 约束它。然而 `Column` 默认不约束子节点高度。两种行为叠加会导致无法确定 `ListView` 的尺寸。 + ```dart Widget build(BuildContext context) { @@ -256,12 +336,16 @@ Widget build(BuildContext context) { **How to fix it?** +**如何修复?** + To fix this error, specify how tall the `ListView` should be. To make it as tall as the remaining space in the `Column`, wrap it using an `Expanded` widget (as shown in the following example). Otherwise, specify an absolute height using a `SizedBox` widget or a relative height using a `Flexible` widget. +修复此错误需指定 `ListView` 的高度。若要与 `Column` 中剩余空间同高,用 `Expanded` widget 包裹(如下例)。否则用 `SizedBox` 指定绝对高度,或用 `Flexible` 指定相对高度。 + ```dart Widget build(BuildContext context) { @@ -285,20 +369,33 @@ Widget build(BuildContext context) { **Further information:** +**更多信息:** + The resources linked below provide further information about this error. +以下链接提供更多关于此错误的信息。 + * [How to debug layout issues with the Flutter Inspector][medium-article] * [Understanding constraints][] + [如何使用 Flutter Inspector 调试布局问题][medium-article] +* [理解约束][Understanding constraints] + ## 'An InputDecorator...cannot have an unbounded width' +## 「An InputDecorator...cannot have an unbounded width」 + The error message suggests that it's also related to box constraints, which are important to understand to avoid many of the most common Flutter framework errors. +错误消息表明这也与 box 约束有关;理解约束有助于避免许多最常见的 Flutter 框架错误。 + **What does the error look like?** +**错误是什么样子的?** + The message shown by the error looks like this: ```plaintext @@ -312,12 +409,18 @@ width of the InputDecorator or the TextField that contains it. (Additional lines of this message omitted) ``` +错误显示的消息类似上文。 + **How might you run into the error?** +**你可能会如何遇到此错误?** + This error occurs, for example, when a `Row` contains a `TextFormField` or a `TextField` but the latter has no width constraint. +例如,当 `Row` 包含 `TextFormField` 或 `TextField` 而后者没有宽度约束时,会出现此错误。 + ```dart Widget build(BuildContext context) { @@ -332,11 +435,15 @@ Widget build(BuildContext context) { **How to fix it?** +**如何修复?** + As suggested by the error message, fix this error by constraining the text field using either an `Expanded` or `SizedBox` widget. The following example demonstrates using an `Expanded` widget: +按错误消息建议,使用 `Expanded` 或 `SizedBox` widget 约束文本字段即可修复。以下示例使用 `Expanded` widget: + ```dart Widget build(BuildContext context) { @@ -351,10 +458,16 @@ Widget build(BuildContext context) { ## 'Incorrect use of ParentData widget' +## 「Incorrect use of ParentData widget」 + This error is about missing an expected parent widget. +此错误与缺少预期的父 widget 有关。 + **What does the error look like?** +**错误是什么样子的?** + The message shown by the error looks like this: ```plaintext @@ -365,19 +478,27 @@ Usually, this indicates that at least one of the offending ParentDataWidgets listed above is not placed directly inside a compatible ancestor widget. ``` +错误显示的消息类似上文。 + **How might you run into the error?** +**你可能会如何遇到此错误?** + While Flutter's widgets are generally flexible in how they can be composed together in a UI, a small subset of those widgets expect specific parent widgets. When this expectation can't be satisfied in your widget tree, you're likely to encounter this error. +Flutter 的 widget 在 UI 中的组合通常很灵活,但少数 widget 需要特定的父 widget。当 widget 树无法满足这一要求时,就可能遇到此错误。 + Here is an _incomplete_ list of widgets that expect specific parent widgets within the Flutter framework. Feel free to submit a PR (using the doc icon in the top right corner of the page) to expand this list. +以下是 Flutter 框架中需要特定父 widget 的 widget 的*不完整*列表。欢迎提交 PR(使用页面右上角的文档图标)扩充此列表。 + | Widget | Expected parent widget(s) | |:--------------------------------------|---------------------------:| | `Flexible` | `Row`, `Column`, or `Flex` | @@ -387,17 +508,27 @@ the top right corner of the page) to expand this list. **How to fix it?** +**如何修复?** + The fix should be obvious once you know which parent widget is missing. +一旦知道缺少哪个父 widget,修复方法通常就很明显。 + ## 'setState called during build' +## 「setState called during build」 + The `build` method in your Flutter code isn't a good place to call `setState`, either directly or indirectly. +Flutter 代码中的 `build` 方法不适合调用 `setState`,无论是直接还是间接调用。 + **What does the error look like?** +**错误是什么样子的?** + When the error occurs, the following message is displayed in the console: @@ -412,8 +543,12 @@ is already in the process of building widgets. (Additional lines of this message omitted) ``` +发生错误时,控制台会显示上述消息。 + **How might you run into the error?** +**你可能会如何遇到此错误?** + In general, this error occurs when the `setState` method is called within the `build` method. @@ -423,8 +558,12 @@ attempting to trigger a `Dialog` from within the immediately show information to the user, but `setState` should never be called from a `build` method. +一般而言,在 `build` 方法内调用 `setState` 会导致此错误。常见场景是在 `build` 方法中尝试触发 `Dialog`,往往是为了立即向用户展示信息,但绝不应在 `build` 方法中调用 `setState`。 + The following snippet seems to be a common culprit of this error: +以下代码片段常是此错误的元凶: + ```dart Widget build(BuildContext context) { @@ -448,8 +587,12 @@ The `build` method isn't the right place to call `showDialog` because `build` can be called by the framework for every frame, for example, during an animation. +此代码未显式调用 `setState`,但 `showDialog` 会调用它。`build` 不是调用 `showDialog` 的合适位置,因为框架可能每帧都调用 `build`,例如在动画期间。 + **How to fix it?** +**如何修复?** + One way to avoid this error is to use the `Navigator` API to trigger the dialog as a route. In the following example, there are two pages. The second page has a @@ -459,6 +602,8 @@ clicking a button on the first page, the `Navigator` pushes two routes–one for the second page and another for the dialog. +避免此错误的一种方式是使用 `Navigator` API 将对话框作为路由触发。以下示例有两个页面;第二个页面在进入时显示对话框。用户在第一页点击按钮请求第二页时,`Navigator` 压入两条路由——一条对应第二页,另一条对应对话框。 + ```dart class FirstScreen extends StatelessWidget { @@ -493,6 +638,8 @@ class FirstScreen extends StatelessWidget { ## `The ScrollController is attached to multiple scroll views` +## 「The ScrollController is attached to multiple scroll views」 + This error can occur when multiple scrolling widgets (such as `ListView`) appear on the screen at the same time. It's more likely for @@ -500,22 +647,34 @@ this error to occur on a web or desktop app, than a mobile app since it's rare to encounter this scenario on mobile. +当多个可滚动 widget(如 `ListView`)同时出现在屏幕上时,可能出现此错误。在 Web 或桌面应用中比移动应用更常见,因为移动应用很少遇到此场景。 + For more information and to learn how to fix, check out the following video on [`PrimaryScrollController`][controller-video]: +更多信息及修复方法,请参阅以下关于 [`PrimaryScrollController`][controller-video] 的视频: + [controller-video]: {{site.api}}/flutter/widgets/PrimaryScrollController-class.html ## References +## 参考资料 + To learn more about how to debug errors, especially layout errors in Flutter, check out the following resources: +要进一步了解如何调试错误,尤其是 Flutter 中的布局错误,请参阅以下资源: + * [How to debug layout issues with the Flutter Inspector][medium-article] * [Understanding constraints][] * [Flutter architectural overview][] + [如何使用 Flutter Inspector 调试布局问题][medium-article] +* [理解约束][Understanding constraints] +* [Flutter 架构概览][Flutter architectural overview] + [Flutter architectural overview]: /resources/architectural-overview#layout-and-rendering diff --git a/sites/docs/src/content/testing/debugging.md b/sites/docs/src/content/testing/debugging.md index e0641d3e68..a18b72a0cd 100644 --- a/sites/docs/src/content/testing/debugging.md +++ b/sites/docs/src/content/testing/debugging.md @@ -3,6 +3,7 @@ title: 调试 Flutter 应用 # description: How to debug your Flutter app. description: 如何调试你的 Flutter 应用。 +ai-translated: true --- @@ -35,6 +36,11 @@ Flutter applications. Here are some of the available tools: individual widgets and their property values, enable the performance overlay, and more. + [Flutter inspector][],可在 DevTools 中使用,也可在 Android Studio + 和 IntelliJ(启用 Flutter 插件后)中直接使用。 + 检查器让你查看 widget 树的可视化表示、检查 + 各个 widget 及其属性值、启用性能叠加层等。 + ## Other resources ## 其他资源 diff --git a/sites/docs/src/content/testing/integration-tests/index.md b/sites/docs/src/content/testing/integration-tests/index.md index 2cb77d83b0..e12d6dfee0 100644 --- a/sites/docs/src/content/testing/integration-tests/index.md +++ b/sites/docs/src/content/testing/integration-tests/index.md @@ -4,50 +4,78 @@ title: 通过集成测试检查应用的功能 # description: Learn how to write integration tests description: 学习如何编写集成测试 tags: 测试,Flutter Test,集成测试 +ai-translated: true --- ## Introduction +## 介绍 + This guide describes how to run integration tests with your Flutter app. With it, you'll learn how to do the following: +本指南说明如何为 Flutter 应用运行集成测试。你将学习如何: + * Set up integration tests. * Verify if an app displays specific text. * Tap specific widgets. * Run integration tests. + 设置集成测试。 +* 验证应用是否显示特定文本。 +* 点击特定 widget。 +* 运行集成测试。 + The guide references the `counter_app` project that comes with Flutter and the Flutter [`integration_test`][] package. The `integration_test` package lets you: +本指南引用 Flutter 自带的 `counter_app` 项目以及 Flutter [`integration_test`][] package。`integration_test` package 可以: + * Use the `flutter drive` command to run tests on a physical device or emulator. * Run on [Firebase Test Lab][], to automate testing on a variety of devices. * Use [flutter_test][] APIs to write tests in a style similar to [widget tests][]. + 使用 `flutter drive` 命令在真机或模拟器上运行测试。 +* 在 [Firebase Test Lab][] 上运行,在多种设备上自动化测试。 +* 使用 [flutter_test][] API,以类似[ widget 测试][widget tests]的风格编写测试。 + ## Create a new app to test +## 创建待测新应用 + Integration testing requires an app to test. This example uses the built-in **Counter App** example that Flutter produces when you run the `flutter create` command. The counter app allows a user to tap on a button to increase a counter. +集成测试需要待测应用。本示例使用运行 `flutter create` 时 Flutter 生成的内置**计数器应用**。计数器应用允许用户点击按钮增加计数。 + 1. To create an instance of the built-in Flutter app, run the following command in your terminal: + 要在终端中创建内置 Flutter 应用实例,请运行: + ```console $ flutter create counter_app ``` 1. Change into the `counter_app` directory. + 进入 `counter_app` 目录。 + 1. Open `lib/main.dart` in your preferred IDE. + 在你偏好的 IDE 中打开 `lib/main.dart`。 + 1. Add a `key` parameter to the `floatingActionButton()` widget with an instance of a `Key` class with a string value of `increment`. + 为 `floatingActionButton()` widget 添加 `key` 参数,使用字符串值为 `increment` 的 `Key` 类实例。 + ```dart floatingActionButton: FloatingActionButton( [!key: const ValueKey('increment'),!] @@ -59,9 +87,13 @@ The counter app allows a user to tap on a button to increase a counter. 1. Save your `lib/main.dart` file. + 保存 `lib/main.dart` 文件。 + After these changes, the `lib/main.dart` file should resemble the following code. +完成上述修改后,`lib/main.dart` 应类似以下代码。 + ```dart title="lib/main.dart" import 'package:flutter/material.dart'; @@ -129,17 +161,25 @@ class _MyHomePageState extends State { ## Add the `integration_test` dependency +## 添加 `integration_test` 依赖 + You need to add the testing packages to your new app. +需要为新建应用添加测试 package。 + To add `integration_test` and `flutter_test` packages as `dev_dependencies` using `sdk: flutter`, run following command. +要使用 `sdk: flutter` 将 `integration_test` 与 `flutter_test` package 添加为 `dev_dependencies`,请运行: + ```console $ flutter pub add "dev:integration_test:{sdk: flutter}" ``` Output: +输出: + ```console Building flutter tool... Resolving dependencies... @@ -160,6 +200,8 @@ Try `flutter pub outdated` for more information. Updated `pubspec.yaml` file: +更新后的 `pubspec.yaml` 文件: + ```yaml title="pubspec.yaml" # ... dev_dependencies: @@ -174,14 +216,23 @@ dev_dependencies: ## Create the integration test files +## 创建集成测试文件 + Integration tests reside in a separate directory inside your Flutter project. +集成测试位于 Flutter 项目内的独立目录中。 + 1. Create a new directory named `integration_test`. 1. Add empty file named `app_test.dart` in that directory. +1. 新建名为 `integration_test` 的目录。 +1. 在该目录中添加名为 `app_test.dart` 的空文件。 + The resulting directory tree should resemble the following: +最终目录树应类似: + ```plaintext counter_app/ lib/ @@ -192,18 +243,26 @@ counter_app/ ## Write the integration test +## 编写集成测试 + The integration test file consists of a Dart code file with dependencies on `integration_test`, `flutter_test`, and your app's Dart file. +集成测试文件是由依赖 `integration_test`、`flutter_test` 以及应用 Dart 文件的 Dart 代码文件组成。 + 1. Open your `integration_test/app_test.dart` file in your preferred IDE. + 在你偏好的 IDE 中打开 `integration_test/app_test.dart`。 + 1. Copy the following code and paste it into your `integration_test/app_test.dart` file. The last import should point to the `main.dart` file of your `counter_app`. (This `import` points to the example app called `introduction`.) + 将以下代码复制并粘贴到 `integration_test/app_test.dart`。最后一个 import 应指向 `counter_app` 的 `main.dart`。(此 `import` 指向名为 `introduction` 的示例应用。) + ```dart title="integration_test/counter_test.dart" import 'package:flutter/material.dart'; @@ -242,28 +301,46 @@ and your app's Dart file. This example goes through three steps: +本示例分三步: + 1. Initialize `IntegrationTestWidgetsFlutterBinding`. This singleton service executes tests on a physical device. +1. 初始化 `IntegrationTestWidgetsFlutterBinding`。该单例服务在物理设备上执行测试。 + 2. Interact and test widgets using the `WidgetTester` class. +2. 使用 `WidgetTester` 类与 widget 交互并测试。 + 3. Test the important scenarios. +3. 测试重要场景。 + ## Run integration tests +## 运行集成测试 + The integration tests that run vary depending on the platform on which you test. +运行的集成测试因测试平台而异。 + * To test a desktop platform, use the command line or a CI system. * To test a mobile platform, use the command line or Firebase Test Lab. * To test in a web browser, use the command line. + 在桌面平台上测试,请使用命令行或 CI 系统。 +* 在移动平台上测试,请使用命令行或 Firebase Test Lab。 +* 在 Web 浏览器中测试,请使用命令行。 + --- ### Test on a desktop platform +### 在桌面平台上测试 +
-Expand if you test Linux apps using a CI system +Expand if you test Linux apps using a CI system / 若使用 CI 系统测试 Linux 应用请展开 To test a Linux app, your CI system must invoke an X server first. In the GitHub Action, GitLab Runner, or similar configuration file, @@ -275,6 +352,12 @@ launch and test your Linux app. As an example using GitHub Actions, your `jobs.setup.steps` should include a step resembling the following: +测试 Linux 应用时,CI 系统须先启动 X server。在 GitHub Action、GitLab Runner 或类似配置文件中,将集成测试配置为与 `xvfb-run` 工具配合使用。这样会启动 X Window 系统,Flutter 可在其中启动并测试 Linux 应用。 + +以 GitHub Actions 为例,`jobs.setup.steps` 应包含类似下面的步骤: + +### Example workflow + ### 目录结构 ```yaml @@ -289,6 +372,8 @@ This starts the integration test within an X Window. If you don't configure your integration in this way, Flutter returns an error. +这样会先在 X Window 中启动集成测试。若未如此配置集成测试,Flutter 会返回错误。 + ```console Building Linux application... Error waiting for a debug connection: The log reader stopped unexpectedly, or never started. @@ -299,8 +384,12 @@ Error waiting for a debug connection: The log reader stopped unexpectedly, or ne To test on a macOS, Windows, or Linux platform, complete the following tasks. +在 macOS、Windows 或 Linux 平台上测试,请完成以下任务。 + 1. Run the following command from the root of the project. + 在项目根目录运行: + ```console $ flutter test integration_test/app_test.dart ``` @@ -309,8 +398,12 @@ complete the following tasks. choose the desktop platform. Type `1` to choose the desktop platform. + 若提示选择测试平台,请选择桌面平台。输入 `1` 选择桌面平台。 + Based on platform, the command result should resemble the following output. +根据平台,命令结果应类似以下输出。 + @@ -333,19 +426,29 @@ Based on platform, the command result should resemble the following output. ### Test on an Android device +### 在 Android 设备上测试 + To test on a real Android device, complete the following tasks. +在真实 Android 设备上测试,请完成以下任务。 + 1. Connect the Android device. + 连接 Android 设备。 + 1. Run the following command from the root of the project. + 在项目根目录运行: + ```console $ flutter test integration_test/app_test.dart ``` The result should resemble the following output. + 结果应类似以下输出。 + ```console $ flutter test integration_test/app_test.dart 00:04 +0: loading /path/to/counter_app/integration_test/app_test.dart @@ -359,22 +462,34 @@ complete the following tasks. If not, subsequent tests fail. If needed, press on the app and choose **Remove App** from the context menu. + 确认测试结束后已移除计数器应用。否则后续测试会失败。必要时长按应用,在上下文菜单中选择 **Remove App**。 + --- ### Test on an iOS device +### 在 iOS 设备上测试 + To test on a real iOS device, complete the following tasks. +在真实 iOS 设备上测试,请完成以下任务。 + 1. Connect the iOS device. + 连接 iOS 设备。 + 1. Run the following command from the root of the project. + 在项目根目录运行: + ```console $ flutter test integration_test/app_test.dart ``` The result should resemble the following output. + 结果应类似以下输出。 + ```console $ flutter test integration_test/app_test.dart 00:04 +0: loading /path/to/counter_app/integration_test/app_test.dart @@ -388,10 +503,14 @@ To test on a real iOS device, complete the following tasks. If not, subsequent tests fail. If needed, press on the app and choose **Remove App** from the context menu. + 确认测试结束后已移除计数器应用。否则后续测试会失败。必要时长按应用,在上下文菜单中选择 **Remove App**。 + --- ### Test in a web browser +### 在 Web 浏览器中测试 + {% comment %} TODO(ryjohn): Add back after other WebDriver versions are supported: https://github.com/flutter/flutter/issues/90158 @@ -409,8 +528,12 @@ and download the corresponding web driver: To test in a web browser, perform the following steps. +在 Web 浏览器中测试,请按以下步骤操作。 + 1. Install [ChromeDriver][] into the directory of your choice. + 将 [ChromeDriver][] 安装到你选择的目录。 + ```console $ npx @puppeteer/browsers install chromedriver@stable ``` @@ -418,12 +541,18 @@ To test in a web browser, perform the following steps. To simplify the install, this command uses the [`@puppeteer/browsers`][puppeteer] Node library. + 为简化安装,该命令使用 [`@puppeteer/browsers`][puppeteer] Node library。 + [puppeteer]: https://www.npmjs.com/package/@puppeteer/browsers 1. Add the path to ChromeDriver to your `$PATH` environment variable. + 将 ChromeDriver 路径加入 `$PATH` 环境变量。 + 1. Verify the ChromeDriver install succeeded. + 验证 ChromeDriver 安装成功。 + ```console $ chromedriver --version ChromeDriver 124.0.6367.60 (8771130bd84f76d855ae42fbe02752b03e352f17-refs/branch-heads/6367@{#798}) @@ -432,14 +561,20 @@ To test in a web browser, perform the following steps. 1. In your `counter_app` project directory, create a new directory named `test_driver`. + 在 `counter_app` 项目目录中新建 `test_driver` 目录。 + ```console $ mkdir test_driver ``` 1. In this directory, create a new file named `integration_test.dart`. + 在该目录中新建 `integration_test.dart` 文件。 + 1. Copy the following code and paste it into your `integration_test.dart` file. + 将以下代码复制并粘贴到 `integration_test.dart` 文件。 + ```dart title="test_driver/integration_test.dart" import 'package:integration_test/integration_test_driver.dart'; @@ -449,12 +584,16 @@ To test in a web browser, perform the following steps. 1. Launch `chromedriver` as follows: + 按以下方式启动 `chromedriver`: + ```console $ chromedriver --port=4444 ``` 1. From the root of the project, run the following command: + 在项目根目录运行: + ```console $ flutter drive \ --driver=test_driver/integration_test.dart \ @@ -464,6 +603,8 @@ To test in a web browser, perform the following steps. The response should resemble the following output: + 响应应类似以下输出: + ```console Resolving dependencies... leak_tracker 10.0.0 (10.0.5 available) @@ -490,6 +631,8 @@ To test in a web browser, perform the following steps. To run this as a headless test, run `flutter drive` with `-d web-server` option: + 要以无头方式运行,请对 `flutter drive` 使用 `-d web-server` 选项: + ```console $ flutter drive \ --driver=test_driver/integration_test.dart \ @@ -500,33 +643,57 @@ To test in a web browser, perform the following steps. To learn more, see the [Running Flutter driver tests with web][] wiki page. +更多信息请参阅 [Running Flutter driver tests with web][] wiki 页面。 + --- ### Test in Firebase Test Lab (Android) +### 在 Firebase Test Lab 中测试(Android) + You can use Firebase Test Lab to test Android targets. +可使用 Firebase Test Lab 测试 Android 目标。 + #### Android setup +#### Android 设置 + Follow the instructions in the [Android Device Testing][] section of the README. +请遵循 README 中的 [Android Device Testing][] 章节说明。 + #### Test Lab project setup +#### Test Lab 项目设置 + 1. Launch your [Firebase Console][]. + 打开 [Firebase Console][]。 + 1. Create a new Firebase project if necessary. + 如有需要,创建新的 Firebase 项目。 + 1. Navigate to **Quality > Test Lab**. + 导航至 **Quality > Test Lab**。 + Firebase Test Lab Console #### Upload an Android APK +#### 上传 Android APK + Complete the following steps to upload an Android APK. +完成以下步骤以上传 Android APK。 + 1. Create an APK using Gradle. + 使用 Gradle 创建 APK。 + ```console // Go to the Android directory which contains the gradlew script $ pushd android @@ -544,9 +711,13 @@ Complete the following steps to upload an Android APK. * `_test.dart`: The file created in the **Project Setup** section. + `_test.dart`:在 **Project Setup** 章节创建的文件。 + 1. If needed, pass parameters into the integration test as a comma-separated list. Encode all parameters as `base64`. + 如有需要,以逗号分隔列表向集成测试传入参数,并将所有参数编码为 `base64`。 + ```console $ ./gradlew project:task -Pdart-defines="{base64 (key=value)}[, ...]" ``` @@ -554,8 +725,12 @@ Complete the following steps to upload an Android APK. * `(key=value)}[, ...]`: Replace this with a comma-separated list of key value pairs. + `(key=value)}[, ...]`:替换为逗号分隔的键值对列表。 + 1. Return to your previous directory. + 返回之前的目录。 + ```console $ popd ``` @@ -563,64 +738,106 @@ Complete the following steps to upload an Android APK. For additional instructions, see the [Firebase Test Lab section of the README][]. +更多说明请参阅 README 的 [Firebase Test Lab section][Firebase Test Lab section of the README]。 + #### Start Robo test +#### 启动 Robo 测试 + To use Robo test to run integration tests, complete the following steps. +要使用 Robo 测试运行集成测试,请完成以下步骤。 + 1. Drag the debug APK from `/build/app/outputs/apk/debug` into the **Android Robo Test** target on the web page. For example: + 将 debug APK 从 `/build/app/outputs/apk/debug` 拖到网页上的 **Android Robo Test** 目标。例如: + Firebase Test Lab upload 1. Click **Run a test**. + 点击 **Run a test**。 + 1. Select the **Instrumentation** test type. + 选择 **Instrumentation** 测试类型。 + 1. Add the App APK to the **App APK or AAB** box. + 将 App APK 添加到 **App APK or AAB** 框。 + `/build/app/outputs/apk/debug/.apk` 1. Add the Test APK to the **Test APK** box. + 将 Test APK 添加到 **Test APK** 框。 + `/build/app/outputs/apk/androidTest/debug/.apk` Firebase Test Lab upload two APKs 1. If a failure occurs, click the red icon to view the output: + 若失败,点击红色图标查看输出: + Firebase Test Lab test results --- ### Test in Firebase Test Lab (iOS) +### 在 Firebase Test Lab 中测试(iOS) + You can use Firebase Test Lab to test iOS targets. +可使用 Firebase Test Lab 测试 iOS 目标。 + #### iOS setup +#### iOS 设置 + Follow the [iOS Device Testing instructions][]. +请遵循 [iOS Device Testing instructions][]。 + #### Test Lab project setup +#### Test Lab 项目设置 + 1. Launch your [Firebase Console][]. + 打开 [Firebase Console][]。 + 1. Create a new Firebase project if necessary. + 如有需要,创建新的 Firebase 项目。 + 1. Navigate to **Quality > Test Lab**. + 导航至 **Quality > Test Lab**。 + Firebase Test Lab Console #### Upload Xcode tests through the Firebase Console +#### 通过 Firebase Console 上传 Xcode 测试 + To learn how to upload tests from a ZIP file, using the Firebase Test Lab Console, consult the [Firebase Test Lab iOS instructions][]. +要了解如何通过 Firebase Test Lab Console 从 ZIP 文件上传测试,请参阅 [Firebase Test Lab iOS instructions][]。 + #### Upload Xcode tests to Firebase Console with the command line +#### 通过命令行将 Xcode 测试上传到 Firebase Console + To learn how to upload tests from a ZIP file from the command line to the Firebase Test Lab Console, consult the [iOS Device Testing instructions][]. +要了解如何从命令行将 ZIP 中的测试上传到 Firebase Test Lab Console,请参阅 [iOS Device Testing instructions][]。 + [`integration_test`]: {{site.repo.flutter}}/tree/main/packages/integration_test#integration_test [Android Device Testing]: {{site.repo.flutter}}/tree/main/packages/integration_test#android-device-testing [ChromeDriver]: https://googlechromelabs.github.io/chrome-for-testing/ diff --git a/sites/docs/src/content/testing/native-debugging.md b/sites/docs/src/content/testing/native-debugging.md index a9f0cb6932..20f7edf15c 100644 --- a/sites/docs/src/content/testing/native-debugging.md +++ b/sites/docs/src/content/testing/native-debugging.md @@ -1,7 +1,11 @@ --- -title: Use a native language debugger -shortTitle: debuggers -description: How to connect a native debugger to your running Flutter app. +# title: Use a native language debugger +title: 使用原生语言调试器 +# shortTitle: debuggers +shortTitle: 调试器 +# description: How to connect a native debugger to your running Flutter app. +description: 如何将原生调试器连接到你正在运行的 Flutter 应用。 +ai-translated: true --- @@ -11,40 +15,78 @@ This guide presumes you understand general debugging, have installed Flutter and git, and have familiarity with the Dart language as well as one of the following languages: Java, Kotlin, Swift, or Objective-C. + +本指南假定你理解一般调试知识, +已安装 Flutter 和 Git,并熟悉 +Dart 语言以及以下语言之一: +Java、Kotlin、Swift 或 Objective-C。 ::: If you write Flutter apps only with Dart code, you can debug your code using your IDE's debugger. The Flutter team recommends VS Code. +如果你仅用 Dart 代码编写 Flutter 应用, +可以使用 IDE 的调试器调试代码。 +Flutter 团队推荐使用 VS Code。 + If you write a platform-specific plugin or use platform-specific libraries, you can debug that portion of your code with a native debugger. +如果你编写平台特定插件或 +使用平台特定库,可以使用原生调试器 +调试那部分代码。 + - To debug iOS or macOS code written in Swift or Objective-C, you can use Xcode. - To debug Android code written in Java or Kotlin, you can use Android Studio. - To debug Windows code written in C++, you can use Visual Studio. + 要调试用 Swift 或 Objective-C 编写的 iOS 或 macOS 代码, + 可以使用 Xcode。 +- 要调试用 Java 或 Kotlin 编写的 Android 代码, + 可以使用 Android Studio。 +- 要调试用 C++ 编写的 Windows 代码, + 可以使用 Visual Studio。 + This guide shows you how you can connect _two_ debuggers to your Dart app, one for Dart, and one for the native code. +本指南说明如何将 _两个_ +调试器连接到你的 Dart 应用:一个用于 Dart,一个用于原生代码。 + ## Debug Dart code +## 调试 Dart 代码 + This guide describes how to use VS Code to debug your Flutter app. You can also use your preferred IDE with the Flutter and Dart plugins installed and configured. +本指南介绍如何使用 VS Code 调试 Flutter 应用。 +你也可以使用已安装并配置 +Flutter 和 Dart 插件的首选 IDE。 + ## Debug Dart code using VS Code +## 使用 VS Code 调试 Dart 代码 + The following procedure explains how to use the Dart debugger with the default sample Flutter app. The featured components in VS Code work and appear when debugging your own Flutter project as well. +以下步骤说明如何将 Dart 调试器 +与默认示例 Flutter 应用配合使用。 +在调试你自己的 Flutter 项目时, +VS Code 中展示的这些组件同样可用并会出现。 + 1. Create a basic Flutter app. + 创建一个基本的 Flutter 应用。 + ```console $ flutter create my_app ``` @@ -75,24 +117,40 @@ debugging your own Flutter project as well. 1. Open the `lib\main.dart` file in the Flutter app using VS Code. + 使用 VS Code 打开 Flutter 应用中的 `lib\main.dart` 文件。 + 1. Click the bug icon (![VS Code's bug icon to trigger the debugging mode of a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the following panes in VS Code: + 点击虫子图标 + (![VS Code 的虫子图标,用于触发 Flutter 应用的调试模式](/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png))。 + 这会在 VS Code 中打开以下窗格: + + - **Debug** + - **Debug Console** + - **Widget Inspector** + - **Debug** - **Debug Console** - **Widget Inspector** The first time you run the debugger takes the longest. + 首次运行调试器耗时最长。 + {% comment %} ![VS Code window with debug panes opened](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-debugger.png){:width="100%"} {% endcomment %} 1. Test the debugger. + 测试调试器。 + a. In `main.dart`, click on this line: + a. 在 `main.dart` 中,点击这一行: + ```dart _counter++; ``` @@ -101,9 +159,15 @@ debugging your own Flutter project as well. This adds a breakpoint where the `_counter` variable increments. + b. 按 Shift + F9。 + 这会在 `_counter` 变量递增处添加断点。 + c. In the app, click the **+** button to increment the counter. The app pauses. + c. 在应用中,点击 **+** 按钮 + 以增加计数器。应用会暂停。 + {% comment %} ![Flutter test app paused](/assets/images/docs/testing/debugging/native/macos/basic-app.png){:width="50%"}
@@ -115,6 +179,8 @@ debugging your own Flutter project as well. d. At this point, VS Code displays: + d. 此时,VS Code 会显示: + - In the **Editor Groups**: - The highlighted breakpoint in `main.dart` - The widget hierarchy for the Flutter app @@ -125,22 +191,44 @@ debugging your own Flutter project as well. - In the **panel**: - The log of the Flutter app in the **Debug console** + - 在 **Editor Groups** 中: + - `main.dart` 中高亮的断点 + - **Widget Inspector** 的 **Widget Tree** 中 + Flutter 应用的 widget 层级 + - 在**侧边栏**中: + - **Call Stack** 部分中的应用状态 + - **Variables** 部分中 `this` 局部变量的值 + - 在**面板**中: + - **Debug console** 中的 Flutter 应用日志 + {% comment %} ![VS Code window with Flutter app paused](/assets/images/docs/testing/debugging/vscode-ui/screens/vscode-debugger-paused.png){:width="100%"} {% endcomment %} ### VS Code Flutter debugger +### VS Code Flutter 调试器 + The Flutter plugin for VS Code adds a number of components to the VS Code user interface. +VS Code 的 Flutter 插件会向 +VS Code 用户界面添加多个组件。 + #### Changes to VS Code interface +#### VS Code 界面的变化 + When launched, the Flutter debugger adds debugging tools to the VS Code interface. +启动后,Flutter 调试器会向 +VS Code 界面添加调试工具。 + The following screenshot and table explain the purpose of each tool. +以下截图和表格说明每个工具的用途。 + ![VS Code with the Flutter plugin UI additions](/assets/images/docs/testing/debugging/vscode-ui/screens/debugger-parts.png) | Highlight Color in Screenshot | Bar, Panel, or Tab | Contents | @@ -158,16 +246,39 @@ The following screenshot and table explain the purpose of each tool. | | Debug Console | Logs or error messages that the Flutter app generates while debugging | | | Terminal | System shell prompt contained in VS Code | +| 截图中的高亮颜色 | 栏、面板或标签页 | 内容 | +|-------------------------------|---------------------|-----------------------------------------------------------------------------------| +| **黄色** | Variables | Flutter 应用中变量的当前值列表 | +| | Watch | 你在 Flutter 应用中选择跟踪的项列表 | +| | Call Stack | Flutter 应用中活动子例程的栈 | +| | Breakpoints | 你设置的异常与断点列表 | +| **绿色** | `` | 你正在编辑的文件 | +| **粉色** | Widget Inspector | 正在运行的 Flutter 应用中的 widget 层级 | +| **蓝色** | Layout Explorer | Flutter 如何在 Widget Inspector 中放置你所选 widget 的可视化 | +| | Widget Details Tree | Widget Inspector 中所选 widget 的属性列表 | +| **橙色** | Problems | Dart 分析器在当前 Dart 文件中发现的问题列表 | +| | Output | 构建应用时 Flutter 应用返回的响应 | +| | Debug Console | 调试时 Flutter 应用生成的日志或错误消息 | +| | Terminal | VS Code 中包含的系统 shell 提示符 | + {:.table .table-striped} To change where the panel (in **orange**) appears in VS Code, go to **View** > **Appearance** > **Panel Position**. +要更改面板(**橙色**)在 VS Code 中的位置, +请前往 **View** > **Appearance** > **Panel Position**。 + #### VS Code Flutter debugging toolbar +#### VS Code Flutter 调试工具栏 + The toolbar allows you to debug using any debugger. You can step in, out, and over Dart statements, hot reload, or resume the app. +工具栏让你使用任何调试器进行调试。 +你可以单步进入、跳出和跳过 Dart 语句,热重载或恢复应用。 + ![Flutter debugger toolbar in VS Code](/assets/images/docs/testing/debugging/vscode-ui/screens/debug-toolbar.png) | Icon | Action | Default keyboard shortcut | @@ -182,19 +293,40 @@ You can step in, out, and over Dart statements, hot reload, or resume the app. | {% render "docs/vscode-flutter-bar/stop.md" %} | Stop | Shift + F5 | | {% render "docs/vscode-flutter-bar/inspector.md" %} | Open Widget Inspector | | +| 图标 | 操作 | 默认键盘快捷键 | +|-----------------------------------------------------|-----------------------|-------------------------------------------------------| +| {% render "docs/vscode-flutter-bar/play.md" %} | 启动或恢复 | F5 | +| {% render "docs/vscode-flutter-bar/pause.md" %} | 暂停 | F6 | +| {% render "docs/vscode-flutter-bar/step-over.md" %} | 单步跳过 | F10 | +| {% render "docs/vscode-flutter-bar/step-into.md" %} | 单步进入 | F11 | +| {% render "docs/vscode-flutter-bar/step-out.md" %} | 单步跳出 | Shift + F11 | +| {% render "docs/vscode-flutter-bar/hot-reload.md" %} | 热重载 | Ctrl + F5 | +| {% render "docs/vscode-flutter-bar/hot-restart.md" %} | 热重启 | Shift + Special + F5 | +| {% render "docs/vscode-flutter-bar/stop.md" %} | 停止 | Shift + F5 | +| {% render "docs/vscode-flutter-bar/inspector.md" %} | 打开 Widget Inspector | | + {:.table .table-striped} ## Update test Flutter app +## 更新测试用 Flutter 应用 + For the remainder of this guide, you need to update the test Flutter app. This update adds native code to debug. +在本指南的其余部分,你需要更新 +测试用 Flutter 应用。此更新会添加用于调试的原生代码。 + 1. Open the `lib/main.dart` file using your preferred IDE. + 使用你首选的 IDE 打开 `lib/main.dart` 文件。 + 1. Replace the contents of `main.dart` with the following code. + 将 `main.dart` 的内容替换为以下代码。 +
- Expand to see Flutter code for this example + Expand to see Flutter code for this example · 展开以查看此示例的 Flutter 代码 ```dart title="lib/main.dart" // Copyright 2023 The Flutter Authors. All rights reserved. @@ -307,6 +439,9 @@ test Flutter app. This update adds native code to debug. 1. To add the `url_launcher` package as a dependency, run `flutter pub add`: + 要将 `url_launcher` 包添加为依赖, + 请运行 `flutter pub add`: + ```console $ flutter pub add url_launcher ``` @@ -334,9 +469,13 @@ test Flutter app. This update adds native code to debug. 1. To check what changed with the codebase: + 要检查代码库有哪些变更: + {: type="a"} 1. In Linux or macOS, run this `find` command. + 在 Linux 或 macOS 上,运行此 `find` 命令。 + ```console $ find ./ -mmin -120 ``` @@ -356,6 +495,8 @@ test Flutter app. This update adds native code to debug. ``` 1. In Windows, run this command in the command prompt. + 在 Windows 上,在命令提示符中运行此命令。 + ```ps Get-ChildItem C:\dev\example\ -Rescurse | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-1)} ``` @@ -406,15 +547,27 @@ test Flutter app. This update adds native code to debug. Installing `url_launcher` added config files and code files for all target platforms in the Flutter app directory. +安装 `url_launcher` 会在 Flutter 应用目录中为 +所有目标平台添加配置文件和代码文件。 + ## Debug Dart and native language code at the same time +## 同时调试 Dart 与原生语言代码 + This section explains how to debug the Dart code in your Flutter app and any native code with its regular debugger. This capability allows you to leverage Flutter's hot reload when editing native code. +本节说明如何调试 Flutter 应用中的 Dart 代码, +并使用其常规调试器调试任何原生代码。 +此能力让你在编辑原生代码时 +仍可利用 Flutter 的热重载。 + ### Debug Dart and Android code using Android Studio +### 使用 Android Studio 调试 Dart 与 Android 代码 + To debug native Android code, you need a Flutter app that contains Android code. In this section, you learn how to connect the Dart, Java, and Kotlin debuggers to your app. @@ -422,82 +575,162 @@ You don't need VS Code to debug both Dart and Android code. This guide includes the VS Code instructions to be consistent with the Xcode and Visual Studio guides. +要调试原生 Android 代码,你需要包含 +Android 代码的 Flutter 应用。在本节中,你将学习如何将 +Dart、Java 和 Kotlin 调试器连接到你的应用。 +调试 Dart 与 Android 代码不一定需要 VS Code。 +本指南包含 VS Code 说明,以与 +Xcode 和 Visual Studio 指南保持一致。 + These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app). +本节使用在[更新测试用 Flutter 应用](#update-test-flutter-app)中创建的 +相同示例 Flutter `url_launcher` 应用。 + {% render "docs/debug/debug-flow-android.md" %} ### Debug Dart and iOS code using Xcode +### 使用 Xcode 调试 Dart 与 iOS 代码 + To debug iOS code, you need a Flutter app that contains iOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode. +要调试 iOS 代码,你需要包含 iOS 代码的 Flutter 应用。 +在本节中,你将学习将两个调试器连接到你的应用: +通过 VS Code 的 Flutter 与 Xcode。你需要同时运行 VS Code 和 Xcode。 + These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app). +本节使用在[更新测试用 Flutter 应用](#update-test-flutter-app)中创建的 +相同示例 Flutter `url_launcher` 应用。 + {% render "docs/debug/debug-flow-ios.md" %} ### Debug Dart and macOS code using Xcode +### 使用 Xcode 调试 Dart 与 macOS 代码 + To debug macOS code, you need a Flutter app that contains macOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode. +要调试 macOS 代码,你需要包含 macOS 代码的 Flutter 应用。 +在本节中,你将学习将两个调试器连接到你的应用: +通过 VS Code 的 Flutter 与 Xcode。你需要同时运行 VS Code 和 Xcode。 + These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app). +本节使用在[更新测试用 Flutter 应用](#update-test-flutter-app)中创建的 +相同示例 Flutter `url_launcher` 应用。 + {% render "docs/debug/debug-flow-macos.md" %} ### Debug Dart and C++ code using Visual Studio +### 使用 Visual Studio 调试 Dart 与 C++ 代码 + To debug C++ code, you need a Flutter app that contains C++ code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Visual Studio. You need to run both VS Code and Visual Studio. +要调试 C++ 代码,你需要包含 C++ 代码的 Flutter 应用。 +在本节中,你将学习将两个调试器连接到你的应用: +通过 VS Code 的 Flutter 与 Visual Studio。 +你需要同时运行 VS Code 和 Visual Studio。 + These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app). +本节使用在[更新测试用 Flutter 应用](#update-test-flutter-app)中创建的 +相同示例 Flutter `url_launcher` 应用。 + {% render "docs/debug/debug-flow-windows.md" %} ## Resources +## 资源 + Check out the following resources on debugging Flutter, iOS, Android, macOS and Windows: +请查阅以下关于调试 Flutter、iOS、Android、 +macOS 和 Windows 的资源: + +### Flutter + ### Flutter - [Debugging Flutter apps][] + + [调试 Flutter 应用][Debugging Flutter apps] + - [Flutter inspector][] and the [DevTools][] docs + + [Flutter inspector][] 与 [DevTools][] 文档 + - [Performance profiling][] + [性能分析][Performance profiling] + [Debugging Flutter apps]: /testing/debugging [Performance profiling]: /perf/ui-performance ### Android +### Android + You can find the following debugging resources on [developer.android.com][]. +你可以在 [developer.android.com][] 上找到以下调试资源。 + - [Debug your app][] + + [调试你的应用][Debug your app] + - [Android Debug Bridge (adb)][] + [Android Debug Bridge (adb)][Android Debug Bridge (adb)] + ### iOS and macOS +### iOS 与 macOS + You can find the following debugging resources on [developer.apple.com][]. +你可以在 [developer.apple.com][] 上找到以下调试资源。 + - [Debugging][] + + [调试][Debugging] + - [Instruments Help][] + [Instruments 帮助][Instruments Help] + +### Windows + ### Windows You can find debugging resources on [Microsoft Learn][]. +你可以在 [Microsoft Learn][] 上找到调试资源。 + - [Visual Studio Debugger][] + + [Visual Studio 调试器][Visual Studio Debugger] + - [Learn to debug C++ code using Visual Studio][] + [学习使用 Visual Studio 调试 C++ 代码][Learn to debug C++ code using Visual Studio] + [Android Debug Bridge (adb)]: {{site.android-dev}}/studio/command-line/adb [Debug your app]: {{site.android-dev}}/studio/debug [Debugging]: {{site.apple-dev}}/support/debugging/ diff --git a/sites/docs/src/content/testing/overview.md b/sites/docs/src/content/testing/overview.md index da9d2506b3..a047098d30 100644 --- a/sites/docs/src/content/testing/overview.md +++ b/sites/docs/src/content/testing/overview.md @@ -1,27 +1,46 @@ --- -title: Testing Flutter apps +# title: Testing Flutter apps +title: 测试 Flutter 应用 +# description: >- +# Learn more about the different types of testing and how to write them. description: >- - Learn more about the different types of testing and how to write them. + 了解不同类型的测试以及如何编写它们。 +ai-translated: true --- The more features your app has, the harder it is to test manually. Automated tests help ensure that your app performs correctly before you publish it, while retaining your feature and bug fix velocity. +应用功能越多,手动测试就越困难。 +自动化测试有助于在发布前确保应用表现正确, +同时保持功能开发与 bug 修复的速度。 + Automated testing falls into a few categories: +自动化测试可分为以下几类: + * A [_unit test_](#unit-tests) tests a single function, method, or class. * A [_widget test_](#widget-tests) (in other UI frameworks referred to as _component test_) tests a single widget. * An [_integration test_](#integration-tests) tests a complete app or a large part of an app. + [_单元测试_](#unit-tests)(unit test)测试单个函数、方法或类。 +* [_widget 测试_](#widget-tests)(widget test,在其他 UI 框架中称为 _component test_)测试单个 widget。 +* [_集成测试_](#integration-tests)(integration test) + 测试完整应用或应用的大部分。 + Generally speaking, a well-tested app has many unit and widget tests, tracked by [code coverage][], plus enough integration tests to cover all the important use cases. This advice is based on the fact that there are trade-offs between different kinds of testing, seen below. +一般而言,经过充分测试的应用会有大量由 [代码覆盖率][code coverage] 跟踪的单元测试和 widget 测试, +再加上足以覆盖所有重要用例的集成测试。此建议基于 +不同测试类型之间存在权衡,如下所示。 + | Tradeoff | Unit | Widget | Integration | |----------------------|--------|--------|-------------| | **Confidence** | Low | Higher | Highest | @@ -29,10 +48,19 @@ seen below. | **Dependencies** | Few | More | Most | | **Execution speed** | Quick | Quick | Slow | +| 权衡 | 单元测试 | Widget 测试 | 集成测试 | +|----------------------|--------|--------|-------------| +| **置信度** | 低 | 较高 | 最高 | +| **维护成本** | 低 | 较高 | 最高 | +| **依赖** | 少 | 较多 | 最多 | +| **执行速度** | 快 | 快 | 慢 | + {:.table .table-striped} ## Unit tests +## 单元测试 + A _unit test_ tests a single function, method, or class. The goal of a unit test is to verify the correctness of a unit of logic under a variety of conditions. @@ -45,12 +73,28 @@ For more information regarding unit tests, you can view the following recipes or run `flutter test --help` in your terminal. +_单元测试_(unit test)测试单个函数、方法或类。 +单元测试的目标是在多种条件下验证 +逻辑单元的正确性。 +被测单元的外部依赖通常会 +[mock 掉](/cookbook/testing/unit/mocking)。 +单元测试通常不会从磁盘读写、渲染到屏幕, +或从运行测试的进程之外接收用户操作。 +有关单元测试的更多信息, +你可以查看以下食谱, +或在终端中运行 `flutter test --help`。 + :::note If you're writing unit tests for code that uses plugins and you want to avoid crashes, check out [Plugins in Flutter tests][]. If you want to test your Flutter plugin, check out [Testing plugins][]. + +如果你为使用插件的代码编写单元测试并希望避免崩溃, +请参阅 [Flutter 测试中的插件][Plugins in Flutter tests]。 +如果你要测试 Flutter 插件, +请参阅 [测试插件][Testing plugins]。 ::: [Plugins in Flutter tests]: /testing/plugins-in-tests @@ -58,44 +102,94 @@ check out [Testing plugins][]. ### Recipes {:.no_toc} +### 食谱 {:.no_toc} + - [Introduction to unit testing](/cookbook/testing/unit/introduction) + + [单元测试简介](/cookbook/testing/unit/introduction) + - [Mock dependencies using Mockito](/cookbook/testing/unit/mocking) + [使用 Mockito mock 依赖](/cookbook/testing/unit/mocking) + ## Widget tests +## Widget 测试 + A _widget test_ (in other UI frameworks referred to as _component test_) tests a single widget. The goal of a widget test is to verify that the widget's UI looks and interacts as expected. Testing a widget involves multiple classes and requires a test environment that provides the appropriate widget lifecycle context. +_widget 测试_(widget test,在其他 UI 框架中称为 _component test_) +测试单个 widget。widget 测试的目标是验证 +widget 的 UI 外观与交互符合预期。测试 widget 涉及 +多个类,并需要能提供 +适当 widget 生命周期上下文的测试环境。 + For example, the Widget being tested should be able to receive and respond to user actions and events, perform layout, and instantiate child widgets. A widget test is therefore more comprehensive than a unit test. However, like a unit test, a widget test's environment is replaced with an implementation much simpler than a full-blown UI system. +例如,被测 Widget 应能接收并 +响应用户操作与事件、执行布局并实例化子 +widget。因此 widget 测试比单元测试更全面。 +不过与单元测试类似,widget 测试的环境会被替换为 +比完整 UI 系统简单得多的实现。 + ### Recipes {:.no_toc} +### 食谱 {:.no_toc} + - [Introduction to widget testing](/cookbook/testing/widget/introduction) + + [Widget 测试简介](/cookbook/testing/widget/introduction) + - [Find widgets using finders](/cookbook/testing/widget/finders) + + [使用 finder 查找 widget](/cookbook/testing/widget/finders) + - [Handling scrolling in widget tests](/cookbook/testing/widget/scrolling) + + [在 widget 测试中处理滚动](/cookbook/testing/widget/scrolling) + - [Tap, drag, and enter text in widget tests](/cookbook/testing/widget/tap-drag) + + [在 widget 测试中点击、拖拽和输入文本](/cookbook/testing/widget/tap-drag) + - [Test different orientations](/cookbook/testing/widget/orientation) + [测试不同屏幕方向](/cookbook/testing/widget/orientation) + ## Integration tests +## 集成测试 + An _integration test_ tests a complete app or a large part of an app. The goal of an integration test is to verify that all the widgets and services being tested work together as expected. Furthermore, you can use integration tests to verify your app's performance. +_集成测试_(integration test)测试完整应用或应用的大部分。 +集成测试的目标是验证所有被测 +widget 与服务能按预期协同工作。 +此外,你还可以使用集成 +测试验证应用性能。 + Generally, an _integration test_ runs on a real device or an OS emulator, such as iOS Simulator or Android Emulator. The app under test is typically isolated from the test driver code to avoid skewing the results. +一般而言,_集成测试_在真机或操作系统模拟器上运行, +例如 iOS Simulator 或 Android Emulator。 +被测应用通常与 +测试驱动代码隔离,以避免结果偏差。 + The Flutter SDK includes the [`integration_test`][] package. However, this package can't interact with native platform UI, such as permission dialogs, notifications, or platform views. @@ -103,37 +197,89 @@ For apps that need native interactions, you can use the [`patrol`][] package, an open-source framework that extends Flutter's testing capabilities with native platform support. +Flutter SDK 包含 [`integration_test`][] 包。 +不过该包无法与原生平台 UI 交互, +例如权限对话框、通知或 platform view。 +对于需要原生交互的应用,你可以使用 +[`patrol`][] 包,这是一个扩展开源框架, +在原生平台支持下扩展 Flutter 的测试能力。 + For more information on how to write integration tests, see the [integration testing page][]. +有关如何编写集成测试的更多信息,请参阅[集成测试页面][integration testing page]。 + [`integration_test`]: {{site.repo.flutter}}/tree/main/packages/integration_test [`patrol`]: {{site.pub-pkg}}/patrol ### Recipes {:.no_toc} +### 食谱 {:.no_toc} + - [Integration testing concepts](/cookbook/testing/integration/introduction) + + [集成测试概念](/cookbook/testing/integration/introduction) + - [Write and run a Flutter integration test](/testing/integration-tests) + + [编写并运行 Flutter 集成测试](/testing/integration-tests) + - [Write and run a Patrol integration test](https://patrol.leancode.co/documentation/write-your-first-test) + + [编写并运行 Patrol 集成测试](https://patrol.leancode.co/documentation/write-your-first-test) + - [Measure performance with an integration test](/cookbook/testing/integration/profiling) + [使用集成测试衡量性能](/cookbook/testing/integration/profiling) + ## Continuous integration services +## 持续集成服务 + Continuous integration (CI) services allow you to run your tests automatically when pushing new code changes. This provides timely feedback on whether the code changes work as expected and do not introduce bugs. +持续集成(CI)服务让你在推送新代码变更时 +自动运行测试。 +这能及时反馈代码变更 +是否按预期工作且未引入 bug。 + For information on running tests on various continuous integration services, see the following: +有关在各种持续 +集成服务上运行测试的信息,请参阅以下内容: + * [Continuous delivery using fastlane with Flutter][] + + [使用 fastlane 与 Flutter 进行持续交付][Continuous delivery using fastlane with Flutter] + * [Test Flutter apps on Appcircle][] + + [在 Appcircle 上测试 Flutter 应用][Test Flutter apps on Appcircle] + * [Test Flutter apps on Travis][] + + [在 Travis 上测试 Flutter 应用][Test Flutter apps on Travis] + * [Test Flutter apps on Cirrus][] + + [在 Cirrus 上测试 Flutter 应用][Test Flutter apps on Cirrus] + * [Codemagic CI/CD for Flutter][] + + [适用于 Flutter 的 Codemagic CI/CD][Codemagic CI/CD for Flutter] + * [Codemagic CI/CD for Patrol][] + + [适用于 Patrol 的 Codemagic CI/CD][Codemagic CI/CD for Patrol] + * [Flutter CI/CD with Bitrise][] + [使用 Bitrise 的 Flutter CI/CD][Flutter CI/CD with Bitrise] + [code coverage]: https://en.wikipedia.org/wiki/Code_coverage [Codemagic CI/CD for Flutter]: https://blog.codemagic.io/getting-started-with-codemagic/ [Codemagic CI/CD for Patrol]: https://docs.codemagic.io/integrations/patrol-integration/ diff --git a/sites/docs/src/content/testing/plugins-in-tests.md b/sites/docs/src/content/testing/plugins-in-tests.md index 3b3d50525d..c2bfa72c4c 100644 --- a/sites/docs/src/content/testing/plugins-in-tests.md +++ b/sites/docs/src/content/testing/plugins-in-tests.md @@ -1,7 +1,11 @@ --- -title: Plugins in Flutter tests -shortTitle: Plugin tests -description: Adding plugin as part of your Flutter tests. +# title: Plugins in Flutter tests +title: Flutter 测试中的插件 +# shortTitle: Plugin tests +shortTitle: 插件测试 +# description: Adding plugin as part of your Flutter tests. +description: 将插件作为 Flutter 测试的一部分。 +ai-translated: true --- :::note @@ -9,19 +13,32 @@ To learn how to avoid crashes from a plugin when testing your Flutter app, read on. To learn how to test your plugin code, check out [Testing plugins][]. + +要了解在测试 Flutter 应用时如何避免插件导致的崩溃,请继续阅读。 +要了解如何测试插件代码,请参阅 +[测试插件][Testing plugins]。 ::: [Testing plugins]: /testing/testing-plugins Almost all [Flutter plugins][] have two parts: +几乎所有 [Flutter 插件][Flutter plugins] 都包含两部分: + * Dart code, which provides the API your code calls. * Code written in a platform-specific (or "host") language, such as Kotlin or Swift, which implements those APIs. + Dart 代码,提供你的代码所调用的 API。 +* 用平台特定(或「宿主」)语言编写的代码, + 例如 Kotlin 或 Swift,用于实现这些 API。 + In fact, the native (or host) language code distinguishes a plugin package from a standard package. +事实上,原生(或宿主)语言代码是 +插件包与标准包的区别所在。 + [Flutter plugins]: /packages-and-plugins/using-packages Building and registering the host portion of a plugin @@ -34,6 +51,16 @@ When running [Dart unit tests][] or If the code you are testing calls any plugins, this often results in errors like the following: +构建并注册插件的宿主部分是 +Flutter 应用构建流程的一部分, +因此插件仅在你的代码运行于 +应用中时有效,例如通过 `flutter run` +或运行[集成测试][integration tests]时。 +运行 [Dart 单元测试][Dart unit tests] 或 +[widget 测试][widget tests] 时,宿主代码不可用。 +如果你测试的代码调用了任何插件, +通常会出现如下错误: + ```console MissingPluginException(No implementation found for method someMethodName on channel some_channel_name) ``` @@ -47,6 +74,11 @@ Plugin implementations that [only use Dart][] will work in unit tests. This is an implementation detail of the plugin, however, so tests shouldn't rely on it. + +[仅使用 Dart][only use Dart] 的插件实现 +可在单元测试中工作。然而这是插件的 +实现细节, +因此测试不应依赖这一点。 ::: [only use Dart]: /packages-and-plugins/developing-packages#dart-only-platform-implementations @@ -55,14 +87,26 @@ When unit testing code that uses plugins, there are several options to avoid this exception. The following solutions are listed in order of preference. +在单元测试使用插件的代码时, +有几种方式可避免此异常。 +以下解决方案按推荐顺序列出。 + ## Wrap the plugin +## 封装插件 + In most cases, the best approach is to wrap plugin calls in your own API, and provide a way of [mocking][] your own API in tests. +在大多数情况下,最佳做法是将插件 +调用封装在你自己的 API 中, +并在测试中提供 [mock][mocking] 你自己 API 的方式。 + This has several advantages: +这有几个优点: + * If the plugin API changes, you won't need to update your tests. * You are only testing your own code, @@ -72,20 +116,41 @@ This has several advantages: how the plugin is implemented, or even for non-plugin package dependencies. + 如果插件 API 变更, + 你无需更新测试。 +* 你只测试自己的代码, + 因此测试不会因你使用的 + 插件行为而失败。 +* 无论插件如何实现, + 甚至对于非插件包依赖, + 你都可以使用相同方法。 + [mocking]: /cookbook/testing/unit/mocking ## Mock the plugin's public API +## Mock 插件的公共 API + If the plugin's API is already based on class instances, you can mock it directly, with the following caveats: +如果插件 API 已基于类实例, +你可以直接 mock,但需注意以下限制: + * This won't work if the plugin uses non-class functions or static methods. * Tests will need to be updated when the plugin API changes. + 如果插件使用 + 非类函数或静态方法,此方法无效。 +* 插件 API 变更时 + 需要更新测试。 + ## Mock the plugin's platform interface +## Mock 插件的平台接口 + If the plugin is a [federated plugin][], it will include a platform interface that allows registering implementations of its internal logic. @@ -93,6 +158,12 @@ You can register a mock of that platform interface implementation instead of the public API with the following caveats: +如果插件是[联合插件][federated plugin], +它会包含一个平台接口,允许 +注册其内部逻辑的实现。 +你可以注册该平台接口实现的 mock, +而不是公共 API,但需注意以下限制: + * This won't work if the plugin isn't federated. * Your tests will include part of the plugin's code, so plugin behavior could cause problems for your tests. @@ -101,6 +172,14 @@ following caveats: based on whether you had run the test previously. * Tests might need to be updated when the platform interface changes. + 如果插件不是联合插件,此方法无效。 +* 你的测试将包含插件的部分代码, + 因此插件行为可能给你的测试带来问题。 + 例如,如果插件将文件写入 + 内部缓存,测试行为可能取决于 + 你是否曾运行过该测试。 +* 平台接口变更时可能需要更新测试。 + An example of when this might be necessary is mocking the implementation of a plugin used by a package that you rely on, @@ -109,10 +188,19 @@ so you can't change how it's called. However, if possible, you should mock the dependency that uses the plugin instead. +可能需要这样做的一个例子是 +mock 你所依赖的包使用的插件实现, +而不是你自己的代码, +因此你无法更改其调用方式。 +不过,如果可能, +你应改为 mock 使用该插件的依赖。 + [federated plugin]: /packages-and-plugins/developing-packages#federated-plugins ## Mock the platform channel +## Mock 平台通道 + If the plugin uses [platform channels][], you can mock the platform channels using [`TestDefaultBinaryMessenger`][]. @@ -120,6 +208,13 @@ This should only be used if, for some reason, none of the methods above are available, as it has several drawbacks: +如果插件使用[平台通道][platform channels], +你可以使用 +[`TestDefaultBinaryMessenger`][] mock 平台通道。 +仅当出于某种原因 +上述方法都不可用时才应使用, +因为它有几个缺点: + * Only implementations that use platform channels can be mocked. This means that if some implementations don't use platform channels, @@ -140,14 +235,41 @@ as it has several drawbacks: and you have to read the plugin's implementation to know what the key strings and value types are. + 只有使用平台通道的实现 + 才能被 mock。这意味着如果某些实现 + 不使用平台通道, + 在某些平台上运行时测试会意外使用 + 真实实现。 +* 平台通道通常是插件的内部实现 + 细节。 + 即使在插件的 bug 修复更新中 + 也可能大幅变更, + 导致测试意外失败。 +* 联合插件的每种实现中 + 平台通道可能不同。例如, + 你可能设置 mock 平台通道使 + 测试在 Windows 机器上通过, + 却发现若在 macOS 或 Linux 上运行会失败。 +* 平台通道不是强类型的。 + 例如,method channel 常使用字典, + 你必须阅读插件实现 + 才能知道键字符串和值类型。 + Because of these limitations, `TestDefaultBinaryMessenger` is mainly useful in the internal tests of plugin implementations, rather than tests of code using plugins. +由于这些限制,`TestDefaultBinaryMessenger` +主要用于插件实现的内部测试, +而不是使用插件的代码的测试。 + You might also want to check out [Testing plugins][]. +你也可以参阅 +[测试插件][Testing plugins]。 + [platform channels]: /platform-integration/platform-channels [`TestDefaultBinaryMessenger`]: {{site.api}}/flutter/flutter_test/TestDefaultBinaryMessenger-class.html [Testing plugins]: /testing/testing-plugins diff --git a/sites/docs/src/content/testing/testing-plugins.md b/sites/docs/src/content/testing/testing-plugins.md index f41144d1dd..ed3a836a40 100644 --- a/sites/docs/src/content/testing/testing-plugins.md +++ b/sites/docs/src/content/testing/testing-plugins.md @@ -1,6 +1,9 @@ --- -title: Testing plugins -description: Learn how to test your plugin package. +# title: Testing plugins +title: 测试插件 +# description: Learn how to test your plugin package. +description: 了解如何测试你的插件包。 +ai-translated: true --- All of the [usual types of Flutter tests][] apply to @@ -8,6 +11,11 @@ plugin packages as well, but because plugins contain native code they often also require other kinds of tests to test all of their functionality. +所有[常见的 Flutter 测试类型][usual types of Flutter tests]也适用于 +插件包,但由于插件包含 +原生代码,通常还需要其他类型的测试 +来测试其全部功能。 + [usual types of Flutter tests]: /testing/overview :::note @@ -15,24 +23,42 @@ To learn how to test your plugin code, read on. To learn how to avoid crashes from a plugin when testing your Flutter app, check out [Plugins in Flutter tests][]. + +要了解如何测试插件代码,请继续阅读。 +要了解在测试 Flutter 应用时如何避免插件导致的崩溃,请参阅 +[Flutter 测试中的插件][Plugins in Flutter tests]。 ::: [Plugins in Flutter tests]: /testing/plugins-in-tests ## Types of plugin tests +## 插件测试类型 + To see examples of each of these types of tests, you can [create a new plugin from the plugin template][plugin-tests] and look in the indicated directories. +要查看每类测试的示例,你可以 +[从插件模板创建新插件][plugin-tests] +并查看指定目录。 + * Dart [unit tests][] and [widget tests][]. These tests allow you to test the Dart portion of your plugin just as you would test the Dart code of a non-plugin package. However, the plugin's native code [won't be loaded][], so any calls to platform channels need to be [mocked in tests][]. + Dart [单元测试][unit tests] 和 [widget 测试][widget tests]。 + 这些测试让你测试插件的 Dart 部分, + 就像测试非插件包的 Dart 代码一样。 + 不过,插件的原生代码[不会被加载][won't be loaded], + 因此对平台通道的任何调用都需要在测试中[mock][mocked in tests]。 + See the `test` directory for an example. + 请参阅 `test` 目录中的示例。 + * Dart [integration tests][]. Since integration tests run in the context of a Flutter application (the example app), @@ -41,14 +67,30 @@ and look in the indicated directories. They are also useful for unit testing web implementation code that needs to run in a browser. + Dart [集成测试][integration tests]。 + 由于集成测试在 + Flutter 应用(示例应用)的上下文中运行, + 它们可以测试 Dart 与原生代码, + 以及两者之间的交互。 + 它们也适用于对需要在浏览器中运行的 Web 实现 + 代码进行单元测试。 + These are often the most important tests for a plugin. However, Dart integration tests using `integration_test` can't interact with native UI, such as native dialogs or the contents of platform views. For native component interaction support, consider using [`patrol`][] + 这些通常是插件最重要的测试。 + 不过,使用 `integration_test` 的 Dart 集成测试无法 + 与原生 UI 交互,例如原生对话框或 + platform view 的内容。如需原生组件交互支持, + 可考虑使用 [`patrol`][] + See the `example/integration_test` directory for an example. + 请参阅 `example/integration_test` 目录中的示例。 + * Native unit tests. Just as Dart unit tests can test the Dart portions of a plugin in isolation, native unit tests can @@ -57,17 +99,34 @@ and look in the indicated directories. and the tests are written in the same native languages as the code it is testing. + 原生单元测试。 + 正如 Dart 单元测试可以隔离测试插件的 Dart 部分, + 原生单元测试可以 + 隔离测试原生部分。 + 每个平台都有自己的原生单元测试系统, + 测试使用与被测代码相同的原生语言编写。 + Native unit tests can be especially valuable if you need to mock out APIs wrapped by your plugin code, which isn't possible in a Dart integration test. + 如果你需要 mock 插件代码封装的 API, + 这在 Dart 集成测试中无法实现, + 原生单元测试尤其有价值。 + You can set up and use any native test frameworks you are familiar with for each platform, but the following are already configured in the plugin template: + 你可以为每个平台设置并使用你熟悉的任何原生测试框架, + 但以下已在插件模板中配置: + * Android: [JUnit][] tests can be found in `android/src/test/`. + Android: + [JUnit][] 测试位于 `android/src/test/`。 + * iOS and macOS: [XCTest][] tests can be found in `example/ios/RunnerTests/` and `example/macos/RunnerTests/` respectively. @@ -75,10 +134,21 @@ and look in the indicated directories. not the top-level package directory, because they are run via the example app's project. + iOSmacOS: + [XCTest][] 测试分别位于 `example/ios/RunnerTests/` + 和 `example/macos/RunnerTests/`。 + 这些位于示例目录, + 而非顶层包目录, + 因为它们通过示例应用的项目运行。 + * Linux and Windows: [GoogleTest][] tests can be found in `linux/test/` and `windows/test/`, respectively. + LinuxWindows: + [GoogleTest][] 测试分别位于 `linux/test/` + 和 `windows/test/`。 + Other types of tests, which aren't currently pre-configured in the template, are native UI tests. Running your application under a native UI testing framework, @@ -87,6 +157,13 @@ enables tests that interact with both native and Flutter UI elements, so can be useful if your plugin can't be tested without native UI interactions. +模板中当前未预配置的其他测试类型是 原生 UI 测试。 +在原生 UI 测试框架下运行应用, +例如 [Espresso][] 或 [XCUITest][], +可实现与原生和 Flutter UI 元素交互的测试, +因此若插件无法在没有 +原生 UI 交互的情况下测试,这会很有用。 + [Espresso]: {{site.repo.packages}}/tree/main/packages/espresso [GoogleTest]: {{site.github}}/google/googletest [integration tests]: /cookbook/testing/integration/introduction @@ -102,49 +179,88 @@ native UI interactions. ## Running tests +## 运行测试 + ### Dart unit tests +### Dart 单元测试 + These can be run like any other Flutter unit tests, either from your preferred Flutter IDE, or using `flutter test`. +这些可以像任何其他 Flutter 单元测试一样运行, +在你首选的 Flutter IDE 中, +或使用 `flutter test`。 + ### Integration tests +### 集成测试 + For information on running this type of test, check out the [integration test documentation][]. The commands must be run in the `example` directory. +有关运行此类测试的信息,请参阅 +[集成测试文档][integration test documentation]。 +命令必须在 `example` 目录中运行。 + [integration test documentation]: /cookbook/testing/integration/introduction ### Native unit tests +### 原生单元测试 + For all platforms, you need to build the example application at least once before running the unit tests, to ensure that all of the platform-specific build files have been created. +对于所有平台,你需要在运行单元测试前 +至少构建一次示例 +应用,以确保所有平台特定的构建 +文件已创建。 + +Android JUnit
+ Android JUnit
If you have the example opened as an Android project in Android Studio, you can run the unit tests using the [Android Studio test UI][]. +如果你在 Android Studio 中将示例作为 Android 项目打开, +可以使用 +[Android Studio 测试 UI][Android Studio test UI] 运行单元测试。 + To run the tests from the command line, use the following command in the `example/android` directory: +要从命令行运行测试, +在 `example/android` 目录中使用以下命令: + ```sh ./gradlew testDebugUnitTest ``` iOS and macOS XCTest
+iOS 与 macOS XCTest
+ If you have the example app opened in Xcode, you can run the unit tests using the [Xcode Test UI][]. +如果你在 Xcode 中打开了示例应用, +可以使用 [Xcode 测试 UI][Xcode Test UI] 运行单元测试。 + To run the tests from the command line, use the following command in the `example/ios` (for iOS) or `example/macos` (for macOS) directory: +要从命令行运行测试, +在 `example/ios`(iOS)或 +`example/macos`(macOS)目录中使用以下命令: + ```sh xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug ``` @@ -152,12 +268,21 @@ xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debu For iOS tests, you might need to first open `Runner.xcworkspace` in Xcode to configure code signing. +对于 iOS 测试,你可能需要先在 Xcode 中打开 +`Runner.xcworkspace` 以配置代码签名。 + +Linux GoogleTest
+ Linux GoogleTest
To run the tests from the command line, use the following command in the example directory, replacing "my_plugin" with your plugin project name: +要从命令行运行测试, +在示例目录中使用以下命令, +将 "my_plugin" 替换为你的插件项目名称: + ```sh build/linux/plugins/x64/debug/my_plugin/my_plugin_test ``` @@ -165,15 +290,27 @@ build/linux/plugins/x64/debug/my_plugin/my_plugin_test If you built the example app in release mode rather than debug, replace "debug" with "release". +如果你以 release 模式而非 +debug 模式构建示例应用,将 "debug" 替换为 "release"。 + +Windows GoogleTest
+ Windows GoogleTest
If you have the example app opened in Visual Studio, you can run the unit tests using the [Visual Studio test UI][]. +如果你在 Visual Studio 中打开了示例应用, +可以使用 [Visual Studio 测试 UI][Visual Studio test UI] 运行单元测试。 + To run the tests from the command line, use the following command in the example directory, replacing "my_plugin" with your plugin project name: +要从命令行运行测试, +在示例目录中使用以下命令, +将 "my_plugin" 替换为你的插件项目名称: + ```sh build/windows/plugins/my_plugin/Debug/my_plugin_test.exe ``` @@ -181,30 +318,56 @@ build/windows/plugins/my_plugin/Debug/my_plugin_test.exe If you built the example app in release mode rather than debug, replace "Debug" with "Release". +如果你以 release 模式而非 +debug 模式构建示例应用,将 "Debug" 替换为 "Release"。 + ## What types of tests to add +## 应添加哪些类型的测试 + The [general advice for testing Flutter projects][general advice] applies to plugins as well. Some extra considerations for plugin testing: +[测试 Flutter 项目的一般建议][general advice] +也适用于插件。 +插件测试的一些额外考虑: + * Since only integration tests can test the communication between Dart and the native languages, try to have at least one integration test of each platform channel call. + 由于只有集成测试可以测试 Dart 与 + 原生语言之间的通信, + 请尽量为每个 + 平台通道调用至少编写一个集成测试。 + * If some flows can't be tested using the `integration_test` package (for example if they require interacting with native UI or mocking device state), consider using the [`patrol`][] package for native UI interactions, or writing "end to end" tests of the two halves using unit tests: + 如果某些流程无法使用 `integration_test` 包测试 + (例如需要与原生 UI 交互或 mock + 设备状态),可考虑使用 [`patrol`][] 包进行 + 原生 UI 交互,或使用单元测试为两半编写「端到端」测试: + * Native unit tests that set up the necessary mocks, then call into the method channel entry point with a synthesized call and validate the method response. + 设置必要 mock 的原生单元测试, + 然后通过合成的调用调用 method channel 入口点 + 并验证方法响应。 + * Dart unit tests that mock the platform channel, then call the plugin's public API and validate the results. + mock 平台通道的 Dart 单元测试, + 然后调用插件的公共 API 并验证结果。 + [Android Studio test UI]: {{site.android-dev}}/studio/test/test-in-android-studio [general advice]: /testing/overview [Visual Studio test UI]: https://learn.microsoft.com/en-us/visualstudio/test/getting-started-with-unit-testing?view=vs-2022&tabs=dotnet%2Cmstest#run-unit-tests diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_hot-reload.md b/sites/docs/src/content/testing/vscode-flutter-bar/_hot-reload.md index 33786b5e6e..1abbabc822 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_hot-reload.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_hot-reload.md @@ -1 +1,3 @@ ![Small yellow lightning bolt that indicates reloading the UI of a Flutter app without resetting any state values](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-reload.png) + +![小型黄色闪电图标,表示在不重置任何状态值的情况下重载 Flutter 应用的 UI](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-reload.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_hot-restart.md b/sites/docs/src/content/testing/vscode-flutter-bar/_hot-restart.md index ece267dd14..3ee9f29a37 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_hot-restart.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_hot-restart.md @@ -1 +1,3 @@ ![Small green almost circular arrow that indicates reloading the UI of a Flutter app and resetting any state values](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-restart.png) + +![小型绿色近似圆形箭头图标,表示重载 Flutter 应用的 UI 并重置任何状态值](/assets/images/docs/testing/debugging/vscode-ui/icons/hot-restart.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_inspector.md b/sites/docs/src/content/testing/vscode-flutter-bar/_inspector.md index 7a44518fc2..40675c1e1a 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_inspector.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_inspector.md @@ -1 +1,3 @@ ![Small blue magnifying class with the Flutter logo inside it that opens the Widget inspector](/assets/images/docs/testing/debugging/vscode-ui/icons/inspector.png) + +![小型蓝色放大镜(内含 Flutter 标志)图标,用于打开 Widget Inspector](/assets/images/docs/testing/debugging/vscode-ui/icons/inspector.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_pause.md b/sites/docs/src/content/testing/vscode-flutter-bar/_pause.md index f4e51c7a59..fecded2dc4 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_pause.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_pause.md @@ -1 +1,3 @@ ![Small blue double vertical line that indicates pausing the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/pause.png) + +![小型蓝色双竖线图标,表示暂停 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/pause.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_play.md b/sites/docs/src/content/testing/vscode-flutter-bar/_play.md index f9489ceeb7..25d1ad312f 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_play.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_play.md @@ -1 +1,3 @@ ![Small blue vertical line with a blue triangle that indicates playing or resuming the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/play-or-resume.png) + +![小型蓝色竖线与蓝色三角图标,表示播放或恢复 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/play-or-resume.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_step-into.md b/sites/docs/src/content/testing/vscode-flutter-bar/_step-into.md index 1dcd598d19..bf1f559e2a 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_step-into.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_step-into.md @@ -1 +1,3 @@ ![Small blue downward arrow over a blue circle that indicates going into the next function in a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-into.png) + +![小型蓝色向下箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中进入下一个函数](/assets/images/docs/testing/debugging/vscode-ui/icons/step-into.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_step-out.md b/sites/docs/src/content/testing/vscode-flutter-bar/_step-out.md index 827bd97a26..9de08f9912 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_step-out.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_step-out.md @@ -1 +1,3 @@ ![Small blue upward arrow over a blue circle that indicates exiting the current function after one passthrough in a Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-out.png) + +![小型蓝色向上箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中单次执行后退出当前函数](/assets/images/docs/testing/debugging/vscode-ui/icons/step-out.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_step-over.md b/sites/docs/src/content/testing/vscode-flutter-bar/_step-over.md index b0ac5b16b4..f206364c07 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_step-over.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_step-over.md @@ -1 +1,3 @@ ![Small blue arched arrow over a blue circle that indicates skipping the current block or statement in the Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/step-over.png) + +![小型蓝色拱形箭头覆盖蓝色圆圈图标,表示在 Flutter 应用中跳过当前块或语句](/assets/images/docs/testing/debugging/vscode-ui/icons/step-over.png) diff --git a/sites/docs/src/content/testing/vscode-flutter-bar/_stop.md b/sites/docs/src/content/testing/vscode-flutter-bar/_stop.md index 13e43a9b37..db7f473f38 100644 --- a/sites/docs/src/content/testing/vscode-flutter-bar/_stop.md +++ b/sites/docs/src/content/testing/vscode-flutter-bar/_stop.md @@ -1 +1,3 @@ ![Red empty square that indicates you want to stop the running Flutter app](/assets/images/docs/testing/debugging/vscode-ui/icons/stop.png) + +![红色空心方块图标,表示你要停止正在运行的 Flutter 应用](/assets/images/docs/testing/debugging/vscode-ui/icons/stop.png) diff --git a/sites/docs/src/content/tools/android-studio.md b/sites/docs/src/content/tools/android-studio.md index 43b15bdbe1..3e93d256a1 100644 --- a/sites/docs/src/content/tools/android-studio.md +++ b/sites/docs/src/content/tools/android-studio.md @@ -7,6 +7,7 @@ title: 在 Android Studio 或 IntelliJ 里开发 Flutter 应用 description: 如何在 Android Studio 或者其他类 IntelliJ 产品里开发 Flutter 应用。 tags: SDK,Flutter SDK keywords: Android Studio,IntelliJ,安装,检查更新,创建新项目 +ai-translated: true ---
@@ -45,9 +64,13 @@ deep integrations with the [Dart analyzer][] and [Dart and Flutter DevTools][]. ## Online editors +## 在线编辑器 + You can quickly try Flutter online without any local setup with one of the following editors. +你可以使用以下在线编辑器快速体验 Flutter,无需本地配置。 + ## Other editors +## 其他编辑器 + You can develop Dart and Flutter apps using any other text editor and terminal. +你可以使用任意其他文本编辑器和终端开发 Dart 与 Flutter 应用。 + Depending on the editor, you can integrate the Dart SDK's support for the [Language Server Protocol][lsp] and the [Debug Adapter Protocol][dap] to enable rich code editing and debugging features for both Dart and Flutter. +根据编辑器不同,你可以集成 Dart SDK 对 +[Language Server Protocol][lsp](语言服务器协议)和 [Debug Adapter Protocol][dap](调试适配器协议)的支持, +为 Dart 与 Flutter 启用丰富的代码编辑和调试功能。 + [lsp]: https://github.com/dart-lang/sdk/tree/main/pkg/analysis_server/tool/lsp_spec/README.md [dap]: https://github.com/dart-lang/sdk/blob/main/third_party/pkg/dap/tool/README.md diff --git a/sites/docs/src/content/tools/property-editor.md b/sites/docs/src/content/tools/property-editor.md index 2a24b91f43..09903597d9 100644 --- a/sites/docs/src/content/tools/property-editor.md +++ b/sites/docs/src/content/tools/property-editor.md @@ -1,38 +1,61 @@ --- -title: Flutter Property Editor -description: Learn how to use the Flutter Property Editor to view and modify the properties of your widgets. +# title: Flutter Property Editor +title: Flutter 属性编辑器 +# description: Learn how to use the Flutter Property Editor to view and modify the properties of your widgets. +description: 学习如何使用 Flutter 属性编辑器查看和修改 widget 的属性。 +ai-translated: true --- :::note The Flutter Property Editor requires Flutter version 3.32 or higher. + +Flutter 属性编辑器需要 Flutter 3.32 或更高版本。 ::: ## What is it? +## 这是什么? + The Flutter Property Editor is a powerful IDE-tool that lets you view and modify widget properties directly from a visual interface. +Flutter 属性编辑器是一款强大的 IDE 工具,让你通过可视化界面直接查看和修改 widget 属性。 + It allows you to quickly discover and modify your widgets' existing and available constructor arguments, eliminating the need to jump-to-definition or manually edit the source code. Furthermore, its integration with the Flutter inspector and hot reload enables you to view changes in real time, speeding up UI development and iteration. +你可以快速发现并修改 widget 现有及可用的构造函数参数, +无需跳转到定义或手动编辑源码。此外,它与 Flutter 检查器和热重载集成, +可实时查看变更,加快 UI 开发与迭代。 + ![Flutter Property Editor](/assets/images/docs/tools/devtools/property-editor-text-widget.png){:width="500px"} ## How to access the Flutter Property Editor +## 如何访问 Flutter 属性编辑器 + 1. Open the Flutter Property Editor in your supported IDE ([VS Code][], [Android Studio/IntelliJ][]). + 在支持的 IDE([VS Code][]、[Android Studio/IntelliJ][])中打开 Flutter 属性编辑器。 + 2. Locate a [widget constructor invocation][] in your Flutter code. + 在 Flutter 代码中定位 [widget constructor invocation][](widget 构造函数调用)。 + 3. Move your cursor anywhere inside the widget constructor invocation. + 将光标移到 widget 构造函数调用内的任意位置。 + For example, in the following `build` method, place your cursor anywhere between the `T` of `Text` and the ending parenthesis `)` after `TextOverflow.clip`: + 例如,在以下 `build` 方法中,将光标放在 `Text` 的 `T` 与 `TextOverflow.clip` 之后右括号 `)` 之间的任意位置: + ```dart @override Widget build(BuildContext context) { @@ -46,36 +69,63 @@ UI development and iteration. 4. The Flutter Property Editor panel automatically updates to display the properties of the widget at your cursor location. + Flutter 属性编辑器面板会自动更新,显示光标处 widget 的属性。 + [VS Code]: /tools/vs-code#property-editor [Android Studio/IntelliJ]: /tools/android-studio#property-editor [widget constructor invocation]: /learn/pathway/tutorial/widget-fundamentals ### Runtime usage +### 运行时使用 + #### Integration with the Flutter inspector +#### 与 Flutter 检查器集成 + The Flutter Property Editor can be used in conjunction with the [Flutter inspector][] to inspect your widgets simultaneously in both tools. +Flutter 属性编辑器可与 [Flutter inspector][](Flutter 检查器)配合使用,在两个工具中同时检查 widget。 + 1. From your preferred IDE, run and debug your Flutter application. * [VS Code instructions][] * [Android Studio/IntelliJ instructions][] + 在你偏好的 IDE 中运行并调试 Flutter 应用。 + * [VS Code instructions][](VS Code 说明) + * [Android Studio/IntelliJ instructions][](Android Studio/IntelliJ 说明) + 2. Open the [Flutter inspector][] in your IDE. + 在 IDE 中打开 [Flutter inspector][]。 + You can then use the Flutter inspector to load a widget in the Flutter Property Editor by either: +然后你可以通过以下任一方式,用 Flutter 检查器在 Flutter 属性编辑器中加载 widget: + 1. Selecting a widget in tree: * Click on a widget in the [inspector's widget tree][]. + 在树中选择 widget: + * 在 [inspector's widget tree][](检查器的 widget 树)中点击 widget。 + 2. Selecting a widget in your app: * Enable ["Select Widget Mode"][] in the inspector. * Click on a widget in your running application. + 在应用中选择 widget: + * 在检查器中启用 ["Select Widget Mode"][](选择 widget 模式)。 + * 在正在运行的应用中点击 widget。 + Both actions will automatically: - Jump to the widget's declaration in your source code. - Load the selected widget in the Flutter Property Editor. +两种操作都会自动: +- 跳转到源码中该 widget 的声明。 +- 在 Flutter 属性编辑器中加载所选 widget。 + [VS Code instructions]: /tools/devtools/vscode/#run-and-debug [Android Studio/IntelliJ instructions]: /tools/devtools/android-studio/#run-and-debug [Flutter inspector]: /tools/devtools/inspector @@ -84,15 +134,23 @@ Both actions will automatically: #### Integration with hot reload +#### 与热重载集成 + The Flutter Property Editor can be used in conjunction with hot reload to view changes in real time. +Flutter 属性编辑器可与热重载配合,实时查看变更。 + 1. From your preferred IDE, enable autosave and hot reloads on save. + 在你偏好的 IDE 中启用自动保存与保存时热重载。 + **VS Code** Add the following to your `.vscode/settings.json` file: + 将以下内容添加到你的 `.vscode/settings.json` 文件: + ```json "files.autoSave": "afterDelay", "dart.flutterHotReloadOnSave": "all", @@ -105,33 +163,59 @@ with hot reload to view changes in real time. - Check the option to `Save files if the IDE is idle for X seconds`. - **Recommended:** Set a small delay duration. For example, 2 seconds. + * 打开 `Settings > Tools > Actions on Save`(设置 > 工具 > 保存时操作),选择 `Configure autosave options`(配置自动保存选项)。 + - 勾选 `Save files if the IDE is idle for X seconds`(IDE 空闲 X 秒后保存文件)。 + - **Recommended:**(推荐:)设置较短延迟,例如 2 秒。 + * Open `Settings > Languages & Frameworks > Flutter`. - Check the option to `Perform hot reload on save`. + * 打开 `Settings > Languages & Frameworks > Flutter`(设置 > 语言与框架 > Flutter)。 + - 勾选 `Perform hot reload on save`(保存时执行热重载)。 + 2. Run and debug your Flutter application. * [VS Code instructions][] * [Android Studio/IntelliJ instructions][] + 运行并调试 Flutter 应用。 + * [VS Code instructions][] + * [Android Studio/IntelliJ instructions][] + 3. Any changes you make from the Flutter Property Editor are automatically reflected in your running app. + 你在 Flutter 属性编辑器中所做的更改会自动反映到正在运行的应用中。 + ## Feature set +## 功能集 + The Flutter Property Editor comes equipped with several features designed to speed up the development process. +Flutter 属性编辑器配备多项功能,旨在加快开发流程。 + ### Viewing widget documentation +### 查看 widget 文档 + When a widget is selected in the Flutter Property Editor, its documentation is displayed at the top. This allows you to quickly read the widget documentation, without needing to jump-to-definition or search online. +在 Flutter 属性编辑器中选中 widget 时,其文档会显示在顶部。 +你可以快速阅读 widget 文档,无需跳转到定义或在线搜索。 + By default, the widget documentation is truncated. Click on "Show more" to expand the widget documentation. +默认情况下 widget 文档会被截断。点击 "Show more"(显示更多)可展开文档。 + :::tip To see the documentation for your app's custom widgets in the Flutter Property Editor, make sure to follow the [Dart style guide][]. + +要在 Flutter 属性编辑器中查看应用自定义 widget 的文档,请遵循 [Dart style guide][](Dart 风格指南)。 ::: ![Flutter Property Editor gif displaying the documentation for a Text widget](/assets/images/docs/tools/devtools/property-editor-documentation.gif) @@ -140,36 +224,59 @@ Editor, make sure to follow the [Dart style guide][]. ### Editing widget properties +### 编辑 widget 属性 + The Flutter Property Editor contains input fields tailored to the type of each constructor argument. +Flutter 属性编辑器为每个构造函数参数的类型提供对应的输入字段。 + - **string, double, and int properties:** * These are represented by text input fields. * Simply type the new value into the field. * Press ••Tab•• or ••Enter•• to apply the edit directly to your source code. +- **string、double 和 int 属性:** + * 以文本输入框表示。 + * 在字段中输入新值即可。 + * 按 ••Tab•• 或 ••Enter•• 将编辑直接应用到源码。 + - **boolean and enum properties:** * These are represented by dropdown menus. * Click the dropdown to see the available options (`true`/`false` for booleans, or the various enum values). * Select the desired value from the list to apply it to your code. +- **boolean 和 enum 属性:** + * 以下拉菜单表示。 + * 点击下拉菜单查看可用选项(布尔值为 `true`/`false`,或各 enum 值)。 + * 从列表中选择所需值以应用到代码。 + - **object properties (for example, `TextStyle`, `EdgeInsets`, `Color`):** * Currently not supported. The Flutter Property Editor does not yet allow direct editing of complex object properties. You will need to edit these directly in your source code. +- **object 属性(例如 `TextStyle`、`EdgeInsets`、`Color`):** + * 目前不支持。Flutter 属性编辑器尚不允许直接编辑复杂对象属性,需在源码中直接编辑。 + ### Understanding the property inputs +### 理解属性输入 + Each property input in the Flutter Property Editor is accompanied by information to help you understand its usage. +Flutter 属性编辑器中每个属性输入都附带信息,帮助你理解其用法。 + - **Type and name:** The **type** (for example, `StackFit`) and the **name** (for example, `fit`) of the constructor parameter are displayed as a label for each input field. ![Type and name label for a property input](/assets/images/docs/tools/devtools/property-editor-name-type.png){:width="500px"} + **类型和名称:** 每个输入字段的标签会显示构造函数参数的 **type**(类型,例如 `StackFit`)和 **name**(名称,例如 `fit`)。 + - **Info tooltip (ⓘ):** * Hovering over the info icon next to a property input displays a tooltip. * The information in the tooltip includes: @@ -178,6 +285,12 @@ to help you understand its usage. ![Info tooltip for a property input](/assets/images/docs/tools/devtools/property-editor-tooltip.png){:width="600px"} + **信息工具提示 (ⓘ):** + * 将指针悬停在属性输入旁的信息图标上会显示工具提示。 + * 工具提示信息包括: + * 若 widget 构造函数中定义了默认值,则显示该属性的默认值。 + * 该属性的任何文档。 + * **"Set" and "default" labels:** * The **"set"** label appears next to an input if the property has been explicitly set in your source code. This means there is a corresponding @@ -195,11 +308,25 @@ to help you understand its usage. !["Set" and "default" labels for a property input](/assets/images/docs/tools/devtools/property-editor-labels.png){:width="500px"} +* **"Set" 和 "default" 标签:** + * 若属性已在源码中显式设置,输入旁会显示 **"set"** 标签,表示 widget 构造函数调用中提供了对应参数。 + * 若当前属性值与 widget 中定义的默认参数值一致,输入旁会显示 **"default"** 标签。 + + :::tip + 若属性输入同时带有 "set" 和 "default" 标签,表示你在代码中显式提供了值,但该值与 widget 对该属性的默认值相同。此时可以安全地从代码中移除此参数以简化代码,widget 仍会使用默认值。 + ::: + + !["Set" and "default" labels for a property input](/assets/images/docs/tools/devtools/property-editor-labels.png){:width="500px"} + ### Filtering properties +### 筛选属性 + For widgets with many properties, the filter bar can help to quickly locate properties of interest. +对于属性较多的 widget,筛选栏可帮助快速定位感兴趣的属性。 + * **Filter by text:** * Simply type into the filter bar. The list of properties will dynamically update to show only those that match your input. @@ -213,6 +340,13 @@ properties of interest. ![Filter input with filtering by text highlighted](/assets/images/docs/tools/devtools/property-editor-filter-text.png){:width="500px"} + **按文本筛选:** + * 在筛选栏中输入即可。属性列表会动态更新,仅显示匹配项。 + * 可按属性名称、当前值或类型筛选。例如: + * 输入 "main" 会筛选到 `mainAxisAlignment`、`mainAxisSize` 或名称含 "main" 的其他属性。 + * 输入 "true" 会筛选到当前设为 `true` 的所有布尔属性。 + * 输入 "double" 会筛选到所有 `double` 类型属性。 + * **Filter by "set" properties:** * Use the filter menu button to open the filter options. Check "Only include properties that are set in the code." @@ -221,6 +355,10 @@ properties of interest. ![Filter input with filter menu button highlighted](/assets/images/docs/tools/devtools/property-editor-filter-menu-button.png){:width="500px"} + **按「已设置」属性筛选:** + * 使用筛选菜单按钮打开筛选选项,勾选 "Only include properties that are set in the code."(仅包含代码中已设置的属性)。 + * 这会隐藏代码中未显式设置的所有属性,让你只关注已显式设置的属性。 + * **Filter with a regex:** * The regex toggle (an `*` icon button) allows you to toggle on regex mode for the filter input. @@ -229,8 +367,15 @@ properties of interest. ![Filter input with regex toggle highlighted](/assets/images/docs/tools/devtools/property-editor-filter-regex-toggle.png){:width="500px"} + **使用正则表达式筛选:** + * 正则切换按钮(`*` 图标按钮)可为筛选输入开启正则模式。 + * 启用后,筛选文本会按正则表达式解析。 + * **Clear the current filter:** * The clear button (an `X` icon button) clears out any active filters, displaying all properties of the widget again. ![Filter input with clear button highlighted](/assets/images/docs/tools/devtools/property-editor-filter-clear-button.png){:width="500px"} + + **清除当前筛选:** + * 清除按钮(`X` 图标按钮)会清除所有活动筛选,再次显示 widget 的全部属性。 diff --git a/sites/docs/src/content/tools/sdk.md b/sites/docs/src/content/tools/sdk.md index e879ee3357..918d50c2b6 100644 --- a/sites/docs/src/content/tools/sdk.md +++ b/sites/docs/src/content/tools/sdk.md @@ -4,6 +4,7 @@ title: Flutter SDK 概览 shortTitle: Flutter SDK # description: Flutter libraries and command-line tools. description: Flutter 库和命令行工具。 +ai-translated: true --- The Flutter SDK has the packages and command-line tools that you need to develop @@ -32,7 +33,7 @@ The following is available through the Flutter SDK: * Rich set of widgets implementing Material Design and iOS styles - Material Design 风格及 iOS 风格丰富的 widget 组件 + Material Design 风格及 iOS 风格丰富的 widget * APIs for unit and integration tests diff --git a/sites/docs/src/content/tools/widget-previewer.md b/sites/docs/src/content/tools/widget-previewer.md index 2168fce315..4afc9ae4bb 100644 --- a/sites/docs/src/content/tools/widget-previewer.md +++ b/sites/docs/src/content/tools/widget-previewer.md @@ -1,20 +1,33 @@ --- -title: Flutter Widget Previewer +# title: Flutter Widget Previewer +title: Flutter Widget 预览器 +# description: >- +# Learn how to use the Flutter Widget Previewer to see your +# widgets render in real-time, separate from your full app. description: >- - Learn how to use the Flutter Widget Previewer to see your - widgets render in real-time, separate from your full app. + 学习如何使用 Flutter Widget 预览器, + 在全应用之外实时查看 widget 渲染效果。 +ai-translated: true --- In this guide, you will learn how to use the Flutter Widget Previewer. +本指南将介绍如何使用 Flutter Widget 预览器。 + ## Overview +## 概览 + With the Flutter Widget Previewer, you can see your widgets render in real-time, separate from a full app, in the Chrome browser. To start the previewer, show a widget in it, and customize a preview, see the following sections. +借助 Flutter Widget 预览器,你可以在 Chrome 浏览器中 +在全应用之外实时查看 widget 渲染。 +要启动预览器、在其中展示 widget 并自定义预览,请参阅以下各节。 + :::version-note The Flutter Widget Preview requires Flutter version 3.35 or higher. IDE support requires Flutter version 3.38 or higher. @@ -26,47 +39,80 @@ early access version, and you should expect future updates to introduce breaking changes. ::: +:::version-note +Flutter Widget Preview 需要 Flutter 3.35 或更高版本。IDE 支持需要 Flutter 3.38 或更高版本。 + +请注意,这是 Flutter stable 渠道中的 **experimental feature**(实验性功能)。 +API 尚不稳定,_将会变更_。本指南针对当前早期访问版本,未来更新可能引入破坏性变更。 +::: + ## Opening the previewer +## 打开预览器 + ### IDEs +### IDE + As of Flutter 3.38, Android Studio, Intellij, and Visual Studio Code automatically start the Flutter Widget Previewer on launch. +自 Flutter 3.38 起,Android Studio、Intellij 和 Visual Studio Code +在启动时会自动启动 Flutter Widget 预览器。 + #### Android Studio and Intellij +#### Android Studio 与 Intellij + To open the Widget Previewer in Android Studio or Intellij, open the "Flutter Widget Preview" tab in the sidebar: +在 Android Studio 或 Intellij 中打开 Widget 预览器:在侧边栏打开 "Flutter Widget Preview" 标签页: + ![Flutter Widget Previewer in Android Studio](/assets/images/docs/tools/widget-previewer/android-studio.png "Android Studio") #### Visual Studio Code +#### Visual Studio Code + To open the Widget Previewer in Visual Studio Code, open the "Flutter Widget Preview" tab in the sidebar: +在 Visual Studio Code 中打开 Widget 预览器:在侧边栏打开 "Flutter Widget Preview" 标签页: + ![Flutter Widget Previewer in Visual Studio Code](/assets/images/docs/tools/widget-previewer/vscode.png "Visual Studio Code") ### Command line +### 命令行 + To start the Flutter Widget Previewer, navigate to your Flutter project's root directory and run the following command in your terminal. This will launch a local server and open a Widget Preview environment in Chrome that automatically updates based on changes to your project. +要启动 Flutter Widget 预览器,请进入 Flutter 项目根目录, +在终端运行以下命令。这会启动本地服务器, +并在 Chrome 中打开会根据项目变更自动更新的 Widget Preview 环境。 + ```shell flutter widget-preview start ``` ## Preview a widget +## 预览 widget + After you've started the previewer, to look at a widget, you must use the [`@Preview`][] annotation defined in `package:flutter/widget_previews.dart`. This annotation can be applied to: +启动预览器后,要查看 widget,必须使用 +`package:flutter/widget_previews.dart` 中定义的 [`@Preview`][] 注解。该注解可应用于: + - **Top-level functions** that return a `Widget` or `WidgetBuilder`. - **Static methods** within a class that return a `Widget` or @@ -74,9 +120,15 @@ can be applied to: - **Public Widget constructors and factories** with no required arguments. + 返回 `Widget` 或 `WidgetBuilder` 的 **顶层函数**。 +- 返回 `Widget` 或 `WidgetBuilder` 的类内 **静态方法**。 +- 无必需参数的 **公共 Widget 构造函数和工厂**。 + Here is a basic example of how to use the `@Preview` annotation to preview a `Text` widget: +以下是使用 `@Preview` 注解预览 `Text` widget 的基本示例: + ```dart import 'package:flutter/widget_previews.dart'; import 'package:flutter/material.dart'; // For Material widgets @@ -88,78 +140,141 @@ Widget mySampleText() { ``` ![Sample widget in Flutter Widget Previewer](/assets/images/docs/tools/widget-previewer/widget-previewer.png "Example widget") + Each preview instance provides various controls for interacting with the previewed widget. From left to right: +每个预览实例提供多种控件,用于与预览中的 widget 交互。从左到右: + - **Zoom in:** Magnifies the widget in the preview. + + **放大:** 放大预览中的 widget。 + - **Zoom out:** Reduces the magnification of the widget in the preview. + + **缩小:** 减小预览中的放大倍数。 + - **Reset zoom:** Returns the widget preview to its default zoom level. + + **重置缩放:** 将 widget 预览恢复为默认缩放级别。 + - **Toggle between light and dark mode:** Switches the preview's theme between a light and dark color scheme. + + **切换浅色/深色模式:** 在浅色与深色配色方案之间切换预览主题。 + - **Perform a hot restart for the individual preview:** Restarts only the specific widget preview, allowing changes to be applied quickly without restarting the entire application. + + **对单个预览执行热重启:** 仅重启该 widget 预览, + 可快速应用更改而无需重启整个应用。 + For the case where global state has been modified (for example, a static initializer has been changed), the entire widget previewer can be told to hot restart using the button at the bottom right of the environment. +若已修改全局状态(例如静态初始化器已更改), +可使用环境右下角的按钮让整个 widget 预览器热重启。 + ### Filter previews by selected file +### 按所选文件筛选预览 + When viewing previews within an IDE, the widget previewer is configured to filter the set of previews based on the currently selected file: +在 IDE 中查看预览时,widget 预览器会按当前所选文件筛选预览集: + ![Filter by previews selected file in Flutter Widget Previewer](/assets/images/docs/tools/widget-previewer/filter-by-file.gif "Filter previews by selected file") To disable this behavior, toggle the "Filter previews by selected file" option at the bottom left of the environment. +要禁用此行为,请切换环境左下角的 "Filter previews by selected file"(按所选文件筛选预览)选项。 + ## Customize a preview +## 自定义预览 + The [`@Preview`][] annotation has several parameters you can use to customize the preview: +[`@Preview`][] 注解提供多个参数,可用于自定义预览: + - **`name`**: A descriptive name for the preview. + + **`name`**:预览的描述性名称。 + - **`group`**: A name used to group related previews together in the widget previewer. + + **`group`**:在 widget 预览器中将相关预览分组在一起的名称。 + - **`size`**: Artificial size constraints using a `Size` object. + + **`size`**:使用 `Size` 对象施加的人工尺寸约束。 + - **`textScaleFactor`**: A custom font scale. + + **`textScaleFactor`**:自定义字体缩放。 + - **`wrapper`**: A function that wraps your previewed widget in a specific widget tree (for example, to inject application state into the widget tree with an `InheritedWidget`). + + **`wrapper`**:将预览 widget 包裹在特定 widget 树中的函数(例如通过 `InheritedWidget` 向 widget 树注入应用状态)。 + - **`theme`**: A function to provide Material and Cupertino theming data. + + **`theme`**:提供 Material 与 Cupertino 主题数据的函数。 + - **`brightness`**: The initial theme brightness. + + **`brightness`**:初始主题亮度。 + - **`localizations`**: A function to apply a localization configuration. + + **`localizations`**:应用本地化配置的函数。 + ## Create custom preview annotations +## 创建自定义预览注解 + To reduce the amount of boilerplate needed to define previews with a common set of properties, the [`Preview`][] annotation class can be extended to create custom preview annotations tailored for your project. +为减少使用一组通用属性定义预览所需的样板代码,可扩展 [`Preview`][] 注解类, +为项目创建定制的自定义预览注解。 + Here's an example of a custom preview annotation that provides theming data: +以下示例提供主题数据的自定义预览注解: + ```dart final class MyCustomPreview extends Preview { const MyCustomPreview({ @@ -186,6 +301,10 @@ the [`Preview.transform()`][] method. This method is invoked by the widget previ and can be used to modify the preview at runtime, allowing for preview configurations that would not otherwise be possible in a `const` context: +扩展 [`Preview`][] 注解类还可重写 [`Preview.transform()`][] 方法。 +widget 预览器会调用此方法,在运行时修改预览, +从而实现 `const` 上下文中无法实现的预览配置: + ```dart final class TransformativePreview extends Preview { const TransformativePreview({ @@ -225,10 +344,15 @@ final class TransformativePreview extends Preview { ## Creating multiple preview configurations +## 创建多种预览配置 + Creating multiple previews with different configurations can be as simple as applying multiple [`@Preview`][] annotations to a single function or constructor: +为同一函数或构造函数应用多个 [`@Preview`][] 注解, +即可轻松创建多种不同配置的预览: + ```dart @Preview( group: 'Brightness', @@ -250,6 +374,9 @@ can extend the [`MultiPreview`][] to create a custom annotation that creates multiple previews. The following [`MultiPreview`][] creates the same two previews as the previous example: +要简化使用通用配置创建多个预览,可扩展 [`MultiPreview`][] 创建会生成多个预览的自定义注解。 +以下 [`MultiPreview`][] 会创建与上一示例相同的两项预览: + ```dart /// Creates light and dark mode previews. final class MultiBrightnessPreview extends MultiPreview { @@ -278,6 +405,9 @@ Like [`Preview`][], [`MultiPreview`][] also provides a [`MultiPreview.transform()`][] method to perform transformations on each preview at runtime: +与 [`Preview`][] 类似,[`MultiPreview`][] 也提供 [`MultiPreview.transform()`][] 方法, +在运行时对每个预览进行变换: + ```dart /// Creates light and dark mode previews. final class MultiBrightnessPreview extends MultiPreview { @@ -313,14 +443,22 @@ Widget buttonPreview() => const ButtonShowcase(); ## Restrictions and limitations +## 限制与约束 + The Flutter Widget Previewer has certain restrictions you should be aware of: +使用 Flutter Widget 预览器时应注意以下限制: + - **Public callback names**: All callback arguments provided to preview annotations must be public and constant. This is required for the previewer's code generation implementation to work correctly. + + **公共回调名称**:提供给预览注解的所有回调参数必须是 public 且为 constant。 + 预览器的代码生成实现需要如此才能正常工作。 + - **Unsupported APIs**: Native plugins and any APIs from the `dart:io` or `dart:ffi` libraries are not supported. This is because the widget previewer is built with @@ -338,6 +476,14 @@ should be aware of: on how to structure your application to cleanly support platform-specific libraries when targeting multiple platforms. + 因为 widget 预览器基于 Flutter Web 构建,无法访问底层原生平台 API。 + 在 Chrome 中 Web 插件可能可用,但不保证在其他环境(例如嵌入 IDE 时)也能工作。 + + 对 `dart:io` 或 `dart:ffi` 有传递依赖的 widget 可以正常加载, + 但调用这些库中的 API 时会抛出异常。 + + 有关如何在面向多平台时整洁地支持平台特定库,请参阅 [Dart documentation on conditional imports][](Dart 条件导入文档)。 + - **Asset paths**: When using `fromAsset` APIs from `dart:ui` to load resources, you must use **package-based paths** instead of direct local paths. @@ -346,18 +492,33 @@ should be aware of: example, use `'packages/my_package_name/assets/my_image.png'` instead of `'assets/my_image.png'`. + + **资源路径**:使用 `dart:ui` 的 `fromAsset` API 加载资源时, + 必须使用 **基于 package 的路径**,而非直接本地路径。 + 这样资源才能在预览器的 Web 环境中正确定位和加载。 + 例如使用 `'packages/my_package_name/assets/my_image.png'`, + 而不是 `'assets/my_image.png'`。 + - **Unconstrained widgets**: Unconstrained widgets are automatically constrained to approximately half the height and width of the widget previewer. This behavior is likely to change in the future, so constraints should be applied using the `size` parameter when possible. + + **无约束 widget**:无约束 widget 会自动约束为约为 widget 预览器高度和宽度的一半。 + 此行为未来可能变更,因此尽可能使用 `size` 参数施加约束。 + - **Multi-project support in IDEs**: The widget previewer currently only supports displaying previews contained - within a single project or Pub workspace. We’re actively + within a single project or Pub workspace. We're actively investigating options to support IDE sessions with multiple Flutter projects ([#173550][]). + + **IDE 中的多项目支持**:widget 预览器目前仅支持显示单个项目或 Pub 工作区内的预览。 + 我们正在积极研究支持包含多个 Flutter 项目的 IDE 会话的方案 ([#173550][])。 + [`@Preview`]: {{site.api}}/flutter/widget_previews/Preview-class.html [`Preview`]: {{site.api}}/flutter/widget_previews/Preview-class.html [`Preview.transform()`]: {{site.api}}/flutter/widget_previews/Preview/transform.html diff --git a/sites/docs/src/content/ui/accessibility/accessibility-testing.md b/sites/docs/src/content/ui/accessibility/accessibility-testing.md index a3b83d41aa..d2ae35aba4 100644 --- a/sites/docs/src/content/ui/accessibility/accessibility-testing.md +++ b/sites/docs/src/content/ui/accessibility/accessibility-testing.md @@ -1,16 +1,23 @@ --- -title: Accessibility testing -description: Information on Flutter's accessibility testing. +# title: Accessibility testing +title: 无障碍测试 +# description: Information on Flutter's accessibility testing. +description: 关于 Flutter 无障碍测试的信息。 +ai-translated: true --- ## Accessibility regulations +## 无障碍法规 + To ensure your app is accessible, check it against public standards like the [Web Content Accessibility Guidelines (WCAG) 2][], the [EN 301 549][], and use resources like the [Voluntary Product Accessibility Template (VPAT)][] to self-assess your product. For more details on these regulations, check out the main [accessibility page](/ui/accessibility). +为确保你的应用具备无障碍能力,请对照 [Web Content Accessibility Guidelines (WCAG) 2][]、[EN 301 549][] 等公开标准进行检查,并使用 [Voluntary Product Accessibility Template (VPAT)][] 等资源对产品进行自评。有关这些法规的更多详情,请参阅主 [无障碍页面](/ui/accessibility)。 + [Web Content Accessibility Guidelines (WCAG) 2]: https://www.w3.org/WAI/standards-guidelines/wcag/ [EN 301 549]: https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf @@ -18,8 +25,12 @@ regulations, check out the main [accessibility page](/ui/accessibility). ## Inspecting accessibility support +## 检查无障碍支持 + We recommend using automated accessibility scanners to test the following: +我们建议使用自动化无障碍扫描器测试以下内容: + * For Android: 1. Install the [Accessibility Scanner][] for Android 1. Enable the Accessibility Scanner from @@ -28,6 +39,11 @@ We recommend using automated accessibility scanners to test the following: 1. Navigate to the Accessibility Scanner 'checkbox' icon button to initiate a scan. + 对于 Android: + 为 Android 安装 [Accessibility Scanner][] + 在 **Android 设置 > 无障碍 > Accessibility Scanner > 开启** 中启用 Accessibility Scanner。 + 前往 Accessibility Scanner 的「复选框」图标按钮以启动扫描。 + * For iOS: 1. Open the `iOS` folder of your Flutter app in Xcode. 1. Select a Simulator as the target, and click the **Run** button. @@ -41,6 +57,13 @@ We recommend using automated accessibility scanners to test the following: select **Audit** in the toolbar, and then select **Run Audit** to get a report of potential issues. + 对于 iOS: + 在 Xcode 中打开 Flutter 应用的 `iOS` 文件夹。 + 选择模拟器作为目标,然后点击 **Run** 按钮。 + 在 Xcode 中选择 **Xcode > Open Developer Tools > Accessibility Inspector**。 + 在 Accessibility Inspector 中选择 **Inspection > Enable Point to Inspect**,然后在正在运行的 Flutter 应用中点选各种用户界面元素以检查其无障碍属性。 + 在 Accessibility Inspector 的工具栏中选择 **Audit**,然后选择 **Run Audit** 以获取潜在问题的报告。 + * For web: 1. Open Chrome DevTools (or similar tools in other browsers). 2. Inspect the HTML tree under semantics host, containing the ARIA @@ -48,16 +71,27 @@ We recommend using automated accessibility scanners to test the following: 3. In Chrome, the "Elements" tab has an "Accessibility" sub-tab that can be used to inspect the data exported to semantics tree. + 对于 Web: + 打开 Chrome DevTools(或其他浏览器中的类似工具)。 + 检查 semantics host 下的 HTML 树,其中包含 Flutter 生成的 ARIA 属性。 + 在 Chrome 中,「Elements」选项卡带有「Accessibility」子选项卡,可用于检查导出到语义树的数据。 + ## Testing accessibility on mobile +## 在移动端测试无障碍 + Test your app using Flutter's [Accessibility Guideline API][]. This API checks if your app's UI meets Flutter's accessibility recommendations. These cover recommendations for text contrast, target size, and target labels. +使用 Flutter 的 [Accessibility Guideline API][] 测试你的应用。该 API 会检查应用的 UI 是否符合 Flutter 的无障碍建议,涵盖文本对比度、目标尺寸和目标标签等建议。 + The following snippet shows how to use the Guideline API on a sample widget named `AccessibleApp`: +以下代码片段演示如何在名为 `AccessibleApp` 的示例 widget 上使用 Guideline API: + ```dart title="test/a11y_test.dart" import 'package:flutter_test/flutter_test.dart'; @@ -93,18 +127,26 @@ a [new app created with `flutter create`][create-new-app]. Each button on that app's main screen serves as a tappable target with text rendered in an 18-point font. +要试用这些测试,请在[使用 `flutter create` 创建的新应用][create-new-app]上运行它们。该应用主屏幕上的每个按钮都是可点击目标,文本以 18 磅字体渲染。 + You can add Guideline API tests alongside other [widget tests][], or in a separate file, such as `test/a11y_test.dart` in this example. +你可以将 Guideline API 测试与其他 [widget 测试][widget tests] 放在一起,或放在单独的文件中,例如本示例中的 `test/a11y_test.dart`。 + [Accessibility Guideline API]: {{site.api}}/flutter/flutter_test/AccessibilityGuideline-class.html [create-new-app]: /reference/create-new-app [widget tests]: /testing/overview#widget-tests ## Testing accessibility on the web +## 在 Web 上测试无障碍 + You can debug accessibility by visualizing the semantic nodes created for your web app using the following command line flag in profile and release modes: +你可以在 profile 和 release 模式下使用以下命令行标志,通过可视化 Web 应用创建的语义节点来调试无障碍: + ```console flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true ``` @@ -113,5 +155,7 @@ With the flag activated, the semantic nodes appear on top of the widgets; you can verify that the semantic elements are placed where they should be. If the semantic nodes are incorrectly placed, please [file a bug report][]. +启用该标志后,语义节点会显示在 widget 上方;你可以验证语义元素是否放置在正确位置。如果语义节点位置不正确,请[提交 bug 报告][file a bug report]。 + [Accessibility Scanner]: https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en [file a bug report]: https://goo.gle/flutter_web_issue diff --git a/sites/docs/src/content/ui/accessibility/assistive-technologies.md b/sites/docs/src/content/ui/accessibility/assistive-technologies.md index 232c21dc4f..9900db6041 100644 --- a/sites/docs/src/content/ui/accessibility/assistive-technologies.md +++ b/sites/docs/src/content/ui/accessibility/assistive-technologies.md @@ -1,11 +1,17 @@ --- -title: Accessibility technologies +# title: Accessibility technologies +title: 无障碍技术 +# description: >- +# Information about accessibility technologies for Flutter developers. description: >- - Information about accessibility technologies for Flutter developers. + 面向 Flutter 开发者的无障碍技术信息。 +ai-translated: true --- ## Summary +## 摘要 + Assistive technologies are essential for making digital content accessible to individuals with disabilities. This document provides an overview of two key categories of assistive technologies relevant to Flutter development: screen @@ -14,8 +20,12 @@ those with motor limitations. By understanding and testing with these technologies, you can ensure your Flutter application provides a more inclusive and user-friendly experience for everyone. +辅助技术对于让残障人士能够访问数字内容至关重要。本文概述了与 Flutter 开发相关的两类关键辅助技术:面向视障用户的屏幕阅读器,以及面向运动能力受限用户的移动辅助工具。通过了解并使用这些技术进行测试,你可以确保 Flutter 应用为所有人提供更包容、更友好的体验。 + ## Screen readers +## 屏幕阅读器 + For mobile, screen readers ([TalkBack][], [VoiceOver][]) enable visually impaired users to get spoken feedback about the contents of the screen and interact with the UI by using @@ -23,8 +33,12 @@ gestures on mobile and keyboard shortcuts on desktop. Turn on VoiceOver or TalkBack on your mobile device and navigate around your app. +在移动端,屏幕阅读器([TalkBack][]、[VoiceOver][])让视障用户获得屏幕内容的语音反馈,并通过移动端手势或桌面端键盘快捷键与 UI 交互。在移动设备上开启 VoiceOver 或 TalkBack,并在应用中导航。 + **To turn on the screen reader on your device, complete the following steps:** +**要在设备上开启屏幕阅读器,请完成以下步骤:** + @@ -34,9 +48,16 @@ navigate around your app. 3. Turn 'Use TalkBack' on or off. 4. Select Ok. +1. 在设备上打开 **设置**。 +2. 选择 **无障碍**,然后选择 **TalkBack**。 +3. 开启或关闭「使用 TalkBack」。 +4. 选择确定。 + To learn how to find and customize Android's accessibility features, view the following video. +要了解如何查找和自定义 Android 的无障碍功能,请观看以下视频。 + @@ -45,9 +66,14 @@ accessibility features, view the following video. 1. On your device, open **Settings > Accessibility > VoiceOver** 2. Turn the VoiceOver setting on or off +1. 在设备上打开 **设置 > 无障碍 > VoiceOver** +2. 开启或关闭 VoiceOver 设置 + To learn how to find and customize iOS accessibility features, view the following video. +要了解如何查找和自定义 iOS 的无障碍功能,请观看以下视频。 +
@@ -55,21 +81,35 @@ accessibility features, view the following video. For web, the following screen readers are currently supported: +对于 Web,目前支持以下屏幕阅读器: + Mobile browsers: * iOS - VoiceOver * Android - TalkBack +移动端浏览器: + +* iOS - VoiceOver +* Android - TalkBack + Desktop browsers: * macOS - VoiceOver * Windows - JAWs & NVDA +桌面端浏览器: + +* macOS - VoiceOver +* Windows - JAWs & NVDA + Screen readers users on web must toggle the "Enable accessibility" button to build the semantics tree. Users can skip this step if you programmatically auto-enable accessibility for your app using this API: +Web 上的屏幕阅读器用户必须切换「Enable accessibility」按钮以构建语义树。如果你使用以下 API 以编程方式为应用自动启用无障碍,用户可跳过此步骤: + ```dart import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; @@ -89,11 +129,15 @@ NVDA screen reader. To learn about using NVDA to test Windows apps, check out [Screen Readers 101 For Front-End Developers (Windows)][nvda]. +Windows 自带名为 Narrator 的屏幕阅读器,但部分开发者建议使用更流行的 NVDA 屏幕阅读器。要了解如何使用 NVDA 测试 Windows 应用,请参阅 [Screen Readers 101 For Front-End Developers (Windows)][nvda]。 + [nvda]: https://evinced.com/blog/screen-readers-101-for-front-end-developers-windows On a Mac, you can use the desktop version of VoiceOver, which is included in macOS. +在 Mac 上,你可以使用 macOS 自带的桌面版 VoiceOver。 + On Linux, a popular screen reader is called Orca. @@ -102,6 +146,8 @@ and is available on package repositories such as `apt`. To learn about using Orca, check out [Getting started with Orca screen reader on Gnome desktop][orca]. +在 Linux 上,常用的屏幕阅读器是 Orca。部分发行版已预装,也可通过 `apt` 等软件包仓库安装。要了解如何使用 Orca,请参阅 [Getting started with Orca screen reader on Gnome desktop][orca]。 + [orca]: https://www.a11yproject.com/posts/getting-started-with-orca @@ -112,10 +158,14 @@ To learn about using Orca, check out Check out the following [video demo][] to see how to use VoiceOver with the now-archived [Flutter Gallery][] web app. +请观看以下[视频演示][video demo],了解如何将 VoiceOver 与已归档的 [Flutter Gallery][] Web 应用配合使用。 + Flutter's standard widgets generate an accessibility tree automatically. However, if your app needs something different, it can be customized using the [`Semantics` widget][]. +Flutter 的标准 widget 会自动生成无障碍树。不过,若应用需要不同行为,可使用 [`Semantics` widget][] 进行自定义。 + When there is text in your app that should be voiced with a specific voice, inform the screen reader which voice to use by calling [`TextSpan.locale`][]. @@ -124,6 +174,8 @@ will affect screen reader voices starting from flutter 3.38 release. Usually, the screen reader uses the system voice except where you explicitly set it with `TextSpan.locale`. +当应用中的文本需要使用特定语音朗读时,请调用 [`TextSpan.locale`][] 告知屏幕阅读器使用哪种语音。从 Flutter 3.38 起,`MaterialApp.locale` 和 `Localizations.override` 会影响屏幕阅读器的语音。通常,屏幕阅读器使用系统语音,除非你通过 `TextSpan.locale` 显式指定。 + [Flutter Gallery]: {{site.gallery-archive}} [`TextSpan.locale`]: {{site.api}}/flutter/painting/TextSpan/locale.html [`Semantics` widget]: {{site.api}}/flutter/widgets/Semantics-class.html @@ -133,17 +185,23 @@ except where you explicitly set it with `TextSpan.locale`. ## Mobility support +## 移动辅助 + For users with limited dexterity or hand strength, mobility support features can be helpful. Both Android and iOS offer a range of tools designed to make navigation and control easier. These features allow users to operate their devices through external switches, voice commands, or simplified on-screen menus. +对于灵巧度或手部力量受限的用户,移动辅助功能很有帮助。Android 和 iOS 都提供多种工具,旨在让导航和控制更轻松。这些功能允许用户通过外接开关、语音命令或简化的屏幕菜单操作设备。 + Android provides Switch Access, Voice Access and Accessibility Menu, while iOS offers Switch Control, Voice Control, and AssistiveTouch. Understanding these tools helps in creating apps that are usable by people with diverse physical abilities. +Android 提供 Switch Access、Voice Access 和 Accessibility Menu,iOS 提供 Switch Control、Voice Control 和 AssistiveTouch。了解这些工具有助于创建可供不同身体能力人群使用的应用。 + @@ -151,6 +209,11 @@ apps that are usable by people with diverse physical abilities. + + + + + @@ -158,30 +221,60 @@ apps that are usable by people with diverse physical abilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Features Functions
操作系统功能作用
Switch Access As an alternate input method, you can use Switch Access and Camera Switches
AndroidSwitch Access 作为替代输入方式,你可以使用 Switch Access 和 Camera Switches
Android Voice Access Control your device with your voice
AndroidVoice Access 用语音控制设备
Android Accessibility Menu A floating, on-screen menu that provides simplified buttons to control essential phone functions.
AndroidAccessibility Menu 浮动屏幕菜单,提供简化按钮以控制手机基本功能。
iOS Switch Control Use switches as an alternate input methods
iOSSwitch Control 将开关用作替代输入方式
iOS Voice Control Control your device with your voice
iOSVoice Control 用语音控制设备
iOS AssistiveTouch Use AssistiveTouch to replace multi-finger gestures or hardware button actions
iOSAssistiveTouch 使用 AssistiveTouch 替代多指手势或硬件按钮操作
diff --git a/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md b/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md index e67cde3408..1147aad615 100644 --- a/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md +++ b/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md @@ -1,33 +1,50 @@ --- -title: UI design & styling -description: Information on Flutter's accessibility support. +# title: UI design & styling +title: UI 设计与样式 +# description: Information on Flutter's accessibility support. +description: 关于 Flutter 无障碍支持的信息。 +ai-translated: true --- To create an accessible app, design your UI with accessibility in mind. This page covers key aspects of accessible UI design and styling. +要创建无障碍应用,请在设计 UI 时考虑无障碍。本页介绍无障碍 UI 设计与样式的关键方面。 + ## Large fonts +## 大字体 + Both Android and iOS contain system settings to configure the desired font sizes used by apps. Flutter text widgets respect this OS setting when determining font sizes. +Android 和 iOS 都提供系统设置,用于配置应用使用的字体大小。Flutter 文本 widget 在确定字体大小时会遵循该操作系统设置。 + Font sizes are calculated automatically by Flutter based on the OS setting. However, as a developer you should make sure your layout has enough room to render all its contents when the font sizes are increased. For example, you can test all parts of your app on a small-screen device configured to use the largest font setting. +Flutter 会根据操作系统设置自动计算字体大小。不过,作为开发者,你应确保在字体变大时,布局仍有足够空间渲染全部内容。例如,可在配置为最大字体的小屏设备上测试应用的各个部分。 + To adjust font sizes: on iOS, go to Settings > Accessibility > Display & Text Size; on Android, go to Settings > Font size. +调整字体大小:在 iOS 上,前往 设置 > 无障碍 > 显示与文字大小;在 Android 上,前往 设置 > 字体大小。 + ### Example +### 示例 + The following two screenshots show the standard Flutter app template rendered with the default iOS font setting, and with the largest font setting selected in iOS accessibility settings. +以下两张截图分别展示:使用 iOS 默认字体设置渲染的标准 Flutter 应用模板,以及在 iOS 无障碍设置中选择最大字体后的渲染效果。 +
@@ -36,6 +53,8 @@ and with the largest font setting selected in iOS accessibility settings. ## Sufficient contrast +## 足够对比度 + Sufficient color contrast makes text and images easier to read. Along with benefitting users with various visual impairments, sufficient color contrast helps all users when viewing an interface @@ -43,30 +62,49 @@ on devices in extreme lighting conditions, such as when exposed to direct sunlight or on a display with low brightness. +足够的颜色对比度让文字和图片更易阅读。除了惠及各类视障用户,在强光直射或屏幕亮度较低等极端光照条件下查看界面时,足够对比度也有助于所有用户。 + The [W3C recommends][]: +[W3C 建议][W3C recommends]: + * At least 4.5:1 for small text (below 18 point regular or 14 point bold) * At least 3.0:1 for large text (18 point and above regular or 14 point and above bold) + 小号文字(低于 18 磅常规或 14 磅粗体)至少 4.5:1 +* 大号文字(18 磅及以上常规或 14 磅及以上粗体)至少 3.0:1 + You can test contrast using Flutter's [Accessibility Guideline API][]. For more details on testing, check out the [accessibility testing page](/ui/accessibility/accessibility-testing/). +你可以使用 Flutter 的 [Accessibility Guideline API][] 测试对比度。有关测试的更多详情,请参阅[无障碍测试页面](/ui/accessibility/accessibility-testing/)。 + [W3C recommends]: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html ## Tap target size +## 点击目标尺寸 + Controls that are too small are hard for many people to interact with and select. Ensure that interactive elements have a large enough tap target to be easily pressed by users. +过小的控件对许多人来说难以操作和选中。请确保交互元素具有足够大的点击目标,便于用户按压。 + Both [Android][] and [iOS][] recommend a minimum tap target size of 48x48 dp and 44x44 pts respectively. +[Android][] 和 [iOS][] 分别建议最小点击目标为 48x48 dp 和 44x44 pt。 + The [W3C] recommends a minimum target size of 44 by 44 CSS pixels. +[W3C] 建议最小目标尺寸为 44×44 CSS 像素。 + You can test tap target size using Flutter's [Accessibility Guideline API][]. For more details on testing, check out the [accessibility testing page](/ui/accessibility/accessibility-testing/). +你可以使用 Flutter 的 [Accessibility Guideline API][] 测试点击目标尺寸。有关测试的更多详情,请参阅[无障碍测试页面](/ui/accessibility/accessibility-testing/)。 + [Android]: https://developer.android.com/guide/topics/ui/accessibility/apps#large-controls [iOS]: https://developer.apple.com/design/human-interface-guidelines/accessibility#Mobility [W3C]: https://www.w3.org/WAI/WCAG21/Understanding/target-size.html @@ -75,8 +113,12 @@ For more details on testing, check out the [accessibility testing page](/ui/acce ## Other accessibility features +## 其他无障碍功能 + You can check the [AccessibilityFeatures] class for additional accessibility features that may be enabled by the platform, such as bold text, high contrast, and inverted colors. +你可以查看 [AccessibilityFeatures] 类,了解平台可能启用的其他无障碍功能,例如粗体文字、高对比度和反色。 + [AccessibilityFeatures]: https://api.flutter-io.cn/flutter/dart-ui/AccessibilityFeatures-class.html diff --git a/sites/docs/src/content/ui/accessibility/web-accessibility.md b/sites/docs/src/content/ui/accessibility/web-accessibility.md index 585ca1a694..1e8cff6543 100644 --- a/sites/docs/src/content/ui/accessibility/web-accessibility.md +++ b/sites/docs/src/content/ui/accessibility/web-accessibility.md @@ -1,10 +1,15 @@ --- -title: Web accessibility -description: Information about web accessibility +# title: Web accessibility +title: Web 无障碍 +# description: Information about web accessibility +description: 关于 Web 无障碍的信息 +ai-translated: true --- ## Background +## 背景 + Flutter supports web accessibility by translating its internal Semantics tree into an accessible HTML DOM structure that screen readers can understand. @@ -12,21 +17,34 @@ Since Flutter renders its UI on a single canvas, it needs a special layer to expose the UI's meaning and structure to web browsers. +Flutter 通过将内部 Semantics 树转换为屏幕阅读器可理解的 HTML DOM 结构来支持 Web 无障碍。由于 Flutter 在单一 canvas 上渲染 UI,需要特殊层向 Web 浏览器暴露 UI 的含义与结构。 + + ## Opt-in web accessibility +## 可选启用的 Web 无障碍 + ### Invisible button +### 隐形按钮 + For performance reasons, Flutter's web accessibility is not on by default. To turn on accessibility, the user needs to press an invisible button with `aria-label="Enable accessibility"`. After pressing the button, the DOM tree will reflect all accessibility information for the widgets. +出于性能考虑,Flutter 的 Web 无障碍默认未开启。要启用无障碍,用户需按下带有 `aria-label="Enable accessibility"` 的隐形按钮。按下后,DOM 树将反映 widget 的全部无障碍信息。 + ### Turn on accessibility mode in code +### 在代码中开启无障碍模式 + An alternative approach is to turn on accessibility mode by adding the following code when running an app. +另一种方式是在运行应用时添加以下代码以开启无障碍模式。 + ```dart import 'package:flutter/semantics.dart'; @@ -42,49 +60,77 @@ void main() { ## Enhancing Accessibility with Semantic Roles +## 使用语义角色增强无障碍 + ### What are Semantic Roles? +### 什么是语义角色? + Semantic roles define the purpose of a UI element, helping screen readers and other assistive tools interpret and present your application effectively -to users. For example, a role can indicate if a widget is a button, a link, to users. For example, a role can indicate whether a widget is a button, a link, a heading, a slider, or part of a table. +语义角色定义 UI 元素的用途,帮助屏幕阅读器和其他辅助工具向用户有效解释和呈现你的应用。例如,角色可表明 widget 是按钮、链接、标题、滑块还是表格的一部分。 + While Flutter's standard widgets often provide these semantics automatically, a custom component without a clearly defined role can be incomprehensible to a screen reader user. +Flutter 的标准 widget 通常会自动提供这些语义,但没有明确定义角色的自定义 widget 可能对屏幕阅读器用户难以理解。 + + By assigning appropriate roles, you ensure that: +通过分配合适的角色,你可以确保: + * Screen readers can announce the type and purpose of elements correctly. * Users can navigate your application more effectively using assistive technologies. * Your application adheres to web accessibility standards, improving usability. + 屏幕阅读器能正确播报元素的类型与用途。 +* 用户能借助辅助技术更高效地在应用中导航。 +* 应用符合 Web 无障碍标准,提升可用性。 + ### Using `SemanticsRole` in Flutter for web +### 在 Flutter Web 中使用 `SemanticsRole` + Flutter provides the [`Semantics` widget][] with the [`SemanticsRole` enum][] to allow developers to assign specific roles to their widgets. When your Flutter web app is rendered, these Flutter-specific roles are translated into corresponding ARIA roles in the web page's HTML structure. +Flutter 提供 [`Semantics` widget][] 与 [`SemanticsRole` enum][],让开发者为 widget 分配特定角色。Flutter Web 应用渲染时,这些 Flutter 特有角色会转换为网页 HTML 结构中对应的 ARIA 角色。 + [`Semantics` widget]: {{site.api}}/flutter/widgets/Semantics-class.html [`SemanticsRole` enum]: {{site.api}}/flutter/dart-ui/SemanticsRole.html **1. Automatic Semantics from Standard Widgets** +**1. 标准 widget 的自动语义** + Many standard Flutter widgets, like `TabBar`, `MenuAnchor`, and `Table`, automatically include semantic information along with their roles. Whenever possible, prefer using these standard widgets as they handle many accessibility aspects out-of-the-box. +许多标准 Flutter widget(如 `TabBar`、`MenuAnchor`、`Table`)会自动包含语义信息及其角色。尽可能优先使用这些标准 widget,它们开箱即用地处理许多无障碍方面。 + **2. Explicitly adding or overriding roles** +**2. 显式添加或覆盖角色** + For custom components or when the default semantics aren't sufficient, use the `Semantics` widget to define the role: +对于自定义 widget 或默认语义不足时,使用 `Semantics` widget 定义角色: + Here's an example of how you might explicitly define a list and its items: +以下示例演示如何显式定义列表及其列表项: + ```dart import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; diff --git a/sites/docs/src/content/ui/adaptive-responsive/best-practices.md b/sites/docs/src/content/ui/adaptive-responsive/best-practices.md index 8dd464fdfe..1c22f9d3b6 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/best-practices.md +++ b/sites/docs/src/content/ui/adaptive-responsive/best-practices.md @@ -1,23 +1,38 @@ --- -title: Best practices for adaptive design +# title: Best practices for adaptive design +title: 自适应设计最佳实践 +# description: >- +# Summary of some of the best practices for adaptive design. +# shortTitle: Best practices description: >- - Summary of some of the best practices for adaptive design. -shortTitle: Best practices + 自适应设计部分最佳实践摘要。 +shortTitle: 最佳实践 +ai-translated: true --- Recommended best practices for adaptive design include: +自适应设计的推荐最佳实践包括: + ## Design considerations +## 设计考量 + ### Break down your widgets +### 拆分 widget + While designing your app, try to break down large, complex widgets into smaller, simpler ones. +设计应用时,尽量将大型、复杂的 widget 拆分为更小、更简单的 widget。 + Refactoring widgets can reduce the complexity of adopting an adaptive UI by sharing core pieces of code. There are other benefits as well: +重构 widget 可通过共享核心代码降低采用自适应 UI 的复杂度,还有其他好处: + * On the performance side, having lots of small `const` widgets improves rebuild times over having large, complex widgets. @@ -29,13 +44,21 @@ There are other benefits as well: of each `Widget` down. A less-complex `Widget` is more readable, easier to refactor, and less likely to have surprising behavior. + 在性能方面,大量小型 `const` widget 比大型复杂 widget 更能缩短重建时间。 +* Flutter 可复用 `const` widget 实例,而较大的复杂 widget 每次重建都需重新设置。 +* 从代码健康角度看,将 UI 组织成更小的片段有助于降低每个 `Widget` 的复杂度。复杂度较低的 `Widget` 更易读、更易重构,也更少出现意外行为。 + To learn more, check out the 3 steps of adaptive design in [General approach][]. +要了解更多,请参阅 [General approach][] 中的自适应设计三步法。 + [General approach]: /ui/adaptive-responsive/general ### Design to the strengths of each form factor +### 针对各形态优势进行设计 + Beyond screen size, you should also spend time considering the unique strengths and weaknesses of different form factors. It isn't always ideal @@ -44,6 +67,8 @@ functionality everywhere. Consider whether it makes sense to focus on specific capabilities, or even remove certain features, on some device categories. +除屏幕尺寸外,你还应花时间考虑不同形态的独特优势与劣势。多平台应用并非在所有地方提供完全相同功能总是理想选择。考虑是否在某些设备类别上聚焦特定能力,甚至移除某些功能。 + For example, mobile devices are portable and have cameras, but they aren't well suited for detailed creative work. With this in mind, you might focus more on capturing content @@ -51,30 +76,42 @@ and tagging it with location data for a mobile UI, but focus on organizing or manipulating that content for a tablet or desktop UI. +例如,移动设备便携且带相机,但不适合精细创意工作。据此,移动 UI 可更侧重内容采集与位置标记,平板或桌面 UI 则侧重组织或编辑该内容。 + Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide which [deep links][] to support, and design your navigation routes with those in mind. +另一例是利用 Web 极低的分享门槛。若部署 Web 应用,决定支持哪些[深度链接][deep links],并据此设计导航路由。 + The key takeaway here is to think about what each platform does best and see if there are unique capabilities you can leverage. +要点是思考各平台最擅长什么,以及能否利用独特能力。 + [deep links]: /ui/navigation/deep-linking ### Solve touch first +### 先解决触控 + Building a great touch UI can often be more difficult than a traditional desktop UI due, in part, to the lack of input accelerators like right-click, scroll wheel, or keyboard shortcuts. +构建出色的触控 UI 往往比传统桌面 UI 更难,部分原因是缺少右键、滚轮或键盘快捷键等输入加速器。 + One way to approach this challenge is to focus initially on a great touch-oriented UI. You can still do most of your testing using the desktop target for its iteration speed. But, remember to switch frequently to a mobile device to verify that everything feels right. +一种应对方式是先聚焦出色的触控导向 UI。你仍可用桌面目标做大部分测试以加快迭代,但请经常切换到移动设备验证体验是否合适。 + After you have the touch interface polished, you can tweak the visual density for mouse users, and then layer on all the additional inputs. Approach these other inputs as @@ -83,34 +120,53 @@ The important thing to consider is what a user expects when using a particular input device, and work to reflect that in your app. +触控界面打磨好后,可为鼠标用户调整视觉密度,再叠加其他输入。将这些输入视为加速器——让任务更快的替代方式。重要的是考虑用户使用特定输入设备时的预期,并在应用中体现。 + ## Implementation details +## 实现细节 + ### Don't lock the orientation of your app. +### 不要锁定应用方向。 + An adaptive app should look good on windows of different sizes and shapes. While locking an app to portrait mode on phones can help narrow the scope of a minimum viable product, it can increase the effort required to make the app adaptive in the future. +自适应应用应在不同尺寸和形状的窗口上表现良好。在手机上锁定竖屏有助于缩小最小可行产品范围,但会增加日后实现自适应的工作量。 + For example, the assumption that phones will only render your app in a full screen portrait mode is not a guarantee. Multi window app support is becoming common, and foldables have many use cases that work best with multiple apps running side by side. +例如,手机仅以全屏竖屏渲染应用并非必然。多窗口应用支持日益普遍,折叠屏也有许多适合多应用并排的场景。 + If you absolutely must lock your app in portrait mode (but don't), use the `Display` API instead of something like `MediaQuery` to get the physical dimensions of the screen. +若必须锁定竖屏(但最好不要),请使用 `Display` API 而非 `MediaQuery` 等方式获取屏幕物理尺寸。 + To summarize: +总结如下: + * Locked screens can be [an accessibility issue][] for some users * Android large format tiers require portrait and landscape support at the [lowest level][]. * Android devices can [override a locked screen][] * Apple guidelines say [aim to support both orientations][] + 锁定屏幕可能对部分用户构成[无障碍问题][an accessibility issue] + * Android 大屏分级在[最低层级][lowest level]要求支持竖屏与横屏 + * Android 设备可[覆盖锁定屏幕][override a locked screen] + * Apple 指南建议[尽量支持两种方向][aim to support both orientations] + [an accessibility issue]: https://www.w3.org/WAI/WCAG21/Understanding/orientation.html [aim to support both orientations]: https://www.w3.org/WAI/WCAG21/Understanding/orientation.html [lowest level]: {{site.android-dev}}/docs/quality-guidelines/large-screen-app-quality#T3-8 @@ -118,6 +174,8 @@ To summarize: ### Avoid device orientation-based layouts +### 避免基于设备方向的布局 + Avoid using `MediaQuery`'s orientation field or `OrientationBuilder` near the top of your widget tree to switch between different app layouts. This is @@ -126,31 +184,45 @@ to determine screen size. The device's orientation also doesn't necessarily inform you of how much space your app window has. +避免在 widget 树顶部附近使用 `MediaQuery` 的 orientation 字段或 `OrientationBuilder` 在不同应用布局间切换。这与不要通过设备类型判断屏幕尺寸的建议类似。设备方向也未必告诉你应用窗口有多少空间。 + Instead, use `MediaQuery`'s `sizeOf` or `LayoutBuilder`, as discussed in the [General approach][] page. Then use adaptive breakpoints like the ones that [Material][] recommends. +请改用 `MediaQuery` 的 `sizeOf` 或 `LayoutBuilder`,如 [General approach][] 页所述。然后使用 [Material][] 推荐的自适应断点等。 + [General approach]: /ui/adaptive-responsive/general# [Material]: https://m3.material.io/foundations/layout/applying-layout/window-size-classes ### Don't gobble up all of the horizontal space +### 不要占满全部横向空间 + Apps that use the full width of the window to display boxes or text fields don't play well when these apps run on large screens. +在大屏上运行时,用窗口全宽显示方框或文本字段的应用体验不佳。 + To learn how to avoid this, check out [Layout with GridView][]. +要了解如何避免,请参阅 [Layout with GridView][]。 + [Layout with GridView]: /ui/adaptive-responsive/large-screens#layout-with-gridview ### Avoid checking for hardware types +### 避免检查硬件类型 + Avoid writing code that checks whether the device you're running on is a "phone" or a "tablet", or any other type of device when making layout decisions. +避免在布局决策时编写代码判断运行设备是「手机」「平板」或其他类型。 + What space your app is actually given to render in isn't always tied to the full screen size of the device. Flutter can run on many different platforms, @@ -160,19 +232,27 @@ or even in a picture-in-picture on phones. Therefore, device type and app window size aren't really strongly connected. +应用实际获得的渲染空间未必与设备全屏尺寸挂钩。Flutter 可在多种平台运行,应用可能在 ChromeOS 的可调整窗口、平板多窗口并排,甚至手机画中画中运行。因此设备类型与应用窗口尺寸并无强关联。 + Instead, use `MediaQuery` to get the size of the window your app is currently running in. +请改用 `MediaQuery` 获取应用当前运行窗口的尺寸。 + This isn't only helpful for UI code. To learn how abstracting out device capabilities can help your business logic code, check out the 2022 Google I/O talk, [Flutter lessons for federated plugin development][]. +这不仅对 UI 代码有帮助。要了解抽象设备能力如何帮助业务逻辑,请参阅 2022 Google I/O 演讲 [Flutter lessons for federated plugin development][]。 + [Flutter lessons for federated plugin development]: {{site.youtube-site}}/watch?v=GAnSNplNpCA ### Support a variety of input devices +### 支持多种输入设备 + Apps should support basic mice, trackpads, and keyboard shortcuts. The most common user flows should support keyboard navigation @@ -180,17 +260,25 @@ to ensure accessibility. In particular, your app follow accessible best practices for keyboards on large devices. +应用应支持基本鼠标、触控板和键盘快捷键。最常见用户流程应支持键盘导航以确保无障碍。尤其在大屏设备上,应用应遵循键盘相关的无障碍最佳实践。 + The Material library provides widgets with excellent default behavior for touch, mouse, and keyboard interaction. +Material 库提供的 widget 在触控、鼠标和键盘交互方面有出色的默认行为。 + To learn how to add this support to custom widgets, check out [User input & accessibility][]. +要了解如何为自定义 widget 添加此类支持,请参阅 [User input & accessibility][]。 + [User input & accessibility]: /ui/adaptive-responsive/input ### Restore List state +### 恢复 List 状态 + To maintain the scroll position in a list that doesn't change its layout when the device's orientation changes, @@ -199,26 +287,36 @@ use the [`PageStorageKey`][] class. widget state in storage after the widget is destroyed and restores state when recreated. +要在设备方向改变时布局不变的列表中保持滚动位置,请使用 [`PageStorageKey`][] 类。[`PageStorageKey`][] 在 widget 销毁后将状态持久化,并在重建时恢复。 + You can see an example of this in the [Wonderous app][], where it stores the list's state in the `SingleChildScrollView` widget. +可在 [Wonderous app][] 中查看示例,它在 `SingleChildScrollView` widget 中保存列表状态。 + If the `List` widget changes its layout when the device's orientation changes, you might have to do a bit of math ([example][]) to change the scroll position on screen rotation. +若 `List` widget 在方向改变时改变布局,屏幕旋转时可能需要一些计算([示例][example])来调整滚动位置。 + [example]: {{site.github}}/gskinnerTeam/flutter-wonderous-app/blob/34e49a08084fbbe69ed67be948ab00ef23819313/lib/ui/screens/collection/widgets/_collection_list.dart#L39 [`PageStorageKey`]: {{site.api}}/flutter/widgets/PageStorageKey-class.html [Wonderous app]: {{site.github}}/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/screens/wonder_events/widgets/_events_list.dart#L64 ## Save app state +## 保存应用状态 + Apps should retain or restore [app state][] as the device rotates, changes window size, or folds and unfolds. By default, an app should maintain state. +应用应在设备旋转、窗口尺寸变化或折叠/展开时保留或恢复[应用状态][app state]。默认情况下,应用应维持状态。 + If your app loses state during device configuration, verify that the plugins and native extensions that your app uses support the @@ -226,12 +324,16 @@ device type, such as a large screen. Some native extensions might lose state when the device changes position. +若应用在设备配置变更时丢失状态,请确认所用插件和原生扩展是否支持该设备类型(例如大屏)。部分原生扩展在设备姿态变化时可能丢失状态。 + For more information on a real-world case where this occurred, check out [Problem: Folding/unfolding causes state loss][state-loss] in [Developing Flutter apps for Large screens][article], a free article on Medium. +有关此类真实案例的更多信息,请参阅 Medium 免费文章 [Developing Flutter apps for Large screens][article] 中的 [Problem: Folding/unfolding causes state loss][state-loss]。 + [app state]: {{site.android-dev}}/jetpack/compose/state#store-state [article]: {{site.flutter-blog}}/developing-flutter-apps-for-large-screens-53b7b0e17f10 [state-loss]: {{site.flutter-blog}}/developing-flutter-apps-for-large-screens-53b7b0e17f10#:~:text=Problem%3A%20Folding/Unfolding%20causes%20state%2Dloss diff --git a/sites/docs/src/content/ui/adaptive-responsive/capabilities.md b/sites/docs/src/content/ui/adaptive-responsive/capabilities.md index ad4c098e81..2ed9bc3877 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/capabilities.md +++ b/sites/docs/src/content/ui/adaptive-responsive/capabilities.md @@ -1,10 +1,15 @@ --- -title: Capabilities & policies +# title: Capabilities & policies +title: 能力与策略 +# description: >- +# Learn how to adapt your app to the +# capabilities and policies required +# by the platform, app store, your company, +# and so on. description: >- - Learn how to adapt your app to the - capabilities and policies required - by the platform, app store, your company, - and so on. + 了解如何让应用适配平台、应用商店、公司等要求的 + 能力与策略。 +ai-translated: true --- Most real-world apps have the need to adapt to the @@ -12,8 +17,12 @@ capabilities and policies of different devices and platforms. This page contains advice for how to handle these scenarios in your code. +大多数真实应用都需要适配不同设备与平台的能力与策略。本页提供在代码中处理这些场景的建议。 + ## Design to the strengths of each device type +## 针对各设备类型优势进行设计 + Consider the unique strengths and weaknesses of different devices. Beyond their screen size and inputs, such as touch, mouse, keyboard, what other unique capabilities can you leverage? @@ -22,60 +31,99 @@ but strong design is more than just running code. Think about what each platform does best and see if there are unique capabilities to leverage. +考虑不同设备的独特优势与劣势。除屏幕尺寸以及触控、鼠标、键盘等输入外,还有哪些独特能力可利用?Flutter 让你的代码能在不同设备上 _运行_,但优秀设计不止于运行代码。思考各平台最擅长什么,看是否有独特能力可借力。 + For example: Apple's App Store and Google's Play Store have different rules that apps need to abide by. Different host operating systems have differing capabilities across time as well as each other. +例如:Apple App Store 与 Google Play Store 规则不同,应用需遵守。不同宿主操作系统的能力也会随时间变化且彼此不同。 + Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide what deep links to support, and design the navigation routes with those in mind. +另一例是利用 Web 极低的分享门槛。若部署 Web 应用,决定支持哪些深度链接,并据此设计导航路由。 + Flutter's recommended pattern for handling different behavior based on these unique capabilities is to create a set of `Capability` and `Policy` classes for your app. +Flutter 推荐的做法是:根据这些独特能力,为应用创建一组 `Capability` 与 `Policy` 类。 + ### Capabilities +### 能力 + A _capability_ defines what the code or device _can_ do. Examples of capabilities include: +_能力_ 定义代码或设备 _能_ 做什么。能力示例包括: + * The existence of an API * OS-enforced restrictions * Physical hardware requirements (like a camera) + API 是否存在 +* 操作系统强制限制 +* 物理硬件要求(如相机) + ### Policies +### 策略 + A _policy_ defines what the code _should_ do. +_策略_ 定义代码 _应_ 做什么。 + Examples of policies include: +策略示例包括: + * App store guidelines * Design preferences * Assets or copy that refers to the host device * Features enabled on the server side + 应用商店指南 +* 设计偏好 +* 引用宿主设备的资源或文案 +* 服务端启用的功能 + ### How to structure policy code +### 如何组织策略代码 + The simplest mechanical way is `Platform.isAndroid`, `Platform.isIOS`, and `kIsWeb`. These APIs mechanically let you know where the code is running but have some problems as the app expands where it can run, and as host platforms add functionality. +最简单的方式是使用 `Platform.isAndroid`、`Platform.isIOS` 和 `kIsWeb`。这些 API 能机械地告诉你代码运行位置,但随着应用可运行范围扩大以及宿主平台增加功能,会有一些问题。 + The following guidelines explain best practices when developing the capabilities and policies for your app: +以下指南说明为应用开发能力与策略时的最佳实践: + **Avoid using `Platform.isAndroid` and similar functions to make layout decisions or assumptions about what a device can do.** +**避免使用 `Platform.isAndroid` 及类似函数做布局决策或假设设备能做什么。** + Instead, describe what you want to branch on in a method. +请改为在方法中描述你要分支的条件。 + Example: Your app has a link to buy something in a website, but you don't want to show that link on iOS devices for policy reasons. +示例:应用有在网站购买的链接,但出于策略原因不想在 iOS 设备上显示。 + ```dart bool shouldAllowPurchaseClick() { // Banned by Apple App Store guidelines. @@ -97,6 +145,8 @@ This method can exist directly in the class but it's likely that other parts of the code might need this same check. If so, put the code in a class. +增加一层间接能带来什么?代码更清楚地说明分支存在的原因。该方法可直接放在类中,但代码其他部分可能也需要相同检查;若是,请放入类中。 + ```dart title="policy.dart" class Policy { @@ -116,13 +166,19 @@ buying on the web isn't the right flow for Android users, you can change the implementation and the tests for clickable text won't need to change. +将代码放在类中后,任何 widget 测试都可 mock `Policy().shouldAllowPurchaseClick`,并在不依赖运行设备的情况下验证行为。这也意味着日后若认为 Web 购买不适合 Android 用户,可改实现而可点击文本的测试无需改动。 + ## Capabilities +## 能力 + Sometimes you want your code to do something but the API doesn't exist, or maybe you depend on a plugin feature that isn't yet implemented on all of the platforms you support. This is a limitation of what the device _can_ do. +有时你想让代码做某事但 API 不存在,或依赖的插件功能尚未在你支持的所有平台实现。这是设备 _能_ 做什么的限制。 + Those situations are similar to the policy decisions described above, but these are referred to as _capabilities_. Why separate policy classes from capabilities @@ -134,6 +190,8 @@ changes in what platforms can do or require in addition to your own preferences after the initial code is written. +这些情况与上述策略决策类似,但称为 _能力_。类结构相似时为何将策略与能力分开?Flutter 团队在生产应用中发现,区分应用 _能_ 做什么与 _应_ 做什么,有助于大型产品在初始代码完成后,除自身偏好外,还能响应平台能力或要求的变化。 + For example, consider the case where one platform adds a new permission that requires users to interact with a system dialog before your code calls a sensitive API. @@ -145,17 +203,25 @@ then the implementation of `requirePermissionDialogFlow` can now check the API level and return true for platform 2. You've leveraged the work you already did. +例如,某平台新增权限,要求用户在调用敏感 API 前与系统对话框交互。团队为平台 1 实现并创建名为 `requirePermissionDialogFlow` 的能力。若平台 2 日后有类似要求但仅针对新 API 版本,`requirePermissionDialogFlow` 的实现可检查 API 级别并对平台 2 返回 true,从而复用已有工作。 + ## Policies +## 策略 + We encourage starting with a `Policy` class initially even if it seems like you won't make many policy based decisions. As the complexity of the class grows or the number of inputs expands, you might decide to break up the policy class by feature or some other criteria. +我们鼓励即使看似不会做很多策略决策,也先创建 `Policy` 类。随着类复杂度或输入增多,你可按功能或其他标准拆分策略类。 + For policy implementation, you can use compile time, run time, or Remote Procedure Call (RPC) backed implementations. +策略实现可使用编译时、运行时或远程过程调用(RPC)支持的方式。 + Compile-time policy checks are good for platforms where the preference is unlikely to change and where accidentally changing the value might have large consequences. @@ -163,31 +229,47 @@ For example, if a platform requires that you not link to the Play store, or requires that you use a specific payment provider given the content of your app. +编译时策略检查适用于偏好不太可能改变、误改值可能后果严重的平台。例如,某平台要求不得链接 Play 商店,或根据应用内容要求使用特定支付提供商。 + Runtime checks can be good for determining if there is a touch screen the user can use. Android has a feature you can check and your web implementation could check for max touch points. +运行时检查适合判断用户是否可使用触摸屏。Android 有可检查的特性,Web 实现可检查 max touch points。 + RPC-backed policy changes are good for incremental feature rollout or for decisions that might change later. +RPC 支持的策略变更适合渐进式功能发布或日后可能改变的决策。 + ## Summary +## 摘要 + Use a `Capability` class to define what the code *can* do. You might check against the existence of an API, OS-enforced restrictions, and physical hardware requirements (like a camera). A capability usually involves compile or runtime checks. +使用 `Capability` 类定义代码*能*做什么。可检查 API 是否存在、操作系统限制及物理硬件要求(如相机)。能力通常涉及编译或运行时检查。 + Use a `Policy` class (or classes depending on complexity) to define what the code _should_ do to comply with App store guidelines, design preferences, and assets or copy that need to refer to the host device. Policies can be a mix of compile, runtime, or RPC checks. +使用 `Policy` 类(或按复杂度使用多个类)定义代码_应_做什么以符合应用商店指南、设计偏好及需引用宿主设备的资源或文案。策略可混合编译、运行时或 RPC 检查。 + Test the branching code by mocking capabilities and policies so the widget tests don't need to change when capabilities or policies change. +通过 mock 能力与策略测试分支代码,这样在能力或策略变化时 widget 测试无需改动。 + Name the methods in your capabilities and policies classes based on what they are trying to branch, rather than on device type. + +能力与策略类中的方法命名应基于分支意图,而非设备类型。 diff --git a/sites/docs/src/content/ui/adaptive-responsive/general.md b/sites/docs/src/content/ui/adaptive-responsive/general.md index fbd9f09a19..84eb047695 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/general.md +++ b/sites/docs/src/content/ui/adaptive-responsive/general.md @@ -1,8 +1,13 @@ --- -title: General approach to adaptive apps +# title: General approach to adaptive apps +title: 自适应应用的一般方法 +# description: >- +# General advice on how to approach making your Flutter app adaptive. +# shortTitle: General approach description: >- - General advice on how to approach making your Flutter app adaptive. -shortTitle: General approach + 如何让 Flutter 应用具备自适应能力的一般建议。 +shortTitle: 一般方法 +ai-translated: true --- @@ -12,27 +17,43 @@ designed for conventional mobile devices, and make it beautiful on a wide range of devices? What steps are required? +那么,究竟 _如何_ 将面向常规移动设备设计的应用,做得在多种设备上都出色?需要哪些步骤? + Google engineers, who have experience doing this very thing for large apps, recommend the following 3-step approach. +有大型应用实战经验的 Google 工程师推荐以下三步法。 + ## Step 1: Abstract +## 步骤 1:抽象 + ![Step 1: Abstract info common to any UI widget](/assets/images/docs/ui/adaptive-responsive/abstract.png) First, identify the widgets that you plan to make dynamic. Analyze the constructors for those widgets and abstract out the data that you can share. +首先,确定计划做成动态的 widget。分析这些 widget 的构造函数,抽象出可共享的数据。 + Common widgets that require adaptability are: +常见需要自适应的 widget 包括: + * Dialogs, both fullscreen and modal * Navigation UI, both rail and bottom bar * Custom layout, such as "is the UI area taller or wider?" + 对话框,全屏与模态 +* 导航 UI,rail 与底部栏 +* 自定义布局,例如「UI 区域更高还是更宽?」 + For example, in a `Dialog` widget, you can share the info that contains the _content_ of the dialog. +例如,在 `Dialog` widget 中,可共享包含对话框 _内容_ 的信息。 + Or, perhaps you want to switch between a `NavigationBar` when the app window is small, and a `NavigationRail` when the app window is large. @@ -42,16 +63,26 @@ you might create a `Destination` widget to hold this info, and specify the `Destination` as having both an icon and a text label. +或者,应用窗口较小时用 `NavigationBar`,较大时用 `NavigationRail`。这些 widget 可能共享可导航目的地列表。此时可创建 `Destination` widget 保存该信息,并指定 `Destination` 同时包含图标与文本标签。 + Next, you will evaluate your screen size to decide on how to display your UI. +接下来,评估屏幕尺寸以决定如何显示 UI。 + ## Step 2: Measure +## 步骤 2:测量 + ![Step 2: How to measure screen size](/assets/images/docs/ui/adaptive-responsive/measure.png) You have two ways to determine the size of your display area: `MediaQuery` and `LayoutBuilder`. +有两种方式确定显示区域尺寸:`MediaQuery` 与 `LayoutBuilder`。 + +### MediaQuery + ### MediaQuery In the past, you might have used `MediaQuery.of` to @@ -60,6 +91,8 @@ However, devices today feature screens with a wide variety of sizes and shapes, and this test can be misleading. +过去你可能用 `MediaQuery.of` 判断设备屏幕尺寸。但如今设备屏幕尺寸与形状多样,该判断可能误导。 + For example, maybe your app currently occupies a small window on a large screen. If you use the `MediaQuery.of` method and conclude the screen to be small @@ -69,23 +102,31 @@ app's window to lock to the center of the screen, surrounded with black. This is hardly an ideal UI on a large screen. +例如,应用可能只占大屏上的小窗口。若用 `MediaQuery.of` 得出屏幕很小(实则大屏上的小窗口),且应用锁定竖屏,会导致应用窗口锁定在屏幕中央并带黑边,在大屏上绝非理想 UI。 + :::note The Material Guidelines encourage you to never _portrait lock_ your app (by disabling landscape mode). However, if you feel you really must, then at least define the portrait mode to work in top-down mode as well as bottom up. + +Material 指南鼓励你永远不要 _锁定竖屏_(禁用横屏)。但若你确实必须,至少让竖屏同时支持自上而下与自下而上模式。 ::: Keep in mind that `MediaQuery.sizeOf` returns the current size of the app's entire screen and not just a single widget. +请记住,`MediaQuery.sizeOf` 返回的是应用整个屏幕的当前尺寸,而非单个 widget。 + You have two ways to measure your screen space. You can use either `MediaQuery.sizeOf` or `LayoutBuilder`, depending on whether you want the size of the whole app window, or more local sizing. +测量屏幕空间有两种方式:根据你需要整个应用窗口尺寸还是更局部的尺寸,使用 `MediaQuery.sizeOf` 或 `LayoutBuilder`。 + If you want your widget to be fullscreen, even when the app window is small, use `MediaQuery.sizeOf` so you can choose the @@ -94,6 +135,8 @@ In the previous section, you want to base the sizing behavior on the entire app's window, so you would use `MediaQuery.sizeOf`. +若希望 widget 全屏显示(即使应用窗口很小),请用 `MediaQuery.sizeOf`,以便根据应用窗口尺寸选择 UI。上一节中,你希望基于整个应用窗口决定尺寸行为,因此应使用 `MediaQuery.sizeOf`。 + :::secondary Why use `MediaQuery.sizeOf` instead of `MediaQuery.of`? Previous advice recommended that you use the `of` method of `MediaQuery` to obtain the app window's dimensions. @@ -109,6 +152,10 @@ The logical pixel dimensions generally works best as its roughly the same visual size across all devices. The `MediaQuery` class has other specialized functions for each of its individual properties for the same reason. + +此前建议使用 `MediaQuery` 的 `of` 方法获取应用窗口尺寸。为何建议已变?简而言之:**出于性能。** + +`MediaQuery` 包含大量数据,但若你只关心 size 属性,使用 `sizeOf` 更高效。两种方法均返回应用窗口的逻辑像素尺寸(亦称 _密度无关像素_)。逻辑像素在各设备上视觉尺寸大致相同,通常最合适。出于同样原因,`MediaQuery` 类对其各属性也有专门函数。 ::: Requesting the size of the app window from inside @@ -116,11 +163,17 @@ the `build` method, as in `MediaQuery.sizeOf(context)`, causes the given `BuildContext` to rebuild any time the size property changes. +在 `build` 方法内请求应用窗口尺寸(如 `MediaQuery.sizeOf(context)`)会在 size 属性变化时使该 `BuildContext` 重建。 + +### LayoutBuilder + ### LayoutBuilder `LayoutBuilder` accomplishes a similar goal as `MediaQuery.sizeOf`, with some distinctions. +`LayoutBuilder` 与 `MediaQuery.sizeOf` 目标类似,但有区别。 + Rather than providing the size of the app's window, `LayoutBuilder` provides the layout constraints from the parent `Widget`. This means that you get @@ -133,13 +186,19 @@ and height ranges (minimum and maximum) for the content, rather than just a fixed size. This can be useful for custom widgets. +`LayoutBuilder` 不提供应用窗口尺寸,而提供父 `Widget` 的布局约束。即根据你在 widget 树中添加 `LayoutBuilder` 的位置获取尺寸信息。此外,`LayoutBuilder` 返回 `BoxConstraints` 而非 `Size`,因此给出内容有效的宽高范围(最小与最大),而非固定尺寸,这对自定义 widget 很有用。 + For example, imagine a custom widget, where you want the sizing to be based on the space specifically given to that widget, and not the app window in general. In this scenario, use `LayoutBuilder`. +例如,自定义 widget 希望尺寸基于分配给该 widget 的空间,而非整个应用窗口,此时请用 `LayoutBuilder`。 + ## Step 3: Branch +## 步骤 3:分支 + ![Step 3: Branch the code based on the desired UI](/assets/images/docs/ui/adaptive-responsive/branch.png) At this point, you must decide what sizing breakpoints to use @@ -150,13 +209,19 @@ and a nav rail for those that are 600 pixels wide or greater. Again, your choice shouldn't depend on the _type_ of device, but on the device's available window size. +此时,你必须决定在选择显示哪版 UI 时使用哪些尺寸断点。例如,[Material layout][] 指南建议窗口宽度小于 600 逻辑像素时使用底部导航栏,600 像素及以上使用导航 rail。再次强调,选择不应依赖设备 _类型_,而应依赖设备可用窗口尺寸。 + [Material layout]: https://m3.material.io/foundations/layout/applying-layout/window-size-classes To work through an example that switches between a `NavigationRail` and a `NavigationBar`, check out the [Building an animated responsive app layout with Material 3][codelab]. +要了解在 `NavigationRail` 与 `NavigationBar` 之间切换的示例,请参阅 [Building an animated responsive app layout with Material 3][codelab] Codelab。 + [codelab]: {{site.codelabs}}/codelabs/flutter-animated-responsive-layout The next page discusses how to ensure that your app looks best on large screens and foldables. + +下一页讨论如何确保应用在大屏与折叠屏上表现最佳。 diff --git a/sites/docs/src/content/ui/adaptive-responsive/idioms.md b/sites/docs/src/content/ui/adaptive-responsive/idioms.md index f1542c398b..defe6c4a3f 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/idioms.md +++ b/sites/docs/src/content/ui/adaptive-responsive/idioms.md @@ -1,9 +1,15 @@ --- -title: Platform idioms +# title: Platform idioms +title: 平台惯用法 +# description: >- +# Learn how to create a responsive app +# that responds to changes in the screen size. +# shortTitle: Idioms description: >- - Learn how to create a responsive app - that responds to changes in the screen size. -shortTitle: Idioms + 了解如何创建能响应屏幕尺寸变化的 + 响应式应用。 +shortTitle: 惯用法 +ai-translated: true --- @@ -16,12 +22,18 @@ users are accustomed to more customized experiences, but reflecting these platform standards can still provide significant benefits: +自适应应用还需考虑的最后一块是平台标准。各平台有自己的惯用法与规范;这些名义上或事实上的标准塑造用户对应用应如何行为的预期。部分得益于 Web,用户已习惯更定制的体验,但体现这些平台标准仍能带来显著好处: + * **Reduce cognitive load** : By matching the user's existing mental model, accomplishing tasks becomes intuitive, which requires less thinking, boosts productivity, and reduces frustrations. + + **降低认知负担** +:通过匹配用户既有心智模型,完成任务更直观,减少思考、提升效率并降低挫败感。 + * **Build trust** : Users can become wary or suspicious when applications don't adhere to their expectations. @@ -30,8 +42,14 @@ significant benefits: This often has the added benefit of better app store ratings—something we can all appreciate! + + **建立信任** +:应用不符合预期时用户可能警惕或怀疑。反之,熟悉的 UI 能建立信任并提升质量感知,往往还能带来更好的应用商店评分——我们都乐见其成! + ## Consider expected behavior on each platform +## 考虑各平台的预期行为 + The first step is to spend some time considering what the expected appearance, presentation, or behavior is on this platform. @@ -39,11 +57,15 @@ Try to forget any limitations of your current implementation, and just envision the ideal user experience. Work backwards from there. +第一步是花时间思考该平台上的预期外观、呈现或行为。尽量忘记当前实现的限制,只设想理想用户体验,再倒推实现。 + Another way to think about this is to ask, "How would a user of this platform expect to achieve this goal?" Then, try to envision how that would work in your app without any compromises. +另一种思考方式是问:「该平台用户会如何预期达成此目标?」然后设想在你的应用中如何无妥协地实现。 + This can be difficult if you aren't a regular user of the platform. You might be unaware of the specific idioms and can easily miss them completely. For example, a lifetime Android user is @@ -52,8 +74,12 @@ and the same holds true for macOS, Linux, and Windows. These differences might be subtle to you, but be painfully obvious to an experienced user. +若你不是该平台的常用用户,这可能较难。你可能不了解具体惯用法并完全遗漏。例如,长期使用 Android 的用户可能不了解 iOS 平台惯例,macOS、Linux、Windows 同理。这些差异对你可能细微,对资深用户却可能非常明显。 + ### Find a platform advocate +### 寻找平台倡导者 + If possible, assign someone as an advocate for each platform. Ideally, your advocate uses the platform as their primary device, and can offer the perspective of a highly opinionated user. @@ -61,6 +87,8 @@ To reduce the number of people, combine roles. Have one advocate for Windows and Android, one for Linux and the web, and one for Mac and iOS. +若可能,为每个平台指定一名倡导者。理想情况下,倡导者以该平台为主要设备,并能提供挑剔用户的视角。为减少人数可合并角色:一人负责 Windows 与 Android,一人负责 Linux 与 Web,一人负责 Mac 与 iOS。 + The goal is to have constant, informed feedback so the app feels great on each platform. Advocates should be encouraged to be quite picky, calling out anything they feel differs from @@ -70,45 +98,65 @@ and Linux, but is on the right on Windows. Details like that are easy to miss if you aren't using a platform on a regular basis. +目标是获得持续、专业的反馈,使应用在各平台都出色。应鼓励倡导者挑剔,指出与设备上典型应用的差异。简单例子:对话框默认按钮在 Mac 与 Linux 通常在左侧,Windows 则在右侧。若不常用某平台,此类细节易被忽略。 + :::secondary Important Advocates don't need to be developers or even full-time team members. They can be designers, stakeholders, or external testers that are provided with regular builds. + +倡导者不必是开发者或全职成员,可以是设计师、利益相关方或定期获得构建的外部测试者。 ::: ### Stay unique +### 保持独特 + Conforming to expected behaviors doesn't mean that your app needs to use default components or styling. Many of the most popular multiplatform apps have very distinct and opinionated UIs including custom buttons, context menus, and title bars. +符合预期行为并不意味着必须使用默认 widget 或样式。许多热门多平台应用有鲜明、主观的 UI,包括自定义按钮、上下文菜单与标题栏。 + The more you can consolidate styling and behavior across platforms, the easier development and testing will be. The trick is to balance creating a unique experience with a strong identity, while respecting the norms of each platform. +跨平台整合样式与行为越多,开发与测试越轻松。诀窍是在打造强识别度的独特体验与尊重各平台规范之间取得平衡。 + ## Common idioms and norms to consider +## 常见惯用法与规范 + Take a quick look at a few specific norms and idioms you might want to consider, and how you could approach them in Flutter. +快速浏览你可能要考虑的若干规范与惯用法,以及在 Flutter 中的实现思路。 + ### Scrollbar appearance and behavior +### 滚动条外观与行为 + Desktop and mobile users expect scrollbars, but they expect them to behave differently on different platforms. Mobile users expect smaller scrollbars that only appear while scrolling, whereas desktop users generally expect omnipresent, larger scrollbars that they can click or drag. +桌面与移动用户都预期有滚动条,但各平台行为不同。移动用户预期较小、仅在滚动时出现的滚动条;桌面用户通常预期常驻、较大且可点击或拖动的滚动条。 + Flutter comes with a built-in `Scrollbar` widget that already has support for adaptive colors and sizes according to the current platform. The one tweak you might want to make is to toggle `alwaysShown` when on a desktop platform: +Flutter 自带 `Scrollbar` widget,已根据当前平台支持自适应颜色与尺寸。你可能只需在桌面平台切换 `alwaysShown`: + ```dart return Scrollbar( @@ -127,11 +175,17 @@ return Scrollbar( This subtle attention to detail can make your app feel more comfortable on a given platform. +这些细节关注能让应用在给定平台上更舒适。 + ### Multi-select +### 多选 + Dealing with multi-select within a list is another area with subtle differences across platforms: +列表内多选是另一存在跨平台细微差异的领域: + ```dart static bool get isSpanSelectModifierDown => @@ -141,6 +195,8 @@ static bool get isSpanSelectModifierDown => To perform a platform-aware check for control or command, you can write something like this: +要进行平台感知的 Control 或 Command 检查,可编写类似代码: + ```dart static bool get isMultiSelectModifierDown { @@ -165,8 +221,12 @@ If you have a large list of items of selectable items, many of your keyboard users will expect that they can use `Control+A` to select all the items. +键盘用户还需考虑 **全选** 操作。若有大量可选列表项,许多键盘用户会预期可用 `Control+A` 全选。 + #### Touch devices +#### 触控设备 + On touch devices, multi-selection is typically simplified, with the expected behavior being similar to having the `isMultiSelectModifier` down on the desktop. @@ -174,20 +234,30 @@ You can select or deselect items using a single tap, and will usually have a button to **Select All** or **Clear** the current selection. +在触控设备上,多选通常更简单,预期行为类似桌面按住 `isMultiSelectModifier`。可用单击选择或取消选择,通常还有 **全选** 或 **清除** 当前选择的按钮。 + How you handle multi-selection on different devices depends on your specific use cases, but the important thing is to make sure that you're offering each platform the best interaction model possible. +不同设备上的多选处理取决于具体用例,重要的是为各平台提供最佳交互模型。 + ### Selectable text +### 可选中文本 + A common expectation on the web (and to a lesser extent desktop) is that most visible text can be selected with the mouse cursor. When text is not selectable, users on the web tend to have an adverse reaction. +Web(以及程度较轻的桌面)上常见预期是:大部分可见文字可用鼠标选中。文字不可选时,Web 用户往往反感。 + Luckily, this is easy to support with the [`SelectableText`][] widget: +幸运的是,用 [`SelectableText`][] widget 即可轻松支持: + ```dart return const SelectableText('Select me!'); @@ -195,6 +265,8 @@ return const SelectableText('Select me!'); To support rich text, then use `TextSpan`: +要支持富文本,请使用 `TextSpan`: + ```dart return const SelectableText.rich( @@ -214,37 +286,53 @@ return const SelectableText.rich( ### Title bars +### 标题栏 + On modern desktop applications, it's common to customize the title bar of your app window, adding a logo for stronger branding or contextual controls to help save vertical space in your main UI. +在现代桌面应用中,常见做法是自定义应用窗口标题栏,添加 Logo 强化品牌或上下文控件以节省主 UI 垂直空间。 + ![Samples of title bars](/assets/images/docs/ui/adaptive-responsive/titlebar.png){:width="100%"} This isn't supported directly in Flutter, but you can use the [`bits_dojo`][] package to disable the native title bars, and replace them with your own. +Flutter 不直接支持,但可用 [`bits_dojo`][] 包禁用原生标题栏并替换为自定义实现。 + This package lets you add whatever widgets you want to the `TitleBar` because it uses pure Flutter widgets under the hood. This makes it easy to adapt the title bar as you navigate to different sections of the app. +该包底层使用纯 Flutter widget,可在 `TitleBar` 中添加任意 widget,便于在应用不同区域导航时适配标题栏。 + [`bits_dojo`]: {{site.github}}/bitsdojo/bitsdojo_window ### Context menus and tooltips +### 上下文菜单与工具提示 + On desktop, there are several interactions that manifest as a widget shown in an overlay, but with differences in how they're triggered, dismissed, and positioned: +在桌面上,若干交互以叠加层中的 widget 呈现,但触发、关闭与定位方式不同: + * **Context menu** : Typically triggered by a right-click, a context menu is positioned close to the mouse, and is dismissed by clicking anywhere, selecting an option from the menu, or clicking outside it. + + **上下文菜单** +:通常由右键触发,靠近鼠标定位,通过任意点击、选择菜单项或点击外部关闭。 + * **Tooltip** : Typically triggered by hovering for 200-400ms over an interactive element, @@ -252,6 +340,10 @@ and positioned: (as opposed to the mouse position) and is dismissed when the mouse cursor leaves that widget. + + **Tooltip(工具提示)** +:通常在交互元素上悬停 200–400ms 触发,一般锚定到 widget(而非鼠标位置),鼠标离开该 widget 时关闭。 + * **Popup panel (also known as flyout)** : Similar to a tooltip, a popup panel is usually anchored to a widget. @@ -261,9 +353,15 @@ and positioned: Instead, panels are typically dismissed by clicking outside the panel or by pressing a **Close** or **Submit** button. + + **弹出面板(亦称 flyout)** +:与工具提示类似,通常锚定到 widget。主要区别是面板多在点击时显示,光标离开通常不会自动隐藏,而是通过点击面板外部或按 **关闭** / **提交** 按钮关闭。 + To show basic tooltips in Flutter, use the built-in [`Tooltip`][] widget: +在 Flutter 中显示基本工具提示,请使用内置 [`Tooltip`][] widget: + ```dart return const Tooltip( @@ -275,13 +373,25 @@ return const Tooltip( Flutter also provides built-in context menus when editing or selecting text. +Flutter 在编辑或选择文字时也提供内置上下文菜单。 + To show more advanced tooltips, popup panels, or create custom context menus, you either use one of the available packages, or build it yourself using a `Stack` or `Overlay`. +要显示更高级的工具提示、弹出面板或创建自定义上下文菜单,可使用现有包,或用 `Stack` 或 `Overlay` 自行构建。 + Some available packages include: +部分可用包包括: + +* [`context_menus`][] +* [`anchored_popups`][] +* [`flutter_portal`][] +* [`super_tooltip`][] +* [`custom_pop_up_menu`][] + * [`context_menus`][] * [`anchored_popups`][] * [`flutter_portal`][] @@ -295,6 +405,8 @@ and hover for more information. Failing to meet those expectations can lead to disappointed users, or at least, a feeling that something isn't quite right. +这些控件对触控用户可作为加速器很有价值,对鼠标用户则必不可少。他们预期右键、就地编辑、悬停获取更多信息。未满足这些预期会令用户失望,或至少觉得「哪里不对」。 + [`anchored_popups`]: {{site.pub}}/packages/anchored_popups [`context_menus`]: {{site.pub}}/packages/context_menus [`custom_pop_up_menu`]: {{site.pub}}/packages/custom_pop_up_menu @@ -304,15 +416,21 @@ a feeling that something isn't quite right. ### Horizontal button order +### 水平按钮顺序 + On Windows, when presenting a row of buttons, the confirmation button is placed at the start of the row (left side). On all other platforms, it's the opposite. The confirmation button is placed at the end of the row (right side). +在 Windows 上,一行按钮中确认按钮在行首(左侧)。其他平台相反,确认按钮在行尾(右侧)。 + This can be easily handled in Flutter using the `TextDirection` property on `Row`: +在 Flutter 中可用 `Row` 的 `TextDirection` 属性轻松处理: + ```dart TextDirection btnDirection = DeviceType.isWindows @@ -344,57 +462,83 @@ return Row( ### Menu bar +### 菜单栏 + Another common pattern on desktop apps is the menu bar. On Windows and Linux, this menu lives as part of the Chrome title bar, whereas on macOS, it's located along the top of the primary screen. +桌面应用另一常见模式是菜单栏。Windows 与 Linux 上菜单是 Chrome 标题栏的一部分,macOS 上则位于主屏幕顶部。 + Currently, you can specify custom menu bar entries using a prototype plugin, but it's expected that this functionality will eventually be integrated into the main SDK. +目前可用原型插件指定自定义菜单栏项,预计该功能最终会并入主 SDK。 + It's worth mentioning that on Windows and Linux, you can't combine a custom title bar with a menu bar. When you create a custom title bar, you're replacing the native one completely, which means you also lose the integrated native menu bar. +值得一提的是,Windows 与 Linux 上无法将自定义标题栏与菜单栏并存。创建自定义标题栏会完全替换原生标题栏,因而也会失去集成的原生菜单栏。 + If you need both a custom title bar and a menu bar, you can achieve that by implementing it in Flutter, similar to a custom context menu. +若同时需要自定义标题栏与菜单栏,可在 Flutter 中实现,类似自定义上下文菜单。 + ### Drag and drop +### 拖放 + One of the core interactions for both touch-based and pointer-based inputs is drag and drop. Although this interaction is expected for both types of input, there are important differences to think about when it comes to scrolling lists of draggable items. +触控与指针输入的核心交互之一是拖放。两种输入都预期有此交互,但在可滚动可拖放列表上存在重要差异。 + Generally speaking, touch users expect to see drag handles to differentiate draggable areas from scrollable ones, or alternatively, to initiate a drag by using a long press gesture. This is because scrolling and dragging are both sharing a single finger for input. +一般而言,触控用户预期看到拖动手柄以区分可拖区域与可滚动区域,或通过长按启动拖动,因为滚动与拖动共用单指输入。 + Mouse users have more input options. They can use a wheel or scrollbar to scroll, which generally eliminates the need for dedicated drag handles. If you look at the macOS Finder or Windows Explorer, you'll see that they work this way: you just select an item and start dragging. +鼠标用户输入选项更多,可用滚轮或滚动条滚动,通常无需专用拖动手柄。macOS Finder 或 Windows 资源管理器即如此:选中项即可拖动。 + In Flutter, you can implement drag and drop in many ways. Discussing specific implementations is outside the scope of this article, but some high level options include the following: +在 Flutter 中可用多种方式实现拖放。具体实现超出本文范围,高层选项包括: + * Use the [`Draggable`][] and [`DragTarget`][] APIs directly for a custom look and feel. + 直接使用 [`Draggable`][] 与 [`DragTarget`][] API 实现自定义外观。 + * Hook into `onPan` gesture events, and move an object yourself within a parent `Stack`. + 接入 `onPan` 手势事件,在父 `Stack` 中自行移动对象。 + * Use one of the [pre-made list packages][] on pub.dev. + 使用 pub.dev 上的[预制列表包][pre-made list packages]。 + [`Draggable`]: {{site.api}}/flutter/widgets/Draggable-class.html [`DragTarget`]: {{site.api}}/flutter/widgets/DragTarget-class.html [pre-made list packages]: {{site.pub}}/packages?q=reorderable+list diff --git a/sites/docs/src/content/ui/adaptive-responsive/index.md b/sites/docs/src/content/ui/adaptive-responsive/index.md index 5b9d393b07..8ec3c2e68a 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/index.md +++ b/sites/docs/src/content/ui/adaptive-responsive/index.md @@ -1,11 +1,17 @@ --- -title: Adaptive and responsive design in Flutter +# title: Adaptive and responsive design in Flutter +title: Flutter 中的自适应与响应式设计 +# description: >- +# It's important to create an app, +# whether for mobile or web, +# that responds to size and orientation changes +# and maximizes the use of each platform. +# shortTitle: Adaptive design description: >- - It's important to create an app, - whether for mobile or web, - that responds to size and orientation changes - and maximizes the use of each platform. -shortTitle: Adaptive design + 创建能响应尺寸与方向变化、 + 并充分利用各平台的应用(移动端或 Web)很重要。 +shortTitle: 自适应设计 +ai-translated: true --- ![List of supported platforms](/assets/images/docs/ui/adaptive-responsive/platforms.png) @@ -14,6 +20,8 @@ One of Flutter's primary goals is to create a framework that allows you to develop apps from a single codebase that look and feel great on any platform. +Flutter 的主要目标之一是创建框架,让你从单一代码库开发在任何平台都出色好看的应用。 + This means that your app might appear on screens of many different sizes, from a watch, to a foldable phone with two screens, to a high definition monitor. @@ -21,18 +29,26 @@ And your input device might be a physical or virtual keyboard, a mouse, a touchscreen, or any number of other devices. +这意味着应用可能出现在多种尺寸的屏幕上,从手表、双屏折叠手机到高清显示器。输入设备可能是实体或虚拟键盘、鼠标、触摸屏或其他多种设备。 + Two terms that describe these design concepts are _adaptive_ and _responsive_. Ideally, you'd want your app to be _both_ but what, exactly, does this mean? +描述这些设计概念的两个术语是 _adaptive_(自适应)与 _responsive_(响应式)。理想情况下,你希望应用 _兼具两者_,但这究竟意味着什么? + ## What is responsive vs adaptive? +## 什么是响应式与自适应? + An easy way to think about it is that responsive design is about fitting the UI _into_ the space and adaptive design is about the UI being _usable_ in the space. +一个简单的理解是:响应式设计关乎让 UI _融入_ 空间,自适应设计关乎 UI 在空间内 _可用_。 + So, a responsive app adjusts the placement of design elements to _fit_ the available space. And an adaptive app selects the appropriate layout and @@ -40,16 +56,22 @@ input devices to be usable _in_ the available space. For example, should a tablet UI use bottom navigation or side-panel navigation? +因此,响应式应用调整设计元素位置以 _适应_ 可用空间;自适应应用选择合适布局与输入设备,以便在可用空间内 _可用_。例如,平板 UI 应使用底部导航还是侧边面板导航? + :::note Often adaptive and responsive concepts are collapsed into a single term. Most often, _adaptive design_ is used to refer to both adaptive and responsive. + +通常自适应与响应式概念会合并为一个术语,最常见的是用 _adaptive design_ 同时指代自适应与响应式。 ::: This section covers various aspects of adaptive and responsive design: +本节涵盖自适应与响应式设计的多个方面: + * [General approach][] * [SafeArea & MediaQuery][] * [Large screens & foldables][] @@ -58,6 +80,14 @@ responsive design: * [Best practices for adaptive apps][] * [Additional resources][] + [一般方法][General approach] +* [SafeArea 与 MediaQuery][SafeArea & MediaQuery] +* [大屏与折叠屏][Large screens & foldables] +* [用户输入与无障碍][User input & accessibility] +* [能力与策略][Capabilities & policies] +* [自适应应用最佳实践][Best practices for adaptive apps] +* [更多资源][Additional resources] + [Additional resources]: /ui/adaptive-responsive/more-info [Best practices for adaptive apps]: /ui/adaptive-responsive/best-practices [Capabilities & policies]: /ui/adaptive-responsive/capabilities @@ -70,5 +100,9 @@ responsive design: You might also check out the Google I/O 2024 talk about this subject. + + +你也可以观看 Google I/O 2024 关于此主题的演讲。 + ::: diff --git a/sites/docs/src/content/ui/adaptive-responsive/input.md b/sites/docs/src/content/ui/adaptive-responsive/input.md index 92a9b181db..b75881c6b0 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/input.md +++ b/sites/docs/src/content/ui/adaptive-responsive/input.md @@ -1,9 +1,14 @@ --- -title: User input & accessibility +# title: User input & accessibility +title: 用户输入与无障碍 +# description: >- +# A truly adaptive app also handles differences +# in how user input works and also programs +# to help folks with accessibility issues. description: >- - A truly adaptive app also handles differences - in how user input works and also programs - to help folks with accessibility issues. + 真正自适应的应用还需处理用户输入方式的差异, + 并兼顾无障碍辅助功能。 +ai-translated: true --- @@ -14,10 +19,14 @@ The mouse and keyboard introduce input types beyond those found on a touch device, like scroll wheel, right-click, hover interactions, tab traversal, and keyboard shortcuts. +仅适配应用外观不够,还需支持多种用户输入。鼠标与键盘带来触控设备之外的输入类型,如滚轮、右键、悬停交互、Tab 遍历与键盘快捷键。 + Some of these features work by default on Material widgets. But, if you've created a custom widget, you might need to implement them directly. +部分功能在 Material widget 上默认可用。但若创建了自定义 widget,可能需要自行实现。 + Some features that encompass a well-designed app, also help users who work with assistive technologies. For example, aside from being **good app design**, @@ -28,19 +37,27 @@ In addition to the standard advice for info for creating apps that are both adaptive _and_ accessible. +优秀应用设计的部分功能也有助使用辅助技术的用户。例如,除属于**良好应用设计**外,Tab 遍历与键盘快捷键等对_使用辅助设备的用户至关重要_。除[创建无障碍应用][creating accessible apps]的标准建议外,本页涵盖同时实现自适应_与_无障碍的信息。 + [creating accessible apps]: /ui/accessibility ## Scroll wheel for custom widgets +## 自定义 widget 的滚轮 + Scrolling widgets like `ScrollView` or `ListView` support the scroll wheel by default, and because almost every scrollable custom widget is built using one of these, it works with those as well. +`ScrollView` 或 `ListView` 等滚动 widget 默认支持滚轮,几乎所有可滚动自定义 widget 都基于它们构建,因此同样有效。 + If you need to implement custom scroll behavior, you can use the [`Listener`][] widget, which lets you customize how your UI reacts to the scroll wheel. +若需实现自定义滚动行为,可使用 [`Listener`][] widget 自定义 UI 对滚轮的反应。 + ```dart return Listener( @@ -55,15 +72,21 @@ return Listener( ## Tab traversal and focus interactions +## Tab 遍历与焦点交互 + Users with physical keyboards expect that they can use the tab key to quickly navigate an application, and users with motor or vision differences often rely completely on keyboard navigation. +使用实体键盘的用户预期可用 Tab 键快速导航应用,运动或视觉差异用户往往完全依赖键盘导航。 + There are two considerations for tab interactions: how focus moves from widget to widget, known as traversal, and the visual highlight shown when a widget is focused. +Tab 交互需考虑两点:焦点如何在 widget 间移动(遍历),以及 widget 获焦时的视觉高亮。 + Most built-in components, like buttons and text fields, support traversal and highlights by default. If you have your own widget that you want included in @@ -74,6 +97,8 @@ and shortcuts together in one widget. You can create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights. +按钮、文本字段等大多数内置 widget 默认支持遍历与高亮。若希望自定义 widget 参与遍历,可用 [`FocusableActionDetector`][] 创建控件。该 widget 便于在一个 widget 中组合焦点、鼠标输入与快捷键;可创建定义 action 与键绑定的检测器,并提供处理焦点与悬停高亮的回调。 + ```dart class _BasicActionDetectorState extends State { @@ -118,14 +143,20 @@ class _BasicActionDetectorState extends State { ### Controlling traversal order +### 控制遍历顺序 + To get more control over the order that widgets are focused on when the user tabs through, you can use [`FocusTraversalGroup`][] to define sections of the tree that should be treated as a group when tabbing. +要更精确控制用户 Tab 时 widget 的聚焦顺序,可用 [`FocusTraversalGroup`][] 定义 Tab 时应作为一组处理的树片段。 + For example, you might to tab through all the fields in a form before tabbing to the submit button: +例如,可先 Tab 遍历表单所有字段,再 Tab 到提交按钮: + ```dart return Column( @@ -142,10 +173,14 @@ This class usually works well, but it's possible to modify this using another predefined `TraversalPolicy` class or by creating a custom policy. +Flutter 有多种内置遍历 widget 与组的方式,默认使用 `ReadingOrderTraversalPolicy`。该类通常表现良好,也可改用其他预定义 `TraversalPolicy` 或创建自定义策略。 + [`FocusTraversalGroup`]: {{site.api}}/flutter/widgets/FocusTraversalGroup-class.html ## Keyboard accelerators +## 键盘加速器 + In addition to tab traversal, desktop and web users are accustomed to having various keyboard shortcuts bound to specific actions. Whether it's the `Delete` key for quick deletions or @@ -154,13 +189,19 @@ accelerators your users expect. The keyboard is a powerful input tool, so try to squeeze as much efficiency from it as you can. Your users will appreciate it! +除 Tab 遍历外,桌面与 Web 用户习惯将各种快捷键绑定到特定操作。无论是 `Delete` 快速删除还是 `Control+N` 新建文档,务必考虑用户预期的加速器。键盘是强大的输入工具,尽量榨取其效率,用户会感谢你! + Keyboard accelerators can be accomplished in a few ways in Flutter, depending on your goals. +在 Flutter 中实现键盘加速器有多种方式,取决于你的目标。 + If you have a single widget like a `TextField` or a `Button` that already has a focus node, you can wrap it in a [`KeyboardListener`][] or a [`Focus`][] widget and listen for keyboard events: +若单个 widget(如已有焦点节点的 `TextField` 或 `Button`)需处理快捷键,可用 [`KeyboardListener`][] 或 [`Focus`][] 包裹并监听键盘事件: + ```dart @override @@ -186,6 +227,8 @@ or a [`Focus`][] widget and listen for keyboard events: To apply a set of keyboard shortcuts to a large section of the tree, use the [`Shortcuts`][] widget: +要对树中较大部分应用一组键盘快捷键,请使用 [`Shortcuts`][] widget: + ```dart // Define a class for each type of shortcut action you want @@ -218,12 +261,16 @@ The [`Shortcuts`][] widget is useful because it only allows shortcuts to be fired when this widget tree or one of its children has focus and is visible. +[`Shortcuts`][] 很有用,因为仅当该 widget 树或其子级有焦点且可见时才触发快捷键。 + The final option is a global listener. This listener can be used for always-on, app-wide shortcuts or for panels that can accept shortcuts whenever they're visible (regardless of their focus state). Adding global listeners is easy with [`HardwareKeyboard`][]: +最后一种选择是全局监听器,可用于始终开启的应用级快捷键,或面板可见时(无论焦点状态)接受快捷键。用 [`HardwareKeyboard`][] 添加全局监听器很简单: + ```dart @override @@ -244,6 +291,8 @@ you can use the `HardwareKeyboard.instance.logicalKeysPressed` set. For example, a method like the following can check whether any of the provided keys are being held down: +用全局监听器检查键组合时,可使用 `HardwareKeyboard.instance.logicalKeysPressed` 集合。例如,以下方法可检查是否按住所提供的任一键: + ```dart static bool isKeyDown(Set keys) { @@ -256,6 +305,8 @@ static bool isKeyDown(Set keys) { Putting these two things together, you can fire an action when `Shift+N` is pressed: +将两者结合,可在按下 `Shift+N` 时触发操作: + ```dart bool _handleKey(KeyEvent event) { @@ -283,31 +334,43 @@ important when you're binding a Delete/Backspace accelerator for `Delete`, but then have child `TextFields` that the user might be typing in. +使用静态监听器时注意:用户正在字段中输入或关联 widget 不可见时,通常需禁用它。与 `Shortcuts` 或 `KeyboardListener` 不同,这由你自行管理。绑定 Delete/Backspace 加速器时尤其重要,若子级有用户可能正在输入的 `TextField`。 + [`HardwareKeyboard`]: {{site.api}}/flutter/services/HardwareKeyboard-class.html [`KeyboardListener`]: {{site.api}}/flutter/widgets/KeyboardListener-class.html ## Mouse enter, exit, and hover for custom widgets {:#custom-widgets} +## 自定义 widget 的鼠标进入、离开与悬停 {:#custom-widgets} + On desktop, it's common to change the mouse cursor to indicate the functionality about the content the mouse is hovering over. For example, you typically see a hand cursor when you hover over a button, or an `I` cursor when you hover over text. +在桌面上,常通过改变鼠标光标指示悬停内容的功能。例如,悬停按钮时通常为手型光标,悬停文字时为 `I` 型光标。 + Flutter's Material buttons handle basic focus states for standard button and text cursors. (A notable exception is if you change the default styling of the Material buttons to set the `overlayColor` to transparent.) +Flutter 的 Material 按钮处理标准按钮与文字光标的基本焦点状态。(例外:若将 Material 按钮默认样式的 `overlayColor` 设为透明。) + Implement a focus state for any custom buttons or gesture detectors in your app. If you change the default Material button styles, test for keyboard focus states and implement your own, if needed. +为应用中任何自定义按钮或手势检测器实现焦点状态。若更改默认 Material 按钮样式,请测试键盘焦点状态并在需要时自行实现。 + To change the cursor from within your custom widgets, use [`MouseRegion`][]: +在自定义 widget 内更改光标,请使用 [`MouseRegion`][]: + ```dart // Show hand cursor @@ -327,6 +390,8 @@ return MouseRegion( `MouseRegion` is also useful for creating custom rollover and hover effects: +`MouseRegion` 也适用于创建自定义 rollover 与悬停效果: + ```dart return MouseRegion( @@ -347,14 +412,20 @@ The app modifies the [`FocusNode.hasFocus`][] property to check whether the button has focus and, if so, adds an outline. +有关在获焦时为按钮添加轮廓样式的示例,请参阅 [Wonderous 应用的按钮代码][button code for the Wonderous app]。应用通过 [`FocusNode.hasFocus`][] 检查按钮是否获焦并添加轮廓。 + [button code for the Wonderous app]: {{site.github}}/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/common/controls/buttons.dart#L143 [`FocusNode.hasFocus`]: {{site.api}}/flutter/widgets/FocusNode/hasFocus.html ## Visual density +## 视觉密度 + You might consider enlarging the "hit area" of a widget to accommodate a touch screen, for example. +例如,你可能考虑扩大 widget 的「点击区域」以适配触摸屏。 + Different input devices offer various levels of precision, which necessitate differently-sized hit areas. Flutter's `VisualDensity` class makes it easy to adjust the @@ -362,6 +433,8 @@ density of your views across the entire application, for example, by making a button larger (and therefore easier to tap) on a touch device. +不同输入设备精度不同,需要不同尺寸的点击区域。Flutter 的 `VisualDensity` 类便于在全应用调整视图密度,例如在触控设备上让按钮更大(更易点击)。 + When you change the `VisualDensity` for your `MaterialApp`, `MaterialComponents` that support it animate their densities to match. @@ -371,11 +444,15 @@ negative or positive value that you want. By switching between different densities, you can easily adjust your UI. +当你为 `MaterialApp` 更改 `VisualDensity` 时,支持它的 `MaterialComponents` 会动画化其密度以匹配。默认情况下,水平和垂直密度均设为 0.0,但你可以将密度设为任意负值或正值。在不同密度之间切换,可轻松调整 UI。 + ![Adaptive scaffold](/assets/images/docs/ui/adaptive-responsive/adaptive_scaffold.webp){:width="100%"} To set a custom visual density, inject the density into your `MaterialApp` theme: +要设置自定义视觉密度,将密度注入 `MaterialApp` 主题: + ```dart double densityAmt = touchMode ? 0.0 : -1.0; @@ -393,6 +470,8 @@ return MaterialApp( To use `VisualDensity` inside your own views, you can look it up: +在自有视图中使用 `VisualDensity` 时,可查找: + ```dart VisualDensity density = Theme.of(context).visualDensity; @@ -404,6 +483,8 @@ This ties together your custom components, along with the built-in components, for a smooth transition effect across the app. +容器不仅会自动响应密度变化,变化时还会动画,将自定义与内置 widget 串联,在全应用实现平滑过渡。 + As shown, `VisualDensity` is unit-less, so it can mean different things to different views. In the following example, 1 density unit equals 6 pixels, @@ -411,6 +492,8 @@ but this is totally up to you to decide. The fact that it is unit-less makes it quite versatile, and it should work in most contexts. +如上所示,`VisualDensity` 无单位,对不同视图含义可不同。以下示例中 1 密度单位等于 6 像素,但完全由你决定。无单位使其相当灵活,在多数场景应能适用。 + It's worth noting that the Material generally use a value of around 4 logical pixels for each visual density unit. For more information about the @@ -418,5 +501,7 @@ supported components, see the [`VisualDensity`][] API. For more information about density principles in general, see the [Material Design guide][]. +值得注意的是,Material 通常每个视觉密度单位约 4 逻辑像素。有关受支持 widget 的更多信息,请参阅 [`VisualDensity`][] API。有关密度原则的更多信息,请参阅 [Material Design 指南][Material Design guide]。 + [Material Design guide]: {{site.material2}}/design/layout/applying-density.html#usage [`VisualDensity`]: {{site.api}}/flutter/material/VisualDensity-class.html diff --git a/sites/docs/src/content/ui/adaptive-responsive/large-screens.md b/sites/docs/src/content/ui/adaptive-responsive/large-screens.md index 73f2f136d7..0117b1b628 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/large-screens.md +++ b/sites/docs/src/content/ui/adaptive-responsive/large-screens.md @@ -1,9 +1,14 @@ --- -title: Large screen devices +# title: Large screen devices +title: 大屏设备 +# description: >- +# Things to keep in mind when adapting apps +# to large screens. +# shortTitle: Large screens description: >- - Things to keep in mind when adapting apps - to large screens. -shortTitle: Large screens + 将应用适配大屏时需注意的事项。 +shortTitle: 大屏 +ai-translated: true --- @@ -11,11 +16,15 @@ shortTitle: Large screens This page provides guidance on optimizing your app to improve its behavior on large screens. +本页提供优化应用以改善其在大屏上行为的指导。 + Flutter, like Android, defines [large screens][] as tablets, foldables, and ChromeOS devices running Android. Flutter _also_ defines large screen devices as web, desktop, and iPads. +Flutter 与 Android 一样,将[大屏][large screens]定义为平板、折叠屏以及运行 Android 的 ChromeOS 设备。Flutter _还_ 将 Web、桌面与 iPad 视为大屏设备。 + :::secondary Why do large screens matter, in particular? Demand for large screens continues to increase. As of January 2024, @@ -35,6 +44,15 @@ For example, it: large screen support. * Ensures that your app meets iPadOS submission guidelines and is [accepted in the App Store][]. + +大屏需求持续增长。 +截至 2024 年 1 月,Android 上活跃[大屏][large screens]与折叠设备超过 2.7 亿台,[iPad 用户超过 1490 万][14.9 million iPad users]。 + +应用支持大屏时还会获得其他好处。优化应用以填满屏幕,例如: + +* 提升应用用户参与度指标。 +* 提高应用在 Play Store 的可见度。近期 [Play Store 更新][Play Store updates] 按设备类型显示评分,并标明应用是否缺少大屏支持。 +* 确保应用符合 iPadOS 提交指南并在 [App Store 获准上架][accepted in the App Store]。 ::: [14.9 million iPad users]: https://www.statista.com/statistics/299632/tablet-shipments-apple/ @@ -44,6 +62,8 @@ For example, it: ## Layout with GridView +## 使用 GridView 布局 + Consider the following screenshots of an app. The app displays its UI in a `ListView`. The image on the left shows the app running @@ -51,36 +71,54 @@ on a mobile device. The image on the right shows the app running on a large screen device _before the advice on this page was applied_. +请看以下应用截图。应用在 `ListView` 中显示 UI。左图为移动设备上的运行效果,右图为应用在大屏设备上运行、且 _尚未应用本页建议_ 时的效果。 + ![Sample of large screen](/assets/images/docs/ui/adaptive-responsive/large-screen.png){:width="90%"} This is not optimal. +这并不理想。 + The [Android Large Screen App Quality Guidelines][guidelines] and the [iOS equivalent][] say that neither text nor boxes should take up the full screen width. How to solve this in an adaptive way? +[Android 大屏应用质量指南][guidelines] 与 [iOS 对应指南][iOS equivalent] 均指出,文字与方框都不应占满全屏宽度。如何以自适应方式解决? + [guidelines]: https://developer.android.com/docs/quality-guidelines/large-screen-app-quality [iOS equivalent]: https://developer.apple.com/design/human-interface-guidelines/designing-for-ipados A common solution uses `GridView`, as shown in the next section. +常见方案是使用 `GridView`,如下一节所示。 + +### GridView + ### GridView You can use the `GridView` widget to transform your existing `ListView` into more reasonably-sized items. +可用 `GridView` widget 将现有 `ListView` 转为尺寸更合理的项。 + `GridView` is similar to the [`ListView`][] widget, but instead of handling only a list of widgets arranged linearly, `GridView` can arrange widgets in a two-dimensional array. +`GridView` 与 [`ListView`][] widget 类似,但不仅能线性排列 widget 列表,还能在二维数组中排列 widget。 + `GridView` also has constructors that are similar to `ListView`. The `ListView` default constructor maps to `GridView.count`, and `ListView.builder` is similar to `GridView.builder`. +`GridView` 也有与 `ListView` 类似的构造函数。`ListView` 默认构造函数对应 `GridView.count`,`ListView.builder` 类似 `GridView.builder`。 + `GridView` has some additional constructors for more custom layouts. To learn more, visit the [`GridView`][] API page. +`GridView` 还有用于更自定义布局的额外构造函数。更多信息请访问 [`GridView`][] API 页面。 + [`GridView`]: {{site.api}}/flutter/widgets/GridView-class.html [`ListView`]: {{site.api}}/flutter/widgets/ListView-class.html @@ -90,19 +128,31 @@ If your app has a large number of items, it's recommended to use this builder constructor to only build the item widgets that are actually visible. +例如,若原应用使用 `ListView.builder`,可换成 `GridView.builder`。若项很多,建议使用 builder 构造函数仅构建实际可见的项 widget。 + Most of the parameters in the constructor are the same between the two widgets, so it's a straightforward swap. However, you need to figure out what to set for the `gridDelegate`. +两个 widget 的构造函数参数大多相同,替换较直接。但需确定 `gridDelegate` 的设置。 + Flutter provides powerful premade `gridDelegates` that you can use, namely: +Flutter 提供强大的预制 `gridDelegates`,可供使用,即: + [`SliverGridDelegateWithFixedCrossAxisCount`][] : Lets you assign a specific number of columns to your grid. [`SliverGridDelegateWithMaxCrossAxisExtent`][] : Lets you define a max item width. +[`SliverGridDelegateWithFixedCrossAxisCount`][] +:为网格指定固定列数。 + +[`SliverGridDelegateWithMaxCrossAxisExtent`][] +:定义项的最大宽度。 + [`SliverGridDelegateWithFixedCrossAxisCount`]: {{site.api}}/flutter/rendering/SliverGridDelegateWithFixedCrossAxisCount-class.html [`SliverGridDelegateWithMaxCrossAxisExtent`]: {{site.api}}/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent-class.html @@ -121,28 +171,45 @@ the physical size of the display. Also, Flutter apps can run on web and desktop, which might be sized in many ways. **For this reason, use `MediaQuery` to get the app window size rather than the physical device size.** + +不要使用可直接设置列数并据设备是否为平板等硬编码列数的 grid delegate。 +列数应基于窗口尺寸,而非物理设备尺寸。 + +这一区别很重要,因为许多移动设备支持多窗口模式,应用可能渲染在小于物理显示的空间中。Flutter 应用也可在 Web 与桌面以多种尺寸运行。 +**因此请用 `MediaQuery` 获取应用窗口尺寸,而非物理设备尺寸。** ::: ### Other solutions +### 其他方案 + Another way to approach these situations is to use the `maxWidth` property of `BoxConstraints`. This involves the following: +另一种方式是使用 `BoxConstraints` 的 `maxWidth` 属性,涉及: + * Wrap the `GridView`in a `ConstrainedBox` and give it a `BoxConstraints` with a maximum width set. * Use a `Container` instead of a `ConstrainedBox` if you want other functionality like setting the background color. + 用 `ConstrainedBox` 包裹 `GridView`,并设置带最大宽度的 `BoxConstraints`。 +* 若需设置背景色等其他功能,可用 `Container` 替代 `ConstrainedBox`。 + For choosing the maximum width value, consider using the values recommended by Material 3 in the [Applying layout][] guide. +选择最大宽度时,可考虑 Material 3 在 [Applying layout][] 指南中的推荐值。 + [Applying layout]: https://m3.material.io/foundations/layout/applying-layout/window-size-classes ## Foldables +## 折叠屏 + As mentioned previously, Android and Flutter both recommend in their design guidance **not** to lock screen orientation, @@ -150,19 +217,27 @@ but some apps lock screen orientation anyway. Be aware that this can cause problems when running your app on a foldable device. +如前所述,Android 与 Flutter 设计指南均**不**建议锁定屏幕方向,但部分应用仍会锁定。请注意,在折叠屏上运行可能导致问题。 + When running on a foldable, the app might look ok when the device is folded. But when unfolding, you might find the app letterboxed. +折叠屏折叠时应用可能正常,展开后可能出现 letterboxing(信箱模式)。 + As described in the [SafeArea & MediaQuery][sa-mq] page, letterboxing means that the app's window is locked to the center of the screen while the window is surrounded with black. +如 [SafeArea & MediaQuery][sa-mq] 页所述,letterboxing 指应用窗口锁定在屏幕中央,周围为黑边。 + [sa-mq]: /ui/adaptive-responsive/safearea-mediaquery Why can this happen? +为何会这样? + This can happen when using `MediaQuery` to figure out the window size for your app. When the device is folded, orientation is restricted to portrait mode. @@ -172,23 +247,36 @@ is displayed in a letterboxed state. In the letterboxed state, `MediaQuery` never receives the larger window size that allows the UI to expand. +在使用 `MediaQuery` 确定应用窗口尺寸时可能发生。设备折叠时方向被限制为竖屏。底层 `setPreferredOrientations` 使 Android 使用竖屏兼容模式,应用以 letterboxed 状态显示。此状态下 `MediaQuery` 永远收不到允许 UI 扩展的更大窗口尺寸。 + You can solve this in one of two ways: +有两种解决方式: + * Support all orientations. * Use the dimensions of the _physical display_. In fact, this is one of the _few_ situations where you would use the physical display dimensions and _not_ the window dimensions. + 支持所有方向。 +* 使用 _物理显示_ 的尺寸。事实上,这是 _少数_ 应使用物理显示尺寸而非窗口尺寸的情况之一。 + How to obtain the physical screen dimensions? +如何获取物理屏幕尺寸? + You can use the [`Display`][] API, which contains the size, pixel ratio, and refresh rate of the physical device. +可使用 [`Display`][] API,其中包含物理设备的尺寸、像素比与刷新率。 + [`Display`]: {{site.api}}/flutter/dart-ui/Display-class.html The following sample code retrieves a `Display` object: +以下示例代码获取 `Display` 对象: + ```dart /// AppState object. ui.FlutterView? _view; @@ -209,28 +297,42 @@ view that you care about. This creates a forward-looking API that should handle current _and_ future multi-display and multi-view devices. +重要的是找到你所关心 view 的 display。这形成面向未来的 API,应能处理当前 _及_ 未来的多显示与多 view 设备。 + ## Adaptive input +## 自适应输入 + Adding support for more screens, also means expanding input controls. +支持更多屏幕也意味着扩展输入控件。 + Android guidelines describe three tiers of large format device support. +Android 指南描述了大屏格式设备支持的三个层级。 + ![3 tiers of large format device support](/assets/images/docs/ui/adaptive-responsive/large-screen-guidelines.png){:width="90%"} Tier 3, the lowest level of support, includes support for mouse and stylus input ([Material 3 guidelines][m3-guide], [Apple guidelines][]). +第三层为最低支持级别,包括鼠标与触控笔输入([Material 3 指南][m3-guide]、[Apple 指南][Apple guidelines])。 + If your app uses Material 3 and its buttons and selectors, then your app already has built-in support for various additional input states. +若应用使用 Material 3 及其按钮与选择器,则已内置对各种额外输入状态的支持。 + But what if you have a custom widget? Check out the [User input][] page for guidance on adding [input support for widgets][]. +若有自定义 widget?请参阅 [User input][] 页了解如何添加 [widget 输入支持][input support for widgets]。 + [Apple guidelines]: https://developer.apple.com/design/human-interface-guidelines/designing-for-ipados#Best-practices [input support for widgets]: /ui/adaptive-responsive/input#custom-widgets [m3-guide]: {{site.android-dev}}/docs/quality-guidelines/large-screen-app-quality @@ -238,15 +340,21 @@ guidance on adding ### Navigation +### 导航 + Navigation can create unique challenges when working with a variety of differently-sized devices. Generally, you want to switch between a [`BottomNavigationBar`][] and a [`NavigationRail`] depending on available screen space. +在多种不同尺寸设备上工作时,导航可能带来独特挑战。通常应根据可用屏幕空间在 [`BottomNavigationBar`][] 与 [`NavigationRail`][] 之间切换。 + For more information (and corresponding example code), check out [Problem: Navigation rail][], a section in the [Developing Flutter apps for Large screens][article] article. +更多信息(及对应示例代码)请参阅文章 [Developing Flutter apps for Large screens][article] 中的 [Problem: Navigation rail][] 一节。 + [article]: {{site.flutter-blog}}/developing-flutter-apps-for-large-screens-53b7b0e17f10 [`BottomNavigationBar`]: {{site.api}}/flutter/material/BottomNavigationBar-class.html [`NavigationRail`]: {{site.api}}/flutter/material/NavigationRail-class.html diff --git a/sites/docs/src/content/ui/adaptive-responsive/more-info.md b/sites/docs/src/content/ui/adaptive-responsive/more-info.md index 91f15b8c78..63015c3b87 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/more-info.md +++ b/sites/docs/src/content/ui/adaptive-responsive/more-info.md @@ -1,51 +1,100 @@ --- -title: Additional resources +# title: Additional resources +title: 更多资源 +# description: >- +# Other resources that you might find useful +# when writing adaptive apps. +# shortTitle: Learn description: >- - Other resources that you might find useful - when writing adaptive apps. -shortTitle: Learn + 编写自适应应用时可能有用的其他资源。 +shortTitle: 学习 +ai-translated: true --- ## Examples +## 示例 + If you'd like to see how the adaptive and responsive concepts (as described in these pages) come together, check out the source code for the following apps: +若想了解这些页面所述的自适应与响应式概念如何结合,请查看以下应用的源代码: + * [Wonderous][] + + [Wonderous][] + * [Flutter adaptive demo][] + [Flutter 自适应演示][Flutter adaptive demo] + [Flutter adaptive demo]: {{site.github}}/gskinnerTeam/flutter-adaptive-demo [Wonderous]: {{site.github}}/gskinnerTeam/flutter-wonderous-app ## Learn more about basic usability principles +## 进一步了解基本可用性原则 + Of course, these pages don't constitute an exhaustive list of the things you might consider. The more operating systems, form factors, and input devices you support, the more difficult it becomes to spec out every permutation in design. +当然,这些页面并非你可能考虑的全部事项的详尽列表。支持的操作系统、形态与输入设备越多,在设计中穷举每种组合就越难。 + Taking time to learn basic usability principles as a developer empowers you to make better decisions, reduces back-and-forth iterations with design during production, and results in improved productivity with better outcomes. +作为开发者花时间学习基本可用性原则,能帮你做出更好决策,减少生产中与设计的来回迭代,并以更好结果提升效率。 + Here are some resources that you might find useful: +以下资源可能对你有帮助: + * [Android large screen guidelines][] + + [Android 大屏指南][Android large screen guidelines] + * [Material guidelines on applying layout][] + + [Material 布局应用指南][Material guidelines on applying layout] + * [Material design for large screens][] + + [Material 大屏设计][Material design for large screens] * [Material guidelines on canonical layouts][] + + [Material 规范布局指南][Material guidelines on canonical layouts] + * [Build high quality apps (Android)][] + + [构建高质量应用(Android)][Build high quality apps (Android)] + * [UI design do's and don'ts (Apple)][] + + [UI 设计注意事项(Apple)][UI design do's and don'ts (Apple)] * [Human interface guidelines (Apple)][] + + [人机界面指南(Apple)][Human interface guidelines (Apple)] + * [Responsive design techniques (Microsoft)][] + + [响应式设计技术(Microsoft)][Responsive design techniques (Microsoft)] + * [Machine sizes and breakpoints (Microsoft)][] + + [机器尺寸与断点(Microsoft)][Machine sizes and breakpoints (Microsoft)] + * [How to build Adaptive UI with Flutter][], a Google I/O 2024 video. + [How to build Adaptive UI with Flutter][],Google I/O 2024 视频。 + [Android large screen guidelines]: {{site.android-dev}}/docs/quality-guidelines/large-screen-app-quality [Build high quality apps (Android)]: {{site.android-dev}}/quality [How to build Adaptive UI with Flutter]: {{site.youtube-site}}/watch?v=LeKLGzpsz9I diff --git a/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md b/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md index 823ecb5481..7e75991209 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md +++ b/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md @@ -5,6 +5,7 @@ title: 自动适配不同平台操作体验 description: 了解更多 Flutter 的平台适配机制。 tags: Flutter参考资料 keywords: 平台适配,研究,Flutter Android,Flutter iOS,Flutter跨平台 +ai-translated: true --- ## Adaptation philosophy @@ -699,8 +700,8 @@ Below is a code snippet you can adapt to show a platform-specific navigation bars. 如果你要实现特定于平台的底部导航栏, -可以在 Android 上使用 Flutter 的 `NavigationBar` 小部件, -在 iOS 上使用 `CupertinoTabBar` 小部件。 +可以在 Android 上使用 Flutter 的 `NavigationBar` widget, +在 iOS 上使用 `CupertinoTabBar` widget。 下面是用于显示特定于平台的导航栏的代码片段。 ```dart diff --git a/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md b/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md index e3c18bea86..53a3795dab 100644 --- a/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md +++ b/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md @@ -1,13 +1,22 @@ --- -title: SafeArea & MediaQuery +# title: SafeArea & MediaQuery +title: SafeArea 与 MediaQuery +# description: >- +# Learn how to use SafeArea and MediaQuery +# to create an adaptive app. description: >- - Learn how to use SafeArea and MediaQuery - to create an adaptive app. + 了解如何使用 SafeArea 与 MediaQuery + 创建自适应应用。 +ai-translated: true --- This page discusses how and when to use the `SafeArea` and `MediaQuery` widgets. +本页讨论如何以及何时使用 `SafeArea` 与 `MediaQuery` widget。 + +## SafeArea + ## SafeArea When running your app on the latest devices, @@ -20,39 +29,55 @@ as well as operating system UI (such as the status bar on Android), or by rounded corners of the physical display. +在最新设备上运行应用时,UI 部分可能被屏幕挖孔遮挡。可用 [`SafeArea`][] widget 修复,它会内嵌子 widget 以避免侵入(如刘海与相机挖孔)、操作系统 UI(如 Android 状态栏)或物理屏幕圆角。 + If you don't want this behavior, the `SafeArea` widget allows you to disable padding on any of its four sides. By default, all four sides are enabled. +若不需要此行为,`SafeArea` 允许禁用任一侧的内边距。默认四侧均启用。 + It's generally recommended to wrap the body of a `Scaffold` widget in `SafeArea` as a good place to start, but you don't always need to put it this high in the `Widget` tree. +通常建议将 `Scaffold` widget 的 body 包在 `SafeArea` 中作为起点,但不必总放在 `Widget` 树如此高的位置。 + For example, if you purposefully want your app to stretch under the cutouts, you can move the `SafeArea` to wrap whatever content makes sense, and let the rest of the app take up the full screen. +例如,若有意让应用延伸到挖孔下方,可将 `SafeArea` 移到包裹合适内容的位置,其余部分占满全屏。 + Using `SafeArea` ensures that your app content won't be cut off by physical display features or operating system UI, and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market. +使用 `SafeArea` 可确保应用内容不被物理显示特性或操作系统 UI 裁切,并为日后各种挖孔形状的新设备做好准备。 + How does `SafeArea` do so much in a small amount of code? Behind the scenes it uses the `MediaQuery` object. +`SafeArea` 如何用少量代码做到这些?背后它使用 `MediaQuery` 对象。 + [`SafeArea`]: {{site.api}}/flutter/widgets/SafeArea-class.html ## MediaQuery +## MediaQuery + As discussed in the [SafeArea](#safearea) section, `MediaQuery` is a powerful widget for creating adaptive apps. Sometimes you'll use `MediaQuery` directly, and sometimes you'll use `SafeArea`, which uses `MediaQuery` behind the scenes. +如 [SafeArea](#safearea) 节所述,`MediaQuery` 是创建自适应应用的强大 widget。有时直接使用 `MediaQuery`,有时使用背后依赖 `MediaQuery` 的 `SafeArea`。 + `MediaQuery` provides lots of information, including the app's current window size. It exposes accessibility settings like high contrast mode @@ -61,14 +86,20 @@ service like TalkBack or VoiceOver. `MediaQuery` also contains info about the features of your device's display, such as having a hinge or a fold. +`MediaQuery` 提供大量信息,包括应用当前窗口尺寸。它暴露高对比度、文字缩放等无障碍设置,或用户是否使用 TalkBack、VoiceOver 等无障碍服务。`MediaQuery` 还包含设备显示特性信息,例如是否有铰链或折叠。 + `SafeArea` uses the data from `MediaQuery` to figure out how much to inset its child `Widget`. Specifically, it uses the `MediaQuery` padding property, which is basically the amount of the display that's partially obscured by system UI, display notches, or status bar. +`SafeArea` 使用 `MediaQuery` 数据计算子 `Widget` 的内嵌量,具体使用 `MediaQuery` 的 padding 属性,即被系统 UI、显示挖孔或状态栏部分遮挡的显示区域量。 + So, why not use `MediaQuery` directly? +那么,为何不直接使用 `MediaQuery`? + The answer is that `SafeArea` does one clever thing that makes it beneficial to use over just raw `MediaQueryData`. Specifically, it modifies the `MediaQuery` exposed @@ -78,12 +109,16 @@ This means that you can nest `SafeArea`s, and only the topmost one will apply the padding needed to avoid the notches as system UI. +答案是 `SafeArea` 做了一件事,使其比裸用 `MediaQueryData` 更有利:它修改暴露给 `SafeArea` 子级的 `MediaQuery`,使 `SafeArea` 添加的内边距看似不存在。这意味着可嵌套 `SafeArea`,仅最外层应用避开挖孔与系统 UI 所需的内边距。 + As your app grows and you move widgets around, you don't have to worry about having too much padding applied if you have multiple `SafeArea`s, whereas you would have issues if using `MediaQueryData.padding` directly. +随着应用增长和 widget 移动,多个 `SafeArea` 时不必担心内边距过多;若直接使用 `MediaQueryData.padding` 则会有问题。 + You _can_ wrap the body of a `Scaffold` widget with a `SafeArea`, but you don't _have_ to put it this high in the widget tree. @@ -91,6 +126,8 @@ The `SafeArea` just needs to wrap the contents that would cause information loss if cut off by the hardware features mentioned earlier. +你 _可以_ 用 `SafeArea` 包裹 `Scaffold` 的 body,但不必 _必须_ 放在 widget 树如此高的位置。`SafeArea` 只需包裹若被前述硬件特性裁切会导致信息丢失的内容。 + For example, if you purposefully want your app to stretch under the cutouts, you can move the `SafeArea` to wrap whatever content makes sense, @@ -101,7 +138,11 @@ system status bar. This is also why wrapping the body of a `Scaffold` in a `SafeArea` is recommended, instead of wrapping the whole `Scaffold` itself. +例如,若有意让应用延伸到挖孔下,可将 `SafeArea` 移到包裹合适内容的位置,其余占满全屏。顺带说明,`AppBar` widget 默认如此,因而可延伸到系统状态栏下方。这也是建议用 `SafeArea` 包裹 `Scaffold` body 而非整个 `Scaffold` 的原因。 + `SafeArea` ensures that your app content won't be cut off in a generic way and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market. + +`SafeArea` 以通用方式确保应用内容不被裁切,并为日后各种挖孔形状的新设备做好准备。 diff --git a/sites/docs/src/content/ui/assets/asset-transformation.md b/sites/docs/src/content/ui/assets/asset-transformation.md index d38da5d249..19603857a7 100644 --- a/sites/docs/src/content/ui/assets/asset-transformation.md +++ b/sites/docs/src/content/ui/assets/asset-transformation.md @@ -1,17 +1,27 @@ --- -title: Transforming assets at build time -description: How to set up automatic transformation of images (and other assets) in your Flutter app. -shortTitle: Asset transformation +# title: Transforming assets at build time +title: 在构建时转换资源 +# description: How to set up automatic transformation of images (and other assets) in your Flutter app. +description: 如何在你的 Flutter 应用中设置图片(及其他资源)的自动转换。 +# shortTitle: Asset transformation +shortTitle: 资源转换 +ai-translated: true --- You can configure your project to automatically transform assets at build time using compatible Dart packages. +你可以使用兼容的 Dart 包配置项目,在构建时自动转换资源。 + ## Specifying asset transformations +## 指定资源转换 + In the `pubspec.yaml` file, list the assets to be transformed and the associated transformer package. +在 `pubspec.yaml` 文件中,列出要转换的资源及关联的转换器包。 + ```yaml flutter: assets: @@ -25,6 +35,8 @@ With this configuration, `assets/logo.svg` is transformed by the package precompiles SVG files into an optimized binary files that can be displayed using the [`vector_graphics`][] package, like so: +使用此配置,`assets/logo.svg` 在复制到构建输出时会由 [`vector_graphics_compiler`][] 包进行转换。该包将 SVG 文件预编译为优化的二进制文件,可使用 [`vector_graphics`][] 包显示,如下所示: + ```dart import 'package:vector_graphics/vector_graphics.dart'; @@ -34,9 +46,13 @@ const Widget logo = VectorGraphic(loader: AssetBytesLoader('assets/logo.svg')); ### Passing arguments to asset transformers +### 向资源转换器传递参数 + To pass a string of arguments to an asset transformer, also specify that in the pubspec: +要向资源转换器传递参数字符串,也需在 pubspec 中指定: + ```yaml flutter: assets: @@ -48,10 +64,14 @@ flutter: ### Chaining asset transformers +### 链式资源转换器 + Asset transformers can be chained and are applied in the order they are declared. Consider the following example using imaginary packages: +资源转换器可以链式组合,并按声明顺序应用。考虑以下使用假想包的示例: + ```yaml flutter: assets: @@ -65,29 +85,43 @@ Here, `bird.png` is transformed by the `grayscale_filter` package. The output is then transformed by the `png_optimizer` package before being bundled into the built app. +此处,`bird.png` 先由 `grayscale_filter` 包转换,输出再由 `png_optimizer` 包转换,然后才打包进构建后的应用。 + ## Writing asset transformer packages +## 编写资源转换器包 + An asset transformer is a Dart [command-line app][] that is invoked with `dart run` with at least two arguments: `--input`, which contains the path to the file to transform and `--output`, which is the location where the transformer code must write its output to. +资源转换器是一个通过 `dart run` 调用的 Dart [命令行应用][command-line app],至少需要两个参数:`--input` 包含要转换的文件路径,`--output` 是转换器代码必须写入输出的位置。 + If the transformer finishes with a non-zero exit code, the application build fails with an error message explaining that transformation of the asset failed. Anything written to the [`stderr`] stream of the process by the transformer is included in the error message. +如果转换器以非零退出码结束,应用构建将失败,并附带说明资源转换失败的错误消息。转换器写入进程 [`stderr`] 流的任何内容都会包含在错误消息中。 + During the invocation of the transformer, the `FLUTTER_BUILD_MODE` environment variable will be set to the CLI name of the build mode being used. For example, if you run your app with `flutter run -d macos --release`, then `FLUTTER_BUILD_MODE` will be set to `release`. +在调用转换器期间,`FLUTTER_BUILD_MODE` 环境变量将设为所使用构建模式的 CLI 名称。例如,如果你使用 `flutter run -d macos --release` 运行应用,则 `FLUTTER_BUILD_MODE` 将设为 `release`。 + ## Sample +## 示例 + For a sample Flutter project that uses asset transformation and includes a custom Dart package that is used as a transformer, check out the [asset_transformers project in the Flutter samples repo][]. +要查看使用资源转换并包含用作转换器的自定义 Dart 包的 Flutter 示例项目,请查看 [Flutter 示例仓库中的 asset_transformers 项目][asset_transformers project in the Flutter samples repo]。 + [command-line app]: {{site.dart-site}}/tutorials/server/cmdline [asset_transformers project in the Flutter samples repo]: {{site.repo.samples}}/tree/main/asset_transformation [`vector_graphics_compiler`]: {{site.pub}}/packages/vector_graphics_compiler diff --git a/sites/docs/src/content/ui/assets/assets-and-images.md b/sites/docs/src/content/ui/assets/assets-and-images.md index 1933097e85..cfd865b75b 100644 --- a/sites/docs/src/content/ui/assets/assets-and-images.md +++ b/sites/docs/src/content/ui/assets/assets-and-images.md @@ -147,7 +147,7 @@ string/text asset (`loadString()`) or an image/binary asset (`load()`) out of the bundle, given a logical key. The logical key maps to the path to the asset specified in the `pubspec.yaml` file at build time. -Asset bundle 通过指定一个逻辑键(key),允许你读取 string/text(`loadString`) +Asset bundle 通过指定一个逻辑键(key),让你读取 string/text(`loadString`) 和 image/binary(`load`)。在编译期间, 这个逻辑键(key)会映射在 `pubspec.yaml` 中指定的资源路径。 diff --git a/sites/docs/src/content/ui/assets/index.md b/sites/docs/src/content/ui/assets/index.md index f052eda6ac..8d867d3a46 100644 --- a/sites/docs/src/content/ui/assets/index.md +++ b/sites/docs/src/content/ui/assets/index.md @@ -1,5 +1,9 @@ --- +# layout: toc layout: toc -title: Assets & media -description: Content covering incorporating assets and media in Flutter apps. +# title: Assets & media +title: 资源与媒体 +# description: Content covering incorporating assets and media in Flutter apps. +description: 介绍如何在 Flutter 应用中纳入资源和媒体。 +ai-translated: true --- diff --git a/sites/docs/src/content/ui/design/cupertino/index.md b/sites/docs/src/content/ui/design/cupertino/index.md index c356b2cc05..1440a9037a 100644 --- a/sites/docs/src/content/ui/design/cupertino/index.md +++ b/sites/docs/src/content/ui/design/cupertino/index.md @@ -1,6 +1,9 @@ --- -title: Cupertino design for Flutter -description: Learn about Cupertino design for Flutter. +# title: Cupertino design for Flutter +title: Flutter 的 Cupertino 设计 +# description: Learn about Cupertino design for Flutter. +description: 了解 Flutter 的 Cupertino 设计。 +ai-translated: true --- The Flutter Cupertino library is a collection @@ -10,14 +13,20 @@ and feel to iOS, including rounded corners, gradients, and minimalistic design. They also include iOS interactions and animations. +Flutter Cupertino 库是一组为 Flutter 应用实现 Apple iOS 设计语言的 widget。这些 widget 的外观和手感与 iOS 相似,包括圆角、渐变和极简设计。它们还包含 iOS 的交互和动画。 + The following 15-minute video provides a high-level glimpse of the Cupertino package: +以下 15 分钟视频概览了 Cupertino 包: + To see some Cupertino widgets in action, the following videos from the Widget of the Week series cover a few of them. +要观看部分 Cupertino widget 的实际效果,Widget of the Week 系列中的以下视频介绍了其中几个。 +
@@ -48,17 +57,29 @@ Widget of the Week series cover a few of them. ## More information {:.no_toc} +## 更多信息 {:.no_toc} + To learn more about using the Cupertino package with Flutter, check out the following resources: +要了解更多在 Flutter 中使用 Cupertino 包的信息,请查看以下资源: + * The (mostly visual) [Cupertino widget catalog][] on doc.flutter.dev + + doc.flutter.dev 上(以视觉为主)的 [Cupertino widget 目录][Cupertino widget catalog] + * The [Cupertino library][] page in the API docs + + API 文档中的 [Cupertino library][] 页面 + * The [Cupertino API examples][] on the flutter/flutter repo. ([Instructions][]) For example, to run `CupertinoSwitch`: + flutter/flutter 仓库中的 [Cupertino API 示例][Cupertino API examples]。([说明][Instructions])例如,要运行 `CupertinoSwitch`: + ```console cd path/to/flutter cd examples/api diff --git a/sites/docs/src/content/ui/design/graphics/fragment-shaders.md b/sites/docs/src/content/ui/design/graphics/fragment-shaders.md index 7e569cd393..32bf7a39ff 100644 --- a/sites/docs/src/content/ui/design/graphics/fragment-shaders.md +++ b/sites/docs/src/content/ui/design/graphics/fragment-shaders.md @@ -1,13 +1,19 @@ --- -title: Writing and using fragment shaders -description: How to author and use fragment shaders to create custom visual effects in your Flutter app. -shortTitle: Fragment shaders +# title: Writing and using fragment shaders +title: 编写和使用片段着色器 +# description: How to author and use fragment shaders to create custom visual effects in your Flutter app. +description: 如何编写和使用片段着色器,在你的 Flutter 应用中创建自定义视觉效果。 +# shortTitle: Fragment shaders +shortTitle: 片段着色器 +ai-translated: true --- :::note Both the Skia and [Impeller][] backends support writing a custom shader. Except where noted, the same instructions apply to both. + +Skia 和 [Impeller][] 两种后端都支持编写自定义着色器。除非另有说明,否则以下说明对两者均适用。 ::: [Impeller]: /perf/impeller @@ -18,14 +24,20 @@ A shader is a program authored in a small, Dart-like language, known as GLSL, and executed on the user's GPU. +自定义着色器可用于提供超出 Flutter SDK 所提供的丰富图形效果。着色器是用一种类似 Dart 的小型语言(称为 GLSL)编写、并在用户 GPU 上执行的程序。 + Custom shaders are added to a Flutter project by listing them in the `pubspec.yaml` file, and obtained using the [`FragmentProgram`][] API. +通过在 `pubspec.yaml` 文件中列出自定义着色器可将其添加到 Flutter 项目,并使用 [`FragmentProgram`][] API 获取。 + [`FragmentProgram`]: {{site.api}}/flutter/dart-ui/FragmentProgram-class.html ## Adding shaders to an application +## 向应用添加着色器 + Shaders, in the form of GLSL files with the `.frag` extension, must be declared in the `shaders` section of your project's `pubspec.yaml` file. The Flutter command-line tool compiles the shader @@ -33,6 +45,8 @@ to its appropriate backend format, and generates its necessary runtime metadata. The compiled shader is then included in the application just like an asset. +着色器以带 `.frag` 扩展名的 GLSL 文件形式,必须在项目 `pubspec.yaml` 文件的 `shaders` 部分声明。Flutter 命令行工具会将着色器编译为相应的后端格式,并生成所需的运行时元数据。编译后的着色器会像资源一样包含在应用中。 + ```yaml flutter: shaders: @@ -43,17 +57,25 @@ When running in debug mode, changes to a shader program trigger recompilation and update the shader during hot reload or hot restart. +在 debug 模式下运行时,对着色器程序的更改会触发重新编译,并在热重载或热重启期间更新着色器。 + Shaders from packages are added to a project with `packages/$pkgname` prefixed to the shader program's name (where `$pkgname` is the name of the package). +来自包的着色器通过在给色器程序名称前加上 `packages/$pkgname` 前缀添加到项目中(其中 `$pkgname` 是包名)。 + ### Loading shaders at runtime +### 在运行时加载着色器 + To load a shader into a `FragmentProgram` object at runtime, use the [`FragmentProgram.fromAsset`][] constructor. The asset's name is the same as the path to the shader given in the `pubspec.yaml` file. +要在运行时将着色器加载到 `FragmentProgram` 对象,请使用 [`FragmentProgram.fromAsset`][] 构造函数。资源名称与 `pubspec.yaml` 文件中给出的着色器路径相同。 + [`FragmentProgram.fromAsset`]: {{site.api}}/flutter/dart-ui/FragmentProgram/fromAsset.html ```dart @@ -68,6 +90,8 @@ A `FragmentShader` object represents a fragment program along with a particular set of _uniforms_ (configuration parameters). The available uniforms depends on how the shader was defined. +`FragmentProgram` 对象可用于创建一个或多个 [`FragmentShader`][] 实例。`FragmentShader` 对象表示片段程序以及一组特定的 _uniforms_(配置参数)。可用的 uniforms 取决于着色器的定义方式。 + [`FragmentShader`]: {{site.api}}/flutter/dart-ui/FragmentShader-class.html ```dart @@ -80,6 +104,8 @@ void updateShader(Canvas canvas, Rect rect, FragmentProgram program) { ### Canvas API +### Canvas API + Fragment shaders can be used with most Canvas APIs by setting [`Paint.shader`][]. For example, when using [`Canvas.drawRect`][] @@ -88,6 +114,8 @@ For an API like [`Canvas.drawPath`][] with a stroked path, the shader is evaluated for all fragments within the stroked line. Some APIs, such as [`Canvas.drawImage`][], ignore the value of the shader. +片段着色器可通过设置 [`Paint.shader`][] 与大多数 Canvas API 一起使用。例如,使用 [`Canvas.drawRect`][] 时,着色器会对矩形内的所有片段求值。对于像 [`Canvas.drawPath`][] 这样绘制描边路径的 API,着色器会对描边线内的所有片段求值。某些 API(如 [`Canvas.drawImage`][])会忽略着色器的值。 + [`Canvas.drawImage`]: {{site.api}}/flutter/dart-ui/Canvas/drawImage.html [`Canvas.drawRect`]: {{site.api}}/flutter/dart-ui/Canvas/drawRect.html [`Canvas.drawPath`]: {{site.api}}/flutter/dart-ui/Canvas/drawPath.html @@ -115,6 +143,8 @@ void paint(Canvas canvas, Size size, FragmentShader shader) { ### ImageFilter API +### ImageFilter API + Fragment shaders can also be used with the [`ImageFilter`][] API. This allows using custom fragment shaders with the [`ImageFiltered`][] class or the [`BackdropFilter`][] class @@ -122,9 +152,13 @@ to apply shaders to already rendered content. [`ImageFilter`][] provides a constructor, [`ImageFilter.shader`][], for creating an [`ImageFilter`][] with a custom fragment shader. +片段着色器也可与 [`ImageFilter`][] API 一起使用。这允许将自定义片段着色器与 [`ImageFiltered`][] 类或 [`BackdropFilter`][] 类配合,将着色器应用于已渲染的内容。[`ImageFilter`][] 提供构造函数 [`ImageFilter.shader`][],用于创建带自定义片段着色器的 [`ImageFilter`][]。 + :::warning The `ImageFilter` API for custom shaders is only supported by the [Impeller][] backend. Using it on other backends will throw an error. + +用于自定义着色器的 `ImageFilter` API 仅由 [Impeller][] 后端支持。在其他后端上使用会抛出错误。 ::: ```dart @@ -146,7 +180,9 @@ Widget build(BuildContext context, FragmentShader shader) { When using [`ImageFilter`][] with [`BackdropFilter`][], a [`ClipRect`][] can be used to limit the area that is affected by the [`ImageFilter`][]. Without a -[`ClipRect`][] the [`BackdropFilter`][] will be applied to the whole screen. +[`ClipRect`][], the [`BackdropFilter`][] will be applied to the whole screen. + +将 [`ImageFilter`][] 与 [`BackdropFilter`][] 一起使用时,可用 [`ClipRect`][] 限制受 [`ImageFilter`][] 影响的区域。没有 [`ClipRect`][] 时,[`BackdropFilter`][] 将应用于整个屏幕。 `ImageFilter` fragment shaders receive some uniforms automatically from the engine. The `sampler2D` value at index 0 is set to the filter input image, and @@ -154,10 +190,14 @@ the `float` values at indices 0 and 1 are set to the image's width and height. Your shader must specify this constructor to accept these values (for example, a `sampler2D` and a `vec2`), but you should not set them from your Dart code. +`ImageFilter` 片段着色器会从引擎自动接收一些 uniforms。索引 0 处的 `sampler2D` 值设为滤镜输入图像,索引 0 和 1 处的 `float` 值设为图像的宽度和高度。你的着色器必须指定此构造函数以接受这些值(例如 `sampler2D` 和 `vec2`),但你不应从 Dart 代码中设置它们。 + When targeting OpenGLES the y-coordinates of the texture will be flipped so the fragment shader should un-flip the UVs when sampling from textures provided by the engine. +针对 OpenGLES 时,纹理的 y 坐标会翻转,因此片段着色器在从引擎提供的纹理采样时应取消翻转 UV。 + ```glsl #version 460 core #include @@ -188,25 +228,52 @@ void main() { ## Authoring shaders +## 编写着色器 + Fragment shaders are authored as GLSL source files. By convention, these files have the `.frag` extension. (Flutter doesn't support vertex shaders, which would have the `.vert` extension.) +片段着色器以 GLSL 源文件编写。按惯例,这些文件使用 `.frag` 扩展名。(Flutter 不支持顶点着色器,顶点着色器会使用 `.vert` 扩展名。) + Any GLSL version from 460 down to 100 is supported, though some available features are restricted. The rest of the examples in this document use version `460 core`. +支持 GLSL 版本 460 至 100,但部分可用功能受限。本文档其余示例使用 `460 core` 版本。 + Shaders are subject to the following limitations when used with Flutter: +在 Flutter 中使用着色器时受以下限制: + * UBOs and SSBOs aren't supported + + 不支持 UBO 和 SSBO + * `sampler2D` is the only supported sampler type + + `sampler2D` 是唯一支持的采样器类型 + * Only the two-argument version of `texture` (sampler and uv) is supported + + 仅支持 `texture` 的两参数版本(采样器和 uv) + * No additional varying inputs can be declared + + 不能声明额外的 varying 输入 + * All precision hints are ignored when targeting Skia + + 针对 Skia 时,所有精度提示都会被忽略 + * Unsigned integers and booleans aren't supported + 不支持无符号整数和布尔值 + +### Uniforms + ### Uniforms A fragment program can be configured by defining @@ -214,17 +281,23 @@ A fragment program can be configured by defining and then setting these values in Dart for each fragment shader instance. +可通过在 GLSL 着色器源中定义 `uniform` 值,然后为每个片段着色器实例在 Dart 中设置这些值来配置片段程序。 + Floating point uniforms with the GLSL types `float`, `vec2`, `vec3`, and `vec4` are set using the [`FragmentShader.setFloat`][] or [`FragmentShader.getUniformFloat`][] method. GLSL sampler values, which use the `sampler2D` type, are set using the [`FragmentShader.setImageSampler`][] or [`FragmentShader.getImageSampler`][] method. +GLSL 类型为 `float`、`vec2`、`vec3` 和 `vec4` 的浮点 uniform 使用 [`FragmentShader.setFloat`][] 或 [`FragmentShader.getUniformFloat`][] 方法设置。使用 `sampler2D` 类型的 GLSL 采样器值使用 [`FragmentShader.setImageSampler`][] 或 [`FragmentShader.getImageSampler`][] 方法设置。 + The correct index for each `uniform` value is determined by the order that the uniform values are defined in the fragment program. For data types composed of multiple floats, such as a `vec4`, you must call [`FragmentShader.setFloat`][] or [`UniformFloatSlot.set`][] once for each value. +每个 `uniform` 值的正确索引由片段程序中 uniform 值的定义顺序决定。对于由多个 float 组成的数据类型(如 `vec4`),你必须为每个值调用一次 [`FragmentShader.setFloat`][] 或 [`UniformFloatSlot.set`][]。 + [`FragmentShader.setFloat`]: {{site.api}}/flutter/dart-ui/FragmentShader/setFloat.html [`UniformFloatSlot.set`]: {{site.api}}/flutter/dart-ui/UniformFloatSlot/set.html [`FragmentShader.getUniformFloat`]: {{site.api}}/flutter/dart-ui/FragmentShader/getUniformFloat.html @@ -233,6 +306,8 @@ or [`UniformFloatSlot.set`][] once for each value. For example, given the following uniforms declarations in a GLSL fragment program: +例如,给定 GLSL 片段程序中的以下 uniform 声明: + ```glsl uniform float uScale; uniform sampler2D uTexture; @@ -242,6 +317,8 @@ uniform vec4 uColor; The corresponding Dart code to initialize these `uniform` values is as follows: +初始化这些 `uniform` 值的对应 Dart 代码如下: + ```dart class Foobar { late final UniformFloatSlot _scale; @@ -277,11 +354,17 @@ When using [`FragmentShader.setFloat`][] note that the indices do not count the `sampler2D` uniform. This uniform is set separately with [`FragmentShader.setImageSampler`][], with the index starting over at 0. +使用 [`FragmentShader.setFloat`][] 时注意,索引不计入 `sampler2D` uniform。该 uniform 使用 [`FragmentShader.setImageSampler`][] 单独设置,索引从 0 重新开始。 + Any float uniforms that are left uninitialized will default to `0.0`. +任何未初始化的 float uniform 默认为 `0.0`。 + The reflection data generated by the Flutter's shader compiler can be audited with the following commands in order to see things like uniform offsets. +可以使用以下命令审计 Flutter 着色器编译器生成的反射数据,以查看 uniform 偏移等信息。 + ```shell cd $FLUTTER # Generate the .sl file. @@ -304,12 +387,16 @@ cat foo.json #### Current position +#### 当前位置 + The shader has access to a `varying` value that contains the local coordinates for the particular fragment being evaluated. Use this feature to compute effects that depend on the current position, which can be accessed by importing the `flutter/runtime_effect.glsl` library and calling the `FlutterFragCoord` function. For example: +着色器可访问包含当前被求值片段的局部坐标的 `varying` 值。使用此功能可计算依赖当前位置的效果,可通过导入 `flutter/runtime_effect.glsl` 库并调用 `FlutterFragCoord` 函数访问。例如: + ```glsl #include @@ -324,28 +411,42 @@ avoided to ensure that shaders are consistent across backends. When targeting a Skia backend, the calls to `gl_FragCoord` are rewritten to access local coordinates but this rewriting isn't possible with Impeller. +`FlutterFragCoord` 返回的值与 `gl_FragCoord` 不同。`gl_FragCoord` 提供屏幕空间坐标,通常应避免使用,以确保着色器在各后端间一致。针对 Skia 后端时,对 `gl_FragCoord` 的调用会重写为访问局部坐标,但 Impeller 无法进行此重写。 + #### Colors +#### 颜色 + There isn't a built-in data type for colors. Instead they are commonly represented as a `vec4` with each component corresponding to one of the RGBA color channels. +没有用于颜色的内置数据类型。通常用 `vec4` 表示,每个分量对应 RGBA 颜色通道之一。 + The single output `fragColor` expects that the color value is normalized to be in the range of `0.0` to `1.0` and that it has premultiplied alpha. This is different than typical Flutter colors which use a `0-255` value encoding and have unpremultipled alpha. +单一输出 `fragColor` 要求颜色值归一化到 `0.0` 至 `1.0` 范围,并具有预乘 alpha。这与典型的 Flutter 颜色不同,后者使用 `0-255` 值编码且具有非预乘 alpha。 + #### Samplers +#### 采样器 + A sampler provides access to a `dart:ui` `Image` object. This image can be acquired either from a decoded image or from part of the application using [`Scene.toImageSync`][] or [`Picture.toImageSync`][]. +采样器提供对 `dart:ui` `Image` 对象的访问。该图像可从解码后的图像获取,或使用 [`Scene.toImageSync`][] 或 [`Picture.toImageSync`][] 从应用的一部分获取。 + [`Picture.toImageSync`]: {{site.api}}/flutter/dart-ui/Picture/toImageSync.html [`Scene.toImageSync`]: {{site.api}}/flutter/dart-ui/Scene/toImageSync.html ##### Sampler usage in GLSL example +##### GLSL 中的采样器用法示例 + ```glsl #include @@ -364,10 +465,14 @@ By default, the image uses [`TileMode.clamp`][] to determine how values outside of the range of `[0, 1]` behave. Customization of the tile mode is not supported and needs to be emulated in the shader. +默认情况下,图像使用 [`TileMode.clamp`][] 确定超出 `[0, 1]` 范围的值的行为。不支持自定义平铺模式,需要在着色器中模拟。 + [`TileMode.clamp`]: {{site.api}}/flutter/dart-ui/TileMode.html ##### `toImageSync` example +##### `toImageSync` 示例 + ```dart class SDFPainter { SDFPainter(this.sdfShader, this.renderShader); @@ -406,29 +511,52 @@ class SDFPainter { ## Performance considerations +## 性能注意事项 + When targeting the Skia backend, loading the shader might be expensive since it must be compiled to the appropriate platform-specific shader at runtime. If you intend to use one or more shaders during an animation, consider precaching the fragment program objects before starting the animation. +针对 Skia 后端时,加载着色器可能开销较大,因为必须在运行时将着色器编译为相应的平台特定着色器。如果你打算在动画期间使用一个或多个着色器,考虑在动画开始前预缓存片段程序对象。 + You can reuse a `FragmentShader` object across frames; this is more efficient than creating a new `FragmentShader` for each frame. +你可以跨帧复用 `FragmentShader` 对象;这比每帧创建新的 `FragmentShader` 更高效。 + For a more detailed guide on writing performant shaders, check out [Writing efficient shaders][] on GitHub. +有关编写高性能着色器的更详细指南,请查看 GitHub 上的 [Writing efficient shaders][]。 + [Writing efficient shaders]: {{site.repo.flutter}}/blob/main/docs/engine/impeller/docs/shader_optimization.md ## Other resources +## 其他资源 + For more information, here are a few resources. +更多信息请参阅以下资源。 + * [The Book of Shaders][] by Patricio Gonzalez Vivo and Jen Lowe + + Patricio Gonzalez Vivo 和 Jen Lowe 撰写的 [The Book of Shaders][] + * [Shader toy][], a collaborative shader playground + + [Shader toy][],协作式着色器游乐场 + * [`simple_shader`][], a simple Flutter fragment shaders sample project + + [`simple_shader`][],简单的 Flutter 片段着色器示例项目 + * [`flutter_shaders`][], a package that simplifies using fragment shaders in Flutter + [`flutter_shaders`][],简化在 Flutter 中使用片段着色器的包 + [Shader toy]: https://www.shadertoy.com/ [The Book of Shaders]: https://thebookofshaders.com/ [`simple_shader`]: {{site.repo.samples}}/tree/main/simple_shader diff --git a/sites/docs/src/content/ui/design/graphics/index.md b/sites/docs/src/content/ui/design/graphics/index.md index a5f4ac4743..6cb5dd5b11 100644 --- a/sites/docs/src/content/ui/design/graphics/index.md +++ b/sites/docs/src/content/ui/design/graphics/index.md @@ -1,9 +1,17 @@ --- +# layout: toc layout: toc -title: Custom drawing and graphics -shortTitle: Drawing & graphics +# title: Custom drawing and graphics +title: 自定义绘制与图形 +# shortTitle: Drawing & graphics +shortTitle: 绘制与图形 +# description: > +# Content covering how to create custom graphics and +# use your own shaders in Flutter apps. description: > - Content covering how to create custom graphics and - use your own shaders in Flutter apps. + 介绍如何在 Flutter 应用中创建自定义图形 + 并使用你自己的着色器。 +# sitemap: false sitemap: false +ai-translated: true --- diff --git a/sites/docs/src/content/ui/design/index.md b/sites/docs/src/content/ui/design/index.md index 8159a01422..37d8010b69 100644 --- a/sites/docs/src/content/ui/design/index.md +++ b/sites/docs/src/content/ui/design/index.md @@ -1,6 +1,11 @@ --- +# layout: toc layout: toc -title: Design & theming -description: Content covering designing Flutter apps. +# title: Design & theming +title: 设计与主题 +# description: Content covering designing Flutter apps. +description: 介绍如何设计 Flutter 应用的内容。 +# sitemap: false sitemap: false +ai-translated: true --- diff --git a/sites/docs/src/content/ui/design/material/index.md b/sites/docs/src/content/ui/design/material/index.md index 1a04dfe909..2124ab2be2 100644 --- a/sites/docs/src/content/ui/design/material/index.md +++ b/sites/docs/src/content/ui/design/material/index.md @@ -1,16 +1,23 @@ --- -title: Material Design for Flutter -description: Learn about Material Design for Flutter. +# title: Material Design for Flutter +title: Flutter 的 Material Design +# description: Learn about Material Design for Flutter. +description: 了解 Flutter 的 Material Design。 +ai-translated: true --- Material Design is an open-source design system built and supported by Google designers and developers. +Material Design 是由 Google 设计师和开发者构建并支持的开源设计系统。 + The latest version, Material 3, enables personal, adaptive, and expressive experiences—from dynamic color and enhanced accessibility, to foundations for large screen layouts, and design tokens. +最新版本 Material 3 支持个性化、自适应和富有表现力的体验——从动态颜色和增强的无障碍性,到大屏布局基础和设计令牌。 + :::warning As of the Flutter 3.16 release, **Material 3 is enabled by default**. For now, you can opt out @@ -19,6 +26,8 @@ to `false`. But be aware that the `useMaterial3` property and support for Material 2 will eventually be deprecated according to Flutter's [deprecation policy][]. + +自 Flutter 3.16 发布起,**Material 3 默认启用**。目前,你可以通过将 [`useMaterial3`][] 属性设为 `false` 来退出 Material 3。但请注意,根据 Flutter 的[弃用政策][deprecation policy],`useMaterial3` 属性以及对 Material 2 的支持最终将被弃用。 ::: For _most_ Flutter widgets, upgrading to Material 3 @@ -31,6 +40,8 @@ the UI might look or act a bit strange. You can find the entirely new Material components by visiting the [Affected widgets][] page. +对_大多数_ Flutter widget 而言,升级到 Material 3 是无缝的。但_有些_ widget 无法直接更新——需要全新的实现,例如 [`NavigationBar`][]。你必须在代码中手动进行这些更改。在应用完全更新之前,UI 的外观或行为可能会有些异常。你可以访问[受影响的 widget][Affected widgets] 页面查看全新的 Material widget。 + [Affected widgets]: {{site.api}}/flutter/material/ThemeData/useMaterial3.html#affected-widgets [deprecation policy]: /release/compatibility-policy#deprecation-policy [demo]: {{site.github}}/flutter/samples/blob/main/material_3_demo/ @@ -41,15 +52,29 @@ Explore the updated components, typography, color system, and elevation support with the [Material 3 demo][demo]. +通过 [Material 3 演示][demo] 探索更新后的 widget、字体排版、颜色系统和 elevation 支持。 + ## More information {:.no_toc} +## 更多信息 {:.no_toc} + To learn more about Material Design and Flutter, check out: +要了解更多关于 Material Design 和 Flutter 的信息,请查看: + * [Material.io developer documentation][] + + [Material.io 开发者文档][Material.io developer documentation] + * [Migrating a Flutter app to Material 3][] blog post by Taha Tesser + + Taha Tesser 撰写的[将 Flutter 应用迁移到 Material 3][Migrating a Flutter app to Material 3] 博客文章 + * [Umbrella issue on GitHub][] + GitHub 上的[总括 issue][Umbrella issue on GitHub] + [Material.io developer documentation]: {{site.material}}/develop/flutter [Migrating a Flutter app to Material 3]: https://blog.codemagic.io/migrating-a-flutter-app-to-material-3/ [Umbrella issue on GitHub]: {{site.github}}/flutter/flutter/issues/91605 diff --git a/sites/docs/src/content/ui/design/text/index.md b/sites/docs/src/content/ui/design/text/index.md index e4b140bf6c..2bf5f2b580 100644 --- a/sites/docs/src/content/ui/design/text/index.md +++ b/sites/docs/src/content/ui/design/text/index.md @@ -1,6 +1,11 @@ --- +# layout: toc layout: toc -title: Text -description: Content covering how to add and customize text in Flutter appw. +# title: Text +title: 文本 +# description: Content covering how to add and customize text in Flutter appw. +description: 介绍如何在 Flutter 应用中添加和自定义文本。 +# sitemap: false sitemap: false +ai-translated: true --- diff --git a/sites/docs/src/content/ui/design/text/typography.md b/sites/docs/src/content/ui/design/text/typography.md index 5567062a9c..71b3f70b89 100644 --- a/sites/docs/src/content/ui/design/text/typography.md +++ b/sites/docs/src/content/ui/design/text/typography.md @@ -1,6 +1,9 @@ --- -title: Flutter's fonts and typography -description: Learn about Flutter's support for typography. +# title: Flutter's fonts and typography +title: Flutter 的字体与字体排版 +# description: Learn about Flutter's support for typography. +description: 了解 Flutter 对字体排版的支持。 +ai-translated: true --- [_Typography_][] covers the style and appearance of @@ -8,8 +11,12 @@ type or fonts: it specifies how heavy the font is, the slant of the font, the spacing between the letters, and other visual aspects of the text. +[_Typography_][] 涵盖字体或字型的风格与外观:它规定字体的粗细、倾斜度、字母间距以及文本的其他视觉方面。 + All fonts are _not_ created the same. +并非所有字体都生而相同。 + A font style is defined by, at minimum, a typeface, representing the set of common character rules describing fonts in the same type family, such as **Roboto** or **Noto**, a font weight (for example, Regular, Bold, or a @@ -17,46 +24,86 @@ numeric value), and a style (like Regular, _Italic_, etc). All of these and additional pre-set attributes come together to make up what we would call a static font. +字体样式至少由 typeface(表示同一字体家族中字体的通用字符规则集合,例如 **Roboto** 或 **Noto**)、字重(例如 Regular、Bold 或数值)以及样式(如 Regular、_Italic_ 等)定义。这些以及额外的预设属性共同构成我们所说的静态字体。 + Variable fonts allow some of these attributes to be modified at runtime and store what would normally be multiple static fonts in a single file. +可变字体允许在运行时修改其中部分属性,并将通常需要多个静态字体文件的内容存储在单个文件中。 + [_Typography_]: https://en.wikipedia.org/wiki/Typography ## Typographic Scale +## 字体排版比例尺 + A typographical scale is a set of related text styles to provide balance, cohesion, and visual variety in your apps. +字体排版比例尺是一组相关的文本样式,用于在应用中提供平衡、一致性和视觉多样性。 + The common type scale in Flutter, provided by [`TextTheme`][], includes five categories of text indicating the function: +Flutter 中由 [`TextTheme`][] 提供的常见字体比例尺包括五个表示功能的文本类别: + * Display + + 展示(Display) + * Headline + + 标题(Headline) + * Title + + 题头(Title) + * Label + + 标签(Label) + * Body + 正文(Body) + There are also three size variations for each: +每个类别还有三种尺寸变体: + * Small + + 小(Small) + * Medium + + 中(Medium) + * Large + 大(Large) + Each of these fifteen combinations of a category and text size are represented by a single [`TextStyle`][]. +这十五种类别与文本尺寸的组合各由一个 [`TextStyle`][] 表示。 + Listing of typographical scale for Material TextTheme All the platform specific typographical scales that Flutter exposes are contained in the [`Typography`][] class. Usually, you will not need to reference this class directly as the `TextTheme` will be localized to your target platform. +Flutter 暴露的所有平台特定字体排版比例尺都包含在 [`Typography`][] 类中。通常你不需要直接引用该类,因为 `TextTheme` 会针对你的目标平台进行本地化。 + [`TextTheme`]: https://api.flutter-io.cn/flutter/material/TextTheme-class.html [`TextStyle`]: https://api.flutter-io.cn/flutter/painting/TextStyle-class.html [`Typography`]: https://api.flutter-io.cn/flutter/material/Typography-class.html ## Variable fonts +## 可变字体 + [Variable fonts][] allow you to control pre-defined aspects of text styling. Variable fonts support specific axes, such as width, @@ -64,14 +111,20 @@ weight, slant (to name a few). The user can select _any value along the continuous axis_ when specifying the type. +[可变字体][Variable fonts] 让你控制文本样式的预定义方面。可变字体支持特定轴,例如宽度、字重、倾斜度(仅举几例)。用户在指定字体时可以选择连续轴上的_任意值_。 + [Variable fonts]: https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts ### Using the Google Fonts type tester +### 使用 Google Fonts 字体测试器 + A growing number of fonts on Google Fonts offer some variable font capabilities. You can see the range of options by using the Type Tester and see how you might vary a single font. +Google Fonts 上越来越多的字体提供可变字体功能。你可以使用 Type Tester 查看可选范围,并了解如何变化单一字体。 + Demonstration of varying aspects for Noto Sans with Lorem ipsum text In real time, move the slider on any of the axes to @@ -80,24 +133,36 @@ use the [`FontVariation`][] class to modify the font's design axes. The `FontVariation` class conforms to the [OpenType font variables spec][]. +实时移动任一轴上的滑块,查看它如何影响字体。在编程使用可变字体时,使用 [`FontVariation`][] 类修改字体的设计轴。`FontVariation` 类符合 [OpenType 字体变量规范][OpenType font variables spec]。 + [`FontVariation`]: {{site.api}}/flutter/dart-ui/FontVariation-class.html [Google Fonts]: https://fonts.google.com/ [OpenType font variables spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview ## Static fonts +## 静态字体 + Google Fonts also contains static fonts. As with variable fonts, you need to know how the font is designed to know what options are available to you. Once again, the Google Fonts site can help. +Google Fonts 也包含静态字体。与可变字体一样,你需要了解字体的设计方式才能知道有哪些可用选项。同样,Google Fonts 网站可以提供帮助。 + ### Using the Google Fonts package +### 使用 google_fonts 包 + While you can download fonts from the site and install them manually in your apps, you can elect to use theme directly from the [google_fonts][] package on [pub.dev][]. +虽然你可以从网站下载字体并手动安装到应用中,你也可以选择直接使用 [pub.dev][] 上的 [google_fonts][] 包中的主题。 + They can be used as is by referencing simply the font name: +只需引用字体名称即可直接使用: + ```dart Text( 'This is Google Fonts', @@ -107,6 +172,8 @@ Text( or customized by setting properties on the resulting `TextStyle`: +或者通过在生成的 `TextStyle` 上设置属性进行自定义: + ```dart Text( 'This is Google Fonts', @@ -121,19 +188,36 @@ Text( ### Modifying fonts +### 修改字体 + Use the following API to programmatically alter a static font (but remember that this only works if the font was _designed_ to support the feature): +使用以下 API 以编程方式修改静态字体(但请记住,这仅在字体_设计为_支持该功能时才有效): + * [`FontFeature`][] to select glyphs + + [`FontFeature`][] 用于选择字形 + * [`FontWeight`][] to modify weight + + [`FontWeight`][] 用于修改字重 + * [`FontStyle`][] to italicize + + [`FontStyle`][] 用于斜体 + * [`FontVariation`][] to specify a range of values for a specific property. + [`FontVariation`][] 用于为特定属性指定取值范围。 + A `FontFeature` corresponds to an [OpenType feature tag][] and can be thought of as a boolean flag to enable or disable a feature of a given font. +`FontFeature` 对应 [OpenType 特性标签][OpenType feature tag],可视为启用或禁用给定字体某项特性的布尔标志。 + [`FontFeature`]: {{site.api}}/flutter/dart-ui/FontFeature-class.html [`FontStyle`]: {{site.api}}/flutter/dart-ui/FontStyle.html [`FontWeight`]: {{site.api}}/flutter/dart-ui/FontWeight-class.html @@ -143,11 +227,15 @@ a feature of a given font. ## Other resources +## 其他资源 + The following video shows you some of the capabilities of Flutter's typography and combines it with the Material _and_ Cupertino look and feel (depending on the platform the app runs on), animation, and custom fragment shaders: +以下视频展示了 Flutter 字体排版的部分能力,并结合 Material _与_ Cupertino 外观(取决于应用运行的平台)、动画以及自定义片段着色器: + To read one engineer's experience @@ -157,4 +245,6 @@ check out [Playful typography with Flutter][article], a free article on Medium. The associated example also uses a custom shader. +要阅读一位工程师定制可变字体并在变形时为其添加动画的经验(也是上述视频的基础),请查看 Medium 上的免费文章 [Playful typography with Flutter][article]。相关示例还使用了自定义着色器。 + [article]: {{site.flutter-blog}}/playful-typography-with-flutter-f030385058b4 diff --git a/sites/docs/src/content/ui/dot-shorthands.md b/sites/docs/src/content/ui/dot-shorthands.md index 80405c4ce8..00b677eff0 100644 --- a/sites/docs/src/content/ui/dot-shorthands.md +++ b/sites/docs/src/content/ui/dot-shorthands.md @@ -1,28 +1,47 @@ --- -title: "Dot shorthands in Flutter" -description: "Learn how to use Dart's dot shorthands to write cleaner, concise Flutter code." +# title: "Dot shorthands in Flutter" +title: "Flutter 中的点简写" +# description: "Learn how to use Dart's dot shorthands to write cleaner, concise Flutter code." +description: "了解如何使用 Dart 的点简写编写更简洁、更清爽的 Flutter 代码。" +ai-translated: true --- The **dot shorthands** feature allows you to omit the explicit type when accessing static members, constructors, or enum values, provided the compiler can infer the type from the surrounding context. +**点简写**(dot shorthands)特性让你在访问静态成员、构造函数或枚举值时省略显式类型,前提是编译器能从周围上下文推断出类型。 + :::note For a technical overview of this feature, refer to the [Dot Shorthands guide](https://dart.dev/language/dot-shorthand) in the Dart documentation. + +如需该特性的技术概览,请参阅 Dart 文档中的 +[点简写指南](https://dart.dev/language/dot-shorthand)。 ::: ## Why dot shorthands matter +## 为什么点简写很重要 + Building layouts in Flutter often involves deeply nested widget trees. Historically, this meant repeatedly typing explicit class and enum names for properties like colors, typography, and alignment. Dot shorthands reduces this boilerplate, making your code easier to read and faster to write. +在 Flutter 中构建布局通常涉及深度嵌套的 widget 树。 +以往这意味着需要为颜色、排版、对齐等属性反复输入显式的类和枚举名称。 +点简写减少了这类样板代码,让你的代码更易读、写得更快。 + Here is a side-by-side comparison of building a simple `Container`: +下面是通过构建一个简单的 `Container` 进行的并排对比: + ### Without dot shorthands + +### 不使用点简写 + ```dart Container( alignment: Alignment.center, @@ -43,6 +62,9 @@ Container( ``` ### With dot shorthands + +### 使用点简写 + ```dart Container( alignment: .center, // Instead of Alignment.center, @@ -64,21 +86,44 @@ Container( ## Where to use dot shorthands +## 在哪里使用点简写 + Dot shorthands work anywhere the Dart compiler has a clear "context type", meaning it knows exactly what type it expects. In Flutter, this is almost -everywhere inside a widget's property list. +everywhere inside a widget's property list. + + +只要 Dart 编译器有明确的「上下文类型」(context type),即它确切知道期望的类型,点简写就可以使用。 +在 Flutter 中,这几乎适用于 widget 属性列表内的所有位置。 The most common targets for dot shorthands in Flutter are: +在 Flutter 中,点简写最常见的目标包括: + * **Enums**: `MainAxisAlignment`, `CrossAxisAlignment`, `BoxFit`, `TextDirection`. + + + **枚举**:`MainAxisAlignment`、`CrossAxisAlignment`、`BoxFit`、`TextDirection`。 + * **Static properties and methods**: `FontWeight` (constants like `.bold`). + + + **静态属性和方法**:`FontWeight`(如 `.bold` 等常量)。 + * **Constructors**: `EdgeInsets.all()`, `BorderRadius.circular()`. + + **构造函数**:`EdgeInsets.all()`、`BorderRadius.circular()`。 + ### Example: enums +### 示例:枚举 + When a property expects an `enum`, such as `mainAxisAlignment`, you can omit the enum's name and just provide the value preceded by a dot (`.`): +当某个属性期望 `enum` 类型(例如 `mainAxisAlignment`)时,你可以省略枚举名,只提供以点(`.`)开头的值: + ```dart Row( mainAxisAlignment: .spaceEvenly, // Infers MainAxisAlignment.spaceEvenly @@ -88,8 +133,12 @@ Row( ### Example: static properties +### 示例:静态属性 + Static properties work when the context type is exactly the class that defines the property. A common example is text styling with `FontWeight`: +当上下文类型恰好是定义该属性的类时,静态属性即可使用点简写。常见示例是用 `FontWeight` 设置文本样式: + ```dart Text( 'Feature highlights', @@ -101,8 +150,13 @@ Text( ### Example: constructors +### 示例:构造函数 + You can also use dot shorthands for named constructors. Many Flutter layout properties accept a base class like `EdgeInsetsGeometry`. To support dot shorthands, Flutter adds redirecting constructors to these base classes that point to the appropriate subclasses. +你也可以对命名构造函数使用点简写。许多 Flutter 布局属性接受 `EdgeInsetsGeometry` 等基类。 +为支持点简写,Flutter 在这些基类上添加了重定向构造函数,指向相应的子类。 + ```dart Padding( padding: .symmetric(horizontal: 16.0, vertical: 8.0), // Infers EdgeInsetsGeometry.symmetric @@ -113,6 +167,8 @@ Padding( You can even use `.new` to call an unnamed constructor, though this is less common in standard widget trees: +你甚至可以使用 `.new` 调用未命名构造函数,不过在标准 widget 树中较少见: + ```dart class _MyState extends State { final ScrollController _scrollController = .new(); // Infers ScrollController() diff --git a/sites/docs/src/content/ui/index.md b/sites/docs/src/content/ui/index.md index 3aab103a0e..0561c1fa72 100644 --- a/sites/docs/src/content/ui/index.md +++ b/sites/docs/src/content/ui/index.md @@ -4,6 +4,7 @@ title: 使用 Flutter 构建界面 shortTitle: UI # description: Introduction to user interface development in Flutter. description: 介绍如何用 Flutter 构建界面 +ai-translated: true --- @@ -17,17 +18,25 @@ which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next. +Flutter widget 采用受 [React][] 启发的现代框架构建。核心思想是用 widget 构建 UI。Widget 根据当前配置和状态描述其视图应有的外观。当 widget 的状态改变时,widget 会重建其描述,框架将其与先前的描述进行 diff,以确定底层渲染树从一种状态过渡到另一种状态所需的最小变更。 + :::note If you would like to become better acquainted with Flutter by diving into some code, check out [building layouts][], and [adding interactivity to your Flutter app][]. + +若想通过编写代码更好地熟悉 Flutter,请参阅[构建布局][building layouts]和[为 Flutter 应用添加交互性][adding interactivity to your Flutter app]。 ::: ## Hello world +## 你好世界 + The minimal Flutter app simply calls the [`runApp()`][] function with a widget: +最简 Flutter 应用只需用 widget 调用 [`runApp()`][] 函数: + ```dartpad title="Flutter Hello World hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -55,6 +64,8 @@ The text direction needs to be specified in this instance; when the `MaterialApp` widget is used, this is taken care of for you, as demonstrated later. +[`runApp()`][] 函数接收给定的 [`Widget`][] 并将其设为 widget 树的根。本例中 widget 树由两个 widget 组成:[`Center`][] 及其子节点 [`Text`][]。框架强制根 widget 覆盖整个屏幕,因此「Hello, world」文字会显示在屏幕中央。此例需指定文字方向;使用 `MaterialApp` widget 时会自动处理,后文将演示。 + When writing an app, you'll commonly author new widgets that are subclasses of either [`StatelessWidget`][] or [`StatefulWidget`][], depending on whether your widget manages any state. @@ -64,21 +75,31 @@ The framework builds those widgets in turn until the process bottoms out in widgets that represent the underlying [`RenderObject`][], which computes and describes the geometry of the widget. +编写应用时,你通常会编写继承 [`StatelessWidget`][] 或 [`StatefulWidget`][] 的新 widget,取决于 widget 是否管理状态。widget 的主要工作是实现 [`build()`][] 函数,用更低层级的 widget 描述自身。框架依次构建这些 widget,直至底层由表示 [`RenderObject`][] 的 widget 结束,由后者计算并描述 widget 的几何信息。 + ## Basic widgets +## 基础 widget + Flutter comes with a suite of powerful basic widgets, of which the following are commonly used: +Flutter 自带一系列强大的基础 widget,以下是常用的一些: + **[`Text`][]** : The `Text` widget lets you create a run of styled text within your application. +: `Text` widget 让你在应用中创建一段样式化文本。 + **[`Row`][], [`Column`][]** : These flex widgets let you create flexible layouts in both the horizontal (`Row`) and vertical (`Column`) directions. The design of these objects is based on the web's flexbox layout model. +: 这些 flex widget 让你在水平(`Row`)和垂直(`Column`)方向创建灵活布局,其设计基于 Web 的 flexbox 布局模型。 + **[`Stack`][]** : Instead of being linearly oriented (either horizontally or vertically), a `Stack` widget lets you place widgets on top of each other in paint order. @@ -87,6 +108,8 @@ of which the following are commonly used: or left edge of the stack. Stacks are based on the web's absolute positioning layout model. +: `Stack` widget 不按线性方向(水平或垂直)排列,而按绘制顺序将 widget 叠放。可在 `Stack` 的子节点上使用 [`Positioned`][] widget,相对于栈的上、右、下、左边缘定位。`Stack` 基于 Web 的绝对定位布局模型。 + **[`Container`][]** : The `Container` widget lets you create a rectangular visual element. A container can be decorated with a [`BoxDecoration`][], such as a @@ -94,8 +117,12 @@ of which the following are commonly used: padding, and constraints applied to its size. In addition, a `Container` can be transformed in three-dimensional space using a matrix. +: `Container` widget 用于创建矩形视觉元素,可用 [`BoxDecoration`][] 装饰背景、边框或阴影,也可设置外边距、内边距和尺寸约束,还可用矩阵在三维空间中变换。 + Below are some simple widgets that combine these and other widgets: +下面是组合这些及其他 widget 的一些简单示例: + ```dartpad title="Flutter combining widgets hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -177,6 +204,8 @@ section of your `pubspec.yaml` file. It allows you to use the predefined set of [Material icons][]. It's generally a good idea to include this line if you are using the Materials library. +请确保在 `pubspec.yaml` 的 `flutter` 段中包含 `uses-material-design: true` 条目。这样你才能使用预定义的 [Material 图标][Material icons] 集。若使用 Materials 库,通常建议包含这一行。 + ```yaml name: my_app flutter: @@ -187,6 +216,8 @@ Many Material Design widgets need to be inside of a [`MaterialApp`][] to display properly, in order to inherit theme data. Therefore, run the application with a `MaterialApp`. +许多 Material Design widget 需要放在 [`MaterialApp`][] 内才能正确显示并继承主题数据。因此请用 `MaterialApp` 运行应用。 + The `MyAppBar` widget creates a [`Container`][] with a height of 56 device-independent pixels with an internal padding of 8 pixels, both on the left and the right. Inside the container, @@ -198,6 +229,8 @@ You can have multiple `Expanded` children and determine the ratio in which they consume the available space using the [`flex`][] argument to `Expanded`. +`MyAppBar` widget 创建一个高度为 56 逻辑像素的 [`Container`][],左右内边距各为 8 像素。在容器内,`MyAppBar` 使用 [`Row`][] 布局组织子节点。中间的 `title` widget 标记为 [`Expanded`][],表示它会扩展以填满其他子节点未占用的剩余空间。可以有多个 `Expanded` 子节点,并通过 `Expanded` 的 [`flex`][] 参数决定它们占用可用空间的比例。 + The `MyScaffold` widget organizes its children in a vertical column. At the top of the column it places an instance of `MyAppBar`, passing the app bar a [`Text`][] widget to use as its title. @@ -207,10 +240,16 @@ variety of ways. Finally, `MyScaffold` uses an [`Expanded`][] to fill the remaining space with its body, which consists of a centered message. +`MyScaffold` widget 在垂直列中组织子节点。列顶部放置 `MyAppBar` 实例,并向应用栏传入用作标题的 [`Text`][] widget。将 widget 作为参数传给其他 widget 是一种强大技巧,可创建可在多种场景复用的通用 widget。最后,`MyScaffold` 用 [`Expanded`][] 以居中消息填充剩余空间作为 body。 + For more information, check out [Layouts][]. +更多信息请参阅[布局][Layouts]。 + ## Using Material Components +## 使用 Material 组件 + Flutter provides a number of widgets that help you build apps that follow Material Design. A Material app starts with the [`MaterialApp`][] widget, which builds a number of useful widgets @@ -220,6 +259,8 @@ also known as "routes". The `Navigator` lets you transition smoothly between screens of your application. Using the [`MaterialApp`][] widget is entirely optional but a good practice. +Flutter 提供多种 widget,帮助你构建符合 Material Design 的应用。Material 应用以 [`MaterialApp`][] widget 开头,它在应用根节点构建多种实用 widget,包括 [`Navigator`][]——管理以字符串标识的 widget 栈,即「路由」。`Navigator` 让你在应用各界面间平滑过渡。使用 [`MaterialApp`][] widget 完全可选,但是良好实践。 + ```dartpad title="Flutter Material design hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -269,6 +310,8 @@ the app is starting to look a bit more Material. For example, the app bar has a shadow and the title text inherits the correct styling automatically. A floating action button is also added. +现在代码已从 `MyAppBar` 和 `MyScaffold` 切换为 [`AppBar`][] 和 [`Scaffold`][] widget,并改用 `material.dart`,应用开始更具 Material 风格。例如,应用栏带有阴影,标题文字会自动继承正确样式,还添加了浮动操作按钮。 + Notice that widgets are passed as arguments to other widgets. The [`Scaffold`][] widget takes a number of different widgets as named arguments, each of which are placed in the `Scaffold` @@ -278,23 +321,35 @@ layout in the appropriate place. Similarly, the This pattern recurs throughout the framework and is something you might consider when designing your own widgets. +注意 widget 会作为参数传给其他 widget。[`Scaffold`][] widget 接收多种不同 widget 作为命名参数,各自放在 `Scaffold` 布局的合适位置。同样,[`AppBar`][] widget 让你为 [`leading`][]、[`title`][] 的 [`actions`][] 传入 widget。这一模式在框架中反复出现,设计自己的 widget 时也可考虑采用。 + For more information, check out [Material Components widgets][]. +更多信息请参阅 [Material 组件 widget][Material Components widgets]。 + :::note Material is one of the 2 bundled designs included with Flutter. To create an iOS-centric design, check out the [Cupertino components][] package, which has its own versions of [`CupertinoApp`][], and [`CupertinoNavigationBar`][]. -::: +Material 是 Flutter 内置的两套设计之一。 +若要创建以 iOS 为中心的设计, +请参阅 [Cupertino 组件][Cupertino components] package, +其中包含 [`CupertinoApp`][] 和 [`CupertinoNavigationBar`][] 等自有版本。 +::: ## Handling gestures +## 处理手势 + Most applications include some form of user interaction with the system. The first step in building an interactive application is to detect input gestures. See how that works by creating a simple button: +大多数应用都包含与系统的某种用户交互。构建交互式应用的第一步是检测输入手势。通过创建一个简单按钮来了解其工作原理: + ```dartpad title="Flutter button hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -339,22 +394,32 @@ case printing a message to the console. You can use `GestureDetector` to detect a variety of input gestures, including taps, drags, and scales. +[`GestureDetector`][] widget 没有视觉表现,而是检测用户做出的手势。当用户点击 [`Container`][] 时,`GestureDetector` 会调用其 [`onTap()`][] 回调,本例中向控制台打印消息。你可以用 `GestureDetector` 检测多种输入手势,包括点击、拖动和缩放。 + Many widgets use a [`GestureDetector`][] to provide optional callbacks for other widgets. For example, the [`IconButton`][], [`ElevatedButton`][], and [`FloatingActionButton`][] widgets have [`onPressed()`][] callbacks that are triggered when the user taps the widget. +许多 widget 内部使用 [`GestureDetector`][] 为其他 widget 提供可选回调。例如,[`IconButton`][]、[`ElevatedButton`][] 和 [`FloatingActionButton`][] widget 具有 [`onPressed()`][] 回调,在用户点击 widget 时触发。 + For more information, check out [Gestures in Flutter][]. +更多信息请参阅 [Flutter 中的手势][Gestures in Flutter]。 + ## Changing widgets in response to input +## 根据输入更改 widget + So far, this page has used only stateless widgets. Stateless widgets receive arguments from their parent widget, which they store in [`final`][] member variables. When a widget is asked to [`build()`][], it uses these stored values to derive new arguments for the widgets it creates. +到目前为止,本页只使用了无状态 widget。无状态 widget 从父 widget 接收参数,并存入 [`final`][] 成员变量。当要求 widget [`build()`][] 时,它用这些存储的值为其创建的 widget 推导新参数。 + In order to build more complex experiences—for example, to react in more interesting ways to user input—applications typically carry some state. Flutter uses `StatefulWidgets` to capture @@ -362,6 +427,8 @@ this idea. `StatefulWidgets` are special widgets that know how to generate `State` objects, which are then used to hold state. Consider this basic example, using the [`ElevatedButton`][] mentioned earlier: +要构建更复杂的体验——例如以更有趣的方式响应用户输入——应用通常需要持有一些状态。Flutter 用 `StatefulWidget` 表达这一概念。`StatefulWidget` 是知道如何生成 `State` 对象的特殊 widget,再由 `State` 对象保存状态。下面是一个使用前文 [`ElevatedButton`][] 的基础示例: + ```dartpad title="Flutter state management hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -430,6 +497,8 @@ the application in its current state. `State` objects, on the other hand, are persistent between calls to `build()`, allowing them to remember information. +你可能会疑惑为何 `StatefulWidget` 与 `State` 是分开的对象。在 Flutter 中,这两类对象生命周期不同。`Widget` 是临时对象,用于构建应用在某一状态下的呈现。`State` 对象则在多次调用 `build()` 之间保持存在,从而能记住信息。 + The example above accepts user input and directly uses the result in its `build()` method. In more complex applications, different parts of the widget hierarchy might be @@ -439,6 +508,8 @@ with the goal of gathering specific information, such as a date or location, while another widget might use that information to change the overall presentation. +上面的示例接受用户输入并直接在 `build()` 方法中使用结果。在更复杂的应用中,widget 树的不同部分可能负责不同关注点;例如,一个 widget 可能展示用于收集日期或位置等特定信息的复杂界面,另一个 widget 则可能用这些信息改变整体呈现。 + In Flutter, change notifications flow "up" the widget hierarchy by way of callbacks, while current state flows "down" to the stateless widgets that do presentation. @@ -446,6 +517,8 @@ The common parent that redirects this flow is the `State`. The following slightly more complex example shows how this works in practice: +在 Flutter 中,变更通知通过回调沿 widget 层次结构向上流动,当前状态则向下流向负责呈现的无状态 widget。重定向这一流动的共同父级是 `State`。下面稍复杂的示例展示其实际运作方式: + ```dartpad title="Flutter Hello World hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -518,19 +591,27 @@ the separation of responsibility allows greater complexity to be encapsulated in the individual widgets, while maintaining simplicity in the parent. +注意这里创建了两个新的无状态 widget,清晰分离了_显示_计数器(`CounterDisplay`)与_修改_计数器(`CounterIncrementor`)的职责。尽管总体结果与前一示例相同,职责分离使各 widget 能封装更复杂的逻辑,同时保持父 widget 简洁。 + For more information, check out: +更多信息请参阅: + * [`StatefulWidget`][] * [`setState()`][] ## Bringing it all together +## 综合示例 + What follows is a more complete example that brings together these concepts: A hypothetical shopping application displays various products offered for sale, and maintains a shopping cart for intended purchases. Start by defining the presentation class, `ShoppingListItem`: +下面是一个更完整的示例,综合上述概念:假设某购物应用展示待售商品,并维护意向购买的购物车。先从定义呈现类 `ShoppingListItem` 开始: + ```dartpad title="Flutter complete shopping list item hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -614,6 +695,8 @@ For example, the `inCart` boolean toggles between two visual appearances: one that uses the primary color from the current theme, and another that uses gray. +`ShoppingListItem` widget 遵循无状态 widget 的常见模式:将构造函数接收的值存入 [`final`][] 成员变量,并在 [`build()`][] 函数中使用。例如,`inCart` 布尔值在两种视觉外观间切换:一种使用当前主题的主色,另一种使用灰色。 + When the user taps the list item, the widget doesn't modify its `inCart` value directly. Instead, the widget calls the `onCartChanged` function it received from its parent widget. @@ -623,6 +706,8 @@ In the extreme, the state stored on the widget passed to [`runApp()`][] persists for the lifetime of the application. +当用户点击列表项时,widget 不会直接修改 `inCart` 值,而是调用从父 widget 收到的 `onCartChanged` 函数。这一模式让你能把状态保存在 widget 层次结构更高处,使状态持续更久。极端情况下,传给 [`runApp()`][] 的 widget 上保存的状态会贯穿整个应用生命周期。 + When the parent receives the `onCartChanged` callback, the parent updates its internal state, which triggers the parent to rebuild and create a new instance @@ -633,8 +718,12 @@ because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying [`RenderObject`][]. +当父级收到 `onCartChanged` 回调时,会更新内部状态,从而触发父级重建并创建带有新 `inCart` 值的 `ShoppingListItem` 新实例。尽管父级重建时会创建新的 `ShoppingListItem` 实例,但这一操作开销很小,因为框架会将新构建的 widget 与先前构建的 widget 比较,并仅将差异应用到底层 [`RenderObject`][]。 + Here's an example parent widget that stores mutable state: +下面是一个保存可变状态的父 widget 示例: + ```dartpad title="Flutter storing mutable state hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -774,6 +863,8 @@ of `ShoppingList`, but the framework reuses the `_ShoppingListState` instance that is already in the tree rather than calling `createState` again. +`ShoppingList` 类继承 [`StatefulWidget`][],表示该 widget 保存可变状态。当 `ShoppingList` widget 首次插入树时,框架调用 [`createState()`][] 创建新的 `_ShoppingListState` 实例并与树中该位置关联。(注意 [`State`][] 的子类通常以下划线开头,表示它们是私有实现细节。)当该 widget 的父级重建时,父级会创建新的 `ShoppingList` 实例,但框架会复用树中已有的 `_ShoppingListState` 实例,而不会再次调用 `createState`。 + To access properties of the current `ShoppingList`, the `_ShoppingListState` can use its [`widget`][] property. If the parent rebuilds and creates a new `ShoppingList`, @@ -783,6 +874,8 @@ override the [`didUpdateWidget()`][] function, which is passed an `oldWidget` to let you compare the old widget with the current widget. +要访问当前 `ShoppingList` 的属性,`_ShoppingListState` 可使用其 [`widget`][] 属性。若父级重建并创建新的 `ShoppingList`,`_ShoppingListState` 会用新的 widget 值重建。若希望在 `widget` 属性变化时收到通知,可重写 [`didUpdateWidget()`][] 函数,它会传入 `oldWidget` 以便你将旧 widget 与当前 widget 比较。 + When handling the `onCartChanged` callback, the `_ShoppingListState` mutates its internal state by either adding or removing a product from `_shoppingCart`. To signal to the framework that it changed its internal @@ -798,8 +891,12 @@ you don't need to write separate code for creating and updating child widgets. Instead, you simply implement the `build` function, which handles both situations. +处理 `onCartChanged` 回调时,`_ShoppingListState` 通过向 `_shoppingCart` 添加或移除商品来变更内部状态。为向框架表明内部状态已改变,这些调用应包在 [`setState()`][] 中。调用 `setState` 会将该 widget 标记为 dirty,并在应用下次需要更新屏幕时安排重建。若在修改 widget 内部状态时忘记调用 `setState`,框架不会知道 widget 已 dirty,可能不会调用 widget 的 [`build()`][] 函数,界面也就可能不会反映变更后的状态。用这种方式管理状态时,你无需为创建和更新子 widget 分别编写代码,只需实现 `build` 函数,它可同时处理两种情况。 + ## Responding to widget lifecycle events +## 响应 widget 生命周期事件 + After calling [`createState()`][] on the `StatefulWidget`, the framework inserts the new state object into the tree and then calls [`initState()`][] on the state object. @@ -809,6 +906,8 @@ to configure animations or to subscribe to platform services. Implementations of `initState` are required to start by calling `super.initState`. +在 `StatefulWidget` 上调用 [`createState()`][] 之后,框架将新的 state 对象插入树,然后在该 state 对象上调用 [`initState()`][]。[`State`][] 的子类可重写 `initState` 以执行只需进行一次的工作,例如配置动画或订阅平台服务。`initState` 的实现必须先调用 `super.initState`。 + When a state object is no longer needed, the framework calls [`dispose()`][] on the state object. Override the `dispose` function to do cleanup work. @@ -816,10 +915,16 @@ For example, override `dispose` to cancel timers or to unsubscribe from platform services. Implementations of `dispose` typically end by calling `super.dispose`. +当不再需要 state 对象时,框架会在该 state 对象上调用 [`dispose()`][]。可重写 `dispose` 函数进行清理,例如取消定时器或取消订阅平台服务。`dispose` 的实现通常以调用 `super.dispose` 结束。 + For more information, check out [`State`][]. +更多信息请参阅 [`State`][]。 + ## Keys +## 键 + Use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build @@ -827,16 +932,22 @@ according to their [`runtimeType`][] and the order in which they appear. With keys, the framework requires that the two widgets have the same [`key`][] as well as the same `runtimeType`. +使用 key 可控制 widget 重建时框架将哪些 widget 相互匹配。默认情况下,框架根据 [`runtimeType`][] 及出现顺序匹配当前构建与先前构建中的 widget。有了 key,框架还要求两个 widget 具有相同的 [`key`][] 以及相同的 `runtimeType`。 + Keys are most useful in widgets that build many instances of the same type of widget. For example, the `ShoppingList` widget, which builds just enough `ShoppingListItem` instances to fill its visible region: +key 在构建大量同类型 widget 实例时最有用。例如 `ShoppingList` widget 会构建刚好填满可见区域的 `ShoppingListItem` 实例: + * Without keys, the first entry in the current build would always sync with the first entry in the previous build, even if, semantically, the first entry in the list just scrolled off screen and is no longer visible in the viewport. + 没有 key 时,当前构建中的第一项总会与先前构建中的第一项同步,即使从语义上讲列表第一项已滚出屏幕、在视口中不再可见。 + * By assigning each entry in the list a "semantic" key, the infinite list can be more efficient because the framework syncs entries with matching semantic keys @@ -846,10 +957,16 @@ fill its visible region: to the same semantic entry rather than the entry in the same numerical position in the viewport. + 为列表中每项分配「语义」key 后,无限列表可以更高效,因为框架会同步具有匹配语义 key 的项,从而保持相似(或相同)的视觉外观。此外,按语义同步项意味着有状态子 widget 中保留的状态会附着在相同语义项上,而不是视口中相同数值位置的项上。 + For more information, check out the [`Key`][] API. +更多信息请参阅 [`Key`][] API。 + ## Global keys +## 全局键 + Use global keys to uniquely identify child widgets. Global keys must be globally unique across the entire widget hierarchy, unlike local keys which need @@ -857,8 +974,12 @@ only be unique among siblings. Because they are globally unique, a global key can be used to retrieve the state associated with a widget. +使用 global key 可唯一标识子 widget。global key 必须在整个 widget 层次结构中全局唯一,而 local key 只需在兄弟节点间唯一。由于全局唯一,global key 可用于获取与 widget 关联的 state。 + For more information, check out the [`GlobalKey`][] API. +更多信息请参阅 [`GlobalKey`][] API。 + [`actions`]: {{site.api}}/flutter/material/AppBar-class.html#actions [adding interactivity to your Flutter app]: /ui/interactivity [`AppBar`]: {{site.api}}/flutter/material/AppBar-class.html diff --git a/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md b/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md index c295f0d1de..2e0d5dafa6 100644 --- a/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md +++ b/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md @@ -3,14 +3,19 @@ title: 使用操作和快捷方式 # description: How to use Actions and Shortcuts in your Flutter app. description: 如何在 Flutter 应用程序中使用操作和快捷方式。 +ai-translated: true --- This page describes how to bind physical keyboard events to actions in the user interface. For instance, to define keyboard shortcuts in your application, this page is for you. +本页说明如何将物理键盘事件绑定到用户界面中的操作。例如,若要在应用中定义键盘快捷方式,本页适合你。 + ## Overview +## 概览 + For a GUI application to do anything, it has to have actions: users want to tell the application to _do_ something. Actions are often simple functions that directly perform the action (such as set a value or save a file). In a larger @@ -28,6 +33,10 @@ contexts. An [`Action`][] can be a simple callback (as in the case of the [`CallbackAction`][]) or something more complex that integrates with entire undo/redo architectures (for example) or other logic. +GUI 应用要能完成任何事,都需要操作:用户希望告诉应用*做*某件事。操作通常是直接执行该操作的简单函数(例如设置值或保存文件)。然而在较大应用中情况更复杂:调用操作的代码与操作本身的代码可能需要在不同位置。快捷方式(按键绑定)可能需要在不了解其所调用操作的层级上定义。 + +这时 Flutter 的操作与快捷方式系统就派上用场。它允许开发者定义履行绑定到它们的 intent 的操作。在此语境下,intent 是用户希望执行的通用操作,[`Intent`][] 类实例在 Flutter 中表示这些用户 intent。`Intent` 可以是通用目的,在不同上下文中由不同操作履行。[`Action`][] 可以是简单回调(如 [`CallbackAction`][] 的情况),也可以是更复杂、与整套撤销/重做架构(例如)或其他逻辑集成的实现。 + ![Using Shortcuts Diagram][]{:width="100%" .diagram-wrap} [`Shortcuts`][] are key bindings that activate by pressing a key or combination @@ -35,12 +44,18 @@ of keys. The key combinations reside in a table with their bound intent. When the `Shortcuts` widget invokes them, it sends their matching intent to the actions subsystem for fulfillment. +[`Shortcuts`][] 是按下某个键或组合键时激活的按键绑定。键组合与其绑定的 intent 存放在表中。当 `Shortcuts` widget 调用它们时,会将匹配的 intent 发送给操作子系统以履行。 + To illustrate the concepts in actions and shortcuts, this article creates a simple app that allows a user to select and copy text in a text field using both buttons and shortcuts. +为说明操作与快捷方式中的概念,本文创建一个简单应用,让用户通过按钮和快捷方式在文本字段中选择并复制文本。 + ### Why separate Actions from Intents? +### 为何将 Action 与 Intent 分离? + You might wonder: why not just map a key combination directly to an action? Why have intents at all? This is because it is useful to have a separation of concerns between where the key mapping definitions are (often at a high level), @@ -49,6 +64,8 @@ important to be able to have a single key combination map to an intended operation in an app, and have it adapt automatically to whichever action fulfills that intended operation for the focused context. +你可能会想:为何不直接将键组合映射到操作?为何要有 intent?这是因为将按键映射定义所在位置(通常在高层次)与操作定义所在位置(通常在低层次)分离很有用;而且重要的是,单个键组合可以映射到应用中的预期操作,并自动适配当前焦点上下文中履行该预期操作的操作。 + For instance, Flutter has an `ActivateIntent` widget that maps each type of control to its corresponding version of an `ActivateAction` (and that executes the code that activates the control). This code often needs fairly private @@ -60,6 +77,8 @@ invoke, and to have access to or provide state that it wouldn't necessarily have or need otherwise. This allows your code to separate the two concerns to be more independent. +例如,Flutter 有 `ActivateIntent`,将每种控件映射到对应的 `ActivateAction` 版本(并执行激活该控件的代码)。这段代码通常需要相当私有的访问权限才能完成工作。若没有 `Intent` 提供的额外间接层,就必须把操作定义提升到定义 `Shortcuts` widget 的实例可见的位置,导致快捷方式对要调用哪个操作了解过多,并需要访问或提供它本不必拥有或需要的状态。这使你的代码能将两方面关注点更独立地分离。 + Intents configure an action so that the same action can serve multiple uses. An example of this is `DirectionalFocusIntent`, which takes a direction to move the focus in, allowing the `DirectionalFocusAction` to know which direction to @@ -68,16 +87,24 @@ to all invocations of an `Action`: that kind of state should be passed to the constructor of the `Action` itself, to keep the `Intent` from needing to know too much. +Intent 配置操作,使同一操作可服务多种用途。例如 `DirectionalFocusIntent` 接收移动焦点的方向,让 `DirectionalFocusAction` 知道向哪个方向移动焦点。请注意:不要在 `Intent` 中传递适用于 `Action` 所有调用的状态:这类状态应传给 `Action` 本身的构造函数,以免 `Intent` 需要了解过多信息。 + ### Why not use callbacks? +### 为何不使用回调? + You also might wonder: why not just use a callback instead of an `Action` object? The main reason is that it's useful for actions to decide whether they are enabled by implementing `isEnabled`. Also, it is often helpful if the key bindings, and the implementation of those bindings, are in different places. +你也可能想:为何不直接用回调代替 `Action` 对象?主要原因是操作通过实现 `isEnabled` 来决定是否启用很有用。此外,将按键绑定及其实现放在不同位置往往很有帮助。 + If all you need are callbacks without the flexibility of `Actions` and `Shortcuts`, you can use the [`CallbackShortcuts`][] widget: +若你只需要回调而不需要 `Actions` 和 `Shortcuts` 的灵活性,可以使用 [`CallbackShortcuts`][] widget: + ```dart @override @@ -107,10 +134,14 @@ Widget build(BuildContext context) { ## Shortcuts +## 快捷方式 + As you'll see below, actions are useful on their own, but the most common use case involves binding them to a keyboard shortcut. This is what the `Shortcuts` widget is for. +如下文所示,操作本身很有用,但最常见用法是将它们绑定到键盘快捷方式。这正是 `Shortcuts` widget 的用途。 + It is inserted into the widget hierarchy to define key combinations that represent the user's intent when that key combination is pressed. To convert that intended purpose for the key combination into a concrete action, the @@ -120,6 +151,8 @@ define a `SelectAllIntent`, and bind it to your own `SelectAllAction` or to your either one, depending on which part of your application has focus. Let's see how the key binding part works: +它插入 widget 层次结构,用于定义表示用户按下该键组合时意图的键组合。要将键组合的预期目的转换为具体操作,需使用 `Actions` widget 将 `Intent` 映射到 `Action`。例如,你可以定义 `SelectAllIntent`,并将其绑定到你自己的 `SelectAllAction` 或 `CanvasSelectAllAction`,仅凭这一键绑定,系统会根据应用哪一部分拥有焦点而调用其中之一。下面说明键绑定部分如何工作: + ```dart @override @@ -154,6 +187,8 @@ set defines a set of one or more keys, and the intent indicates the intended purpose of the keypress. The `Shortcuts` widget looks up key presses in the map, to find an `Intent` instance, which it gives to the action's `invoke()` method. +传给 `Shortcuts` widget 的 map 将 `LogicalKeySet`(或 `ShortcutActivator`,见下方说明)映射到 `Intent` 实例。逻辑键集定义一个或多个键,intent 表示按键的预期目的。`Shortcuts` widget 在 map 中查找按键,找到 `Intent` 实例后交给操作的 `invoke()` 方法。 + :::note `ShortcutActivator` is a replacement for `LogicalKeySet`. It allows for more flexible and correct activation of shortcuts. @@ -164,22 +199,37 @@ Then there is `CharacterActivator`, which activates a shortcut based on the character produced by a key sequence, instead of the logical keys themselves. `ShortcutActivator` is also meant to be subclassed to allow for custom ways of activating shortcuts from key events. + +`ShortcutActivator` 是 `LogicalKeySet` 的替代方案。 +它允许更灵活、更正确地激活快捷方式。 +`LogicalKeySet` 当然也是一种 `ShortcutActivator`, +此外还有 `SingleActivator`,接收单个键及可选的修饰键。 +还有 `CharacterActivator`,根据按键序列产生的字符而非逻辑键本身来激活快捷方式。 +`ShortcutActivator` 也设计为可子类化,以支持从按键事件自定义激活快捷方式的方式。 ::: ### The ShortcutManager +### ShortcutManager + The shortcut manager, a longer-lived object than the `Shortcuts` widget, passes on key events when it receives them. It contains the logic for deciding how to handle the keys, the logic for walking up the tree to find other shortcut mappings, and maintains a map of key combinations to intents. +快捷方式管理器是比 `Shortcuts` widget 生命周期更长的对象,在收到按键事件时传递它们。它包含决定如何处理按键的逻辑、沿树向上查找其他快捷方式映射的逻辑,并维护键组合到 intent 的 map。 + While the default behavior of the `ShortcutManager` is usually desirable, the `Shortcuts` widget takes a `ShortcutManager` that you can subclass to customize its functionality. +虽然 `ShortcutManager` 的默认行为通常符合需求,但 `Shortcuts` widget 可接收你可子类化以自定义功能的 `ShortcutManager`。 + For example, if you wanted to log each key that a `Shortcuts` widget handled, you could make a `LoggingShortcutManager`: +例如,若要记录 `Shortcuts` widget 处理的每个键,可以创建 `LoggingShortcutManager`: + ```dart class LoggingShortcutManager extends ShortcutManager { @@ -197,19 +247,29 @@ class LoggingShortcutManager extends ShortcutManager { Now, every time the `Shortcuts` widget handles a shortcut, it prints out the key event and relevant context. +现在,每次 `Shortcuts` widget 处理快捷方式时,都会打印按键事件和相关 context。 + ## Actions +## 操作 + `Actions` allow for the definition of operations that the application can perform by invoking them with an `Intent`. Actions can be enabled or disabled, and receive the intent instance that invoked them as an argument to allow configuration by the intent. +`Actions` 允许定义应用通过 `Intent` 调用即可执行的操作。操作可启用或禁用,并接收调用它们的 intent 实例作为参数,以便由 intent 配置。 + ### Defining actions +### 定义操作 + Actions, in their simplest form, are just subclasses of `Action` with an `invoke()` method. Here's a simple action that simply invokes a function on the provided model: +操作的最简形式是带有 `invoke()` 方法的 `Action` 子类。下面是一个在提供的 model 上调用函数的简单操作: + ```dart class SelectAllAction extends Action { @@ -224,6 +284,8 @@ class SelectAllAction extends Action { Or, if it's too much of a bother to create a new class, use a `CallbackAction`: +或者,若创建新类太麻烦,可使用 `CallbackAction`: + ```dart CallbackAction(onInvoke: (intent) => model.selectAll()); @@ -232,6 +294,8 @@ CallbackAction(onInvoke: (intent) => model.selectAll()); Once you have an action, you add it to your application using the [`Actions`][] widget, which takes a map of `Intent` types to `Action`s: +有了操作后,使用 [`Actions`][] widget 将其加入应用,该 widget 接收 `Intent` 类型到 `Action` 的 map: + ```dart @override @@ -249,15 +313,23 @@ intent type in the first `Actions` widget encountered, it considers the next ancestor `Actions` widget, and so on, until it reaches the root of the widget tree, or finds a matching intent type and invokes the corresponding action. +`Shortcuts` widget 使用 `Focus` widget 的 context 和 `Actions.invoke` 查找要调用的操作。若第一个遇到的 `Actions` widget 中没有匹配的 intent 类型,会继续考虑上层祖先 `Actions` widget,直至到达 widget 树根或找到匹配的 intent 类型并调用对应操作。 + ### Invoking Actions +### 调用操作 + The actions system has several ways to invoke actions. By far the most common way is through the use of a `Shortcuts` widget covered in the previous section, but there are other ways to interrogate the actions subsystem and invoke an action. It's possible to invoke actions that are not bound to keys. +操作子系统有多种调用操作的方式。最常见的是上一节介绍的 `Shortcuts` widget,但也有其他方式查询操作子系统并调用操作。可以调用未绑定到按键的操作。 + For instance, to find an action associated with an intent, you can use: +例如,要查找与 intent 关联的操作,可以使用: + ```dart Action? selectAll = Actions.maybeFind( @@ -271,8 +343,12 @@ an associated `Action` should always be available, then use `find` instead of `maybeFind`, which throws an exception when it doesn't find a matching `Intent` type. +若在指定 `context` 中有与 `SelectAllIntent` 类型关联的 `Action`,则返回该 `Action`;否则返回 null。若关联的 `Action` 应始终存在,请使用 `find` 而非 `maybeFind`,找不到匹配的 `Intent` 类型时会抛出异常。 + To invoke the action (if it exists), call: +要调用操作(若存在),请调用: + ```dart Object? result; @@ -285,6 +361,8 @@ if (selectAll != null) { Combine that into one call with the following: +也可通过以下方式合并为一次调用: + ```dart Object? result = Actions.maybeInvoke( @@ -302,6 +380,8 @@ However, if it doesn't have a mapping, it returns `null`. This allows the button to be disabled if there is no enabled action that matches in the context. +有时你想在按下按钮或其他控件时调用操作。可使用 `Actions.handler` 函数。若 intent 映射到已启用的操作,`Actions.handler` 会创建处理闭包;若无映射则返回 `null`。这样在上下文中没有匹配的已启用操作时,按钮可被禁用。 + ```dart @override @@ -327,12 +407,16 @@ for invocation. If the action isn't enabled, then the `Actions` widget gives another enabled action higher in the widget hierarchy (if it exists) a chance to execute. +`Actions` widget 仅在 `isEnabled(Intent intent)` 返回 true 时调用操作,允许操作决定调度器是否应考虑调用它。若操作未启用,`Actions` widget 会给 widget 层次结构中更高位置的另一个已启用操作(若存在)执行机会。 + The previous example uses a `Builder` because `Actions.handler` and `Actions.invoke` (for example) only finds actions in the provided `context`, and if the example passes the `context` given to the `build` function, the framework starts looking _above_ the current widget. Using a `Builder` allows the framework to find the actions defined in the same `build` function. +上一示例使用 `Builder`,因为 `Actions.handler` 和 `Actions.invoke`(等)仅在提供的 `context` 中查找操作;若示例传入 `build` 函数得到的 `context`,框架会从*当前* widget 之上开始查找。使用 `Builder` 可使框架找到同一 `build` 函数中定义的操作。 + You can invoke an action without needing a `BuildContext`, but since the `Actions` widget requires a context to find an enabled action to invoke, you need to provide one, either by creating your own `Action` instance, or by @@ -346,11 +430,17 @@ the action is enabled before calling `invoke`. Of course, you can also just call services that an action dispatcher might provide (like logging, undo/redo, and so on). +你可在不需要 `BuildContext` 的情况下调用操作,但由于 `Actions` widget 需要 context 来查找要调用的已启用操作,你需要提供 context:创建自己的 `Action` 实例,或通过 `Actions.find` 在合适的 context 中查找。要调用操作,将操作传给 `ActionDispatcher` 的 `invoke` 方法——可以是自建的,也可通过 `Actions.of(context)` 从现有 `Actions` widget 获取。调用 `invoke` 前检查操作是否已启用。当然也可直接在操作上调用 `invoke` 并传入 `Intent`,但这样会放弃操作调度器可能提供的服务(如日志、撤销/重做等)。 + ### Action dispatchers +### 操作调度器 + Most of the time, you just want to invoke an action, have it do its thing, and forget about it. Sometimes, however, you might want to log the executed actions. +多数情况下,你只需调用操作、让它完成工作即可。但有时你可能想记录已执行的操作。 + This is where replacing the default `ActionDispatcher` with a custom dispatcher comes in. You pass your `ActionDispatcher` to the `Actions` widget, and it invokes actions from any `Actions` widgets below that one that doesn't set a @@ -363,6 +453,8 @@ it creates a default `ActionDispatcher` that simply invokes the action. If you want a log of all the actions invoked, however, you can create your own `LoggingActionDispatcher` to do the job: +此时可将默认 `ActionDispatcher` 替换为自定义调度器。将 `ActionDispatcher` 传给 `Actions` widget,它会调用其下未自行设置调度器的任何 `Actions` widget 中的操作。`Actions` 调用操作时首先查找 `ActionDispatcher` 并将操作交给它调用;若没有则创建默认 `ActionDispatcher` 直接调用操作。若需要所有已调用操作的日志,可创建自己的 `LoggingActionDispatcher`: + ```dart class LoggingActionDispatcher extends ActionDispatcher { @@ -392,6 +484,8 @@ class LoggingActionDispatcher extends ActionDispatcher { Then you pass that to your top-level `Actions` widget: +然后将它传给顶层 `Actions` widget: + ```dart @override @@ -414,12 +508,16 @@ Widget build(BuildContext context) { This logs every action as it executes, like so: +执行时会记录每个操作,例如: + ```console flutter: Action invoked: SelectAllAction#906fc(SelectAllIntent#a98e3) from Builder(dependencies: _[ActionsMarker]) ``` ## Putting it together +## 综合示例 + The combination of `Actions` and `Shortcuts` is powerful: you can define generic intents that map to specific actions at the widget level. Here's a simple app that illustrates the concepts described above. The app creates a text field that @@ -427,6 +525,8 @@ also has "select all" and "copy to clipboard" buttons next to it. The buttons invoke actions to accomplish their work. All the invoked actions and shortcuts are logged. +`Actions` 与 `Shortcuts` 的组合很强大:你可以在 widget 层级定义映射到具体操作的通用 intent。下面是一个说明上文概念的简单应用:应用创建一个文本字段,旁边有「全选」和「复制到剪贴板」按钮;按钮通过调用操作完成工作;所有被调用的操作和快捷方式都会记录日志。 + ```dartpad title="Copyable text DartPad hands-on example" run="true" import 'package:flutter/material.dart'; diff --git a/sites/docs/src/content/ui/interactivity/focus.md b/sites/docs/src/content/ui/interactivity/focus.md index 855feedff4..c953f66ae9 100644 --- a/sites/docs/src/content/ui/interactivity/focus.md +++ b/sites/docs/src/content/ui/interactivity/focus.md @@ -1,6 +1,9 @@ --- -title: Understanding Flutter's keyboard focus system -description: How to use the focus system in your Flutter app. +# title: Understanding Flutter's keyboard focus system +title: 理解 Flutter 的键盘焦点系统 +# description: How to use the focus system in your Flutter app. +description: 如何在你的 Flutter 应用中使用焦点系统。 +ai-translated: true --- This article explains how to control where keyboard input is directed. If you @@ -8,8 +11,12 @@ are implementing an application that uses a physical keyboard, such as most desktop and web applications, this page is for you. If your app won't be used with a physical keyboard, you can skip this. +本文说明如何控制键盘输入的指向。若你正在实现使用物理键盘的应用(例如大多数桌面和 Web 应用),本页适合你。若你的应用不会配合物理键盘使用,可以跳过本文。 + ## Overview +## 概览 + Flutter comes with a focus system that directs the keyboard input to a particular part of an application. In order to do this, users "focus" the input onto that part of an application by tapping or clicking the desired UI element. @@ -18,17 +25,25 @@ application until the focus moves to another part of the application. Focus can also be moved by pressing a particular keyboard shortcut, which is typically bound to Tab, so it is sometimes called "tab traversal". +Flutter 自带焦点系统,将键盘输入导向应用的特定部分。为此,用户通过点击或点按所需的 UI 元素,将输入「焦点」到应用的该部分。之后,键盘输入的文字会流向该部分,直到焦点移到应用的其他部分。焦点也可以通过按下特定键盘快捷键来移动,通常绑定到 Tab,因此有时称为「Tab 遍历」(tab traversal)。 + This page explores the APIs used to perform these operations on a Flutter application, and how the focus system works. We have noticed that there is some confusion among developers about how to define and use [`FocusNode`][] objects. If that describes your experience, skip ahead to the [best practices for creating `FocusNode` objects](#best-practices-for-creating-focusnode-objects). +本页探讨在 Flutter 应用中执行这些操作所用的 API,以及焦点系统的工作原理。我们注意到开发者在如何定义和使用 [`FocusNode`][] 对象方面存在一些困惑。若你也有类似经历,请跳转到[创建 `FocusNode` 对象的最佳实践](#best-practices-for-creating-focusnode-objects)。 + ### Focus use cases +### 焦点使用场景 + Some examples of situations where you might need to know how to use the focus system: +以下是你可能需要了解如何使用焦点系统的一些场景示例: + - [Receiving/handling key events](#key-events) - [Implementing a custom component that needs to be focusable](#focus-widget) - [Receiving notifications when the focus changes](#change-notifications) @@ -36,11 +51,22 @@ system: - [Defining groups of controls that should be traversed together](#focustraversalgroup-widget) - [Preventing some controls in an application from being focusable](#controlling-what-gets-focus) + [接收/处理按键事件](#key-events) +- [实现需要可获得焦点的自定义 widget](#focus-widget) +- [在焦点变化时接收通知](#change-notifications) +- [更改或定义应用中焦点遍历的「Tab 顺序」](#focustraversalpolicy) +- [定义应一起遍历的控件组](#focustraversalgroup-widget) +- [阻止应用中某些控件获得焦点](#controlling-what-gets-focus) + ## Glossary +## 术语表 + Below are terms, as Flutter uses them, for elements of the focus system. The various classes that implement some of these concepts are introduced below. +以下是 Flutter 对焦点系统各元素的术语定义。实现其中部分概念的各种类将在下文介绍。 + - **Focus tree** - A tree of focus nodes that typically sparsely mirrors the widget tree, representing all the widgets that can receive focus. - **Focus node** - A single node in a focus tree. This node can receive the @@ -60,14 +86,21 @@ various classes that implement some of these concepts are introduced below. the user presses Tab to move to the next focusable control or field. + + **焦点树(Focus tree)** — 焦点节点组成的树,通常稀疏地镜像 widget 树,表示所有可获得焦点的 widget。 + ## FocusNode and FocusScopeNode +## FocusNode 与 FocusScopeNode + The `FocusNode` and [`FocusScopeNode`][] objects implement the mechanics of the focus system. They are long-lived objects (longer than widgets, similar to render objects) that hold the focus state and attributes so that they are persistent between builds of the widget tree. Together, they form the focus tree data structure. +`FocusNode` 和 [`FocusScopeNode`][] 对象实现焦点系统的机制。它们是长生命周期对象(比 widget 更持久,类似 render 对象),保存焦点状态与属性,从而在 widget 树多次构建之间保持持久。它们共同构成焦点树数据结构。 + They were originally intended to be developer-facing objects used to control some aspects of the focus system, but over time they have evolved to mostly implement details of the focus system. In order to prevent breaking existing @@ -79,10 +112,16 @@ Setting of the other attributes is best managed by a [`Focus`][] or [`FocusScope`][] widget, unless you are not using them, or implementing your own version of them. +它们最初旨在作为面向开发者的对象,用于控制焦点系统的某些方面,但随着时间推移,已大多演变为实现焦点系统细节。为避免破坏现有应用,它们仍保留属性的公开接口。但一般而言,它们最有用的用途是作为相对不透明的句柄,传给子 widget,以便在祖先 widget 上调用 `requestFocus()`,请求子 widget 获得焦点。其他属性的设置最好由 [`Focus`][] 或 [`FocusScope`][] widget 管理,除非你未使用它们,或正在实现自己的版本。 + ### Best practices for creating FocusNode objects +### 创建 FocusNode 对象的最佳实践 + Some dos and don'ts around using these objects include: +使用这些对象时的一些建议与禁忌包括: + - Don't allocate a new `FocusNode` for each build. This can cause memory leaks, and occasionally causes a loss of focus when the widget rebuilds while the node has focus. @@ -112,8 +151,18 @@ Some dos and don'ts around using these objects include: `FocusScope.of(context).requestFocus(focusNode)`. The `focusNode.requestFocus()` method is equivalent and more performant. + 不要在每次 build 时分配新的 `FocusNode`。这可能导致内存泄漏,且当节点拥有焦点时 widget 重建偶尔会导致失去焦点。 +- 应在有状态 widget 中创建 `FocusNode` 和 `FocusScopeNode` 对象。使用完毕后需要 dispose,因此应只在有状态 widget 的 state 对象内创建,以便在 `dispose` 中释放它们。 +- 不要对多个 widget 使用同一个 `FocusNode`。否则 widget 会争夺节点属性的管理权,结果往往不符合预期。 +- 应设置焦点节点 widget 的 `debugLabel`,以便诊断焦点问题。 +- 若 `FocusNode` 或 `FocusScopeNode` 由 `Focus` 或 `FocusScope` widget 管理,不要在其上设置 `onKeyEvent` 回调。若需要 `onKeyEvent` 处理器,在你想监听的 widget 子树外再包一层 `Focus` widget,并将该 widget 的 `onKeyEvent` 属性设为你的处理器。若你也不希望它能获得主焦点,将 widget 的 `canRequestFocus` 设为 false。这是因为 `Focus` widget 的 `onKeyEvent` 属性可能在后续 build 中被设为其他值,从而覆盖你在节点上设置的 `onKeyEvent` 处理器。 +- 应在节点上调用 `requestFocus()` 以请求其获得主焦点,尤其是从已将自有节点传给子代的祖先处,在你希望获得焦点的子代上调用。 +- 应使用 `focusNode.requestFocus()`。不必调用 `FocusScope.of(context).requestFocus(focusNode)`。`focusNode.requestFocus()` 方法等价且性能更好。 + ### Unfocusing +### 取消焦点 + There is an API for telling a node to "give up the focus", named `FocusNode.unfocus()`. While it does remove focus from the node, it is important to realize that there really is no such thing as "unfocusing" all nodes. If a @@ -126,6 +175,8 @@ a node, explicitly focus another node instead of calling `unfocus()`, or use the focus traversal mechanism to find another node with the `focusInDirection`, `nextFocus`, or `previousFocus` methods on `FocusNode`. +有一个 API 用于让节点「放弃焦点」,名为 `FocusNode.unfocus()`。虽然它会从该节点移除焦点,但重要的是要理解,并不存在真正「取消所有节点焦点」这回事。若某节点失去焦点,焦点必须转移到别处,因为*始终*存在主焦点。节点调用 `unfocus()` 时接收焦点的节点,取决于传给 `unfocus()` 的 `disposition` 参数,要么是最近的 `FocusScopeNode`,要么是该作用域内先前拥有焦点的节点。若你想更精确地控制从某节点移除焦点后焦点去向,应显式让另一节点获得焦点,而不是调用 `unfocus()`,或使用焦点遍历机制,通过 `FocusNode` 上的 `focusInDirection`、`nextFocus` 或 `previousFocus` 方法找到另一节点。 + When calling `unfocus()`, the `disposition` argument allows two modes for unfocusing: [`UnfocusDisposition.scope`][] and `UnfocusDisposition.previouslyFocusedChild`. The default is `scope`, which gives @@ -133,10 +184,14 @@ the focus to the nearest parent focus scope. This means that if the focus is thereafter moved to the next node with `FocusNode.nextFocus`, it starts with the "first" focusable item in the scope. +调用 `unfocus()` 时,`disposition` 参数提供两种取消焦点模式:[`UnfocusDisposition.scope`][] 和 `UnfocusDisposition.previouslyFocusedChild`。默认为 `scope`,将焦点交给最近的父焦点作用域。这意味着若之后用 `FocusNode.nextFocus` 将焦点移到下一节点,会从作用域内「第一个」可获得焦点的项开始。 + The `previouslyFocusedChild` disposition will search the scope to find the previously focused child and request focus on it. If there is no previously focused child, it is equivalent to `scope`. +`previouslyFocusedChild` 处置会在作用域内查找先前拥有焦点的子节点并请求其获得焦点。若没有先前拥有焦点的子节点,则与 `scope` 等价。 + :::secondary Beware If there is no other scope, then focus moves to the root scope node of the focus system, `FocusManager.rootScope`. This is generally not desirable, as @@ -147,22 +202,30 @@ happened. To fix it, add a `FocusScope` as an ancestor to the focus node that is requesting the unfocus. The `WidgetsApp` (from which `MaterialApp` and `CupertinoApp` are derived) has its own `FocusScope`, so this should not be an issue if you are using those. + +若没有其他作用域,焦点会移到焦点系统的根作用域节点 `FocusManager.rootScope`。这通常不理想,因为根作用域没有 `context`,框架无法确定下一个应获得焦点的节点。若你发现应用突然无法通过焦点遍历导航,可能就是发生了这种情况。修复方法是在请求取消焦点的焦点节点的祖先处添加 `FocusScope`。`WidgetsApp`(`MaterialApp` 和 `CupertinoApp` 均派生自它)自带 `FocusScope`,因此若你使用这些应用壳,一般不会有此问题。 ::: ## Focus widget +## Focus widget + The `Focus` widget owns and manages a focus node, and is the workhorse of the focus system. It manages the attaching and detaching of the focus node it owns from the focus tree, manages the attributes and callbacks of the focus node, and has static functions to enable discovery of focus nodes attached to the widget tree. +`Focus` widget 拥有并管理焦点节点,是焦点系统的主力。它管理其拥有的焦点节点在焦点树上的挂载与卸载,管理焦点节点的属性与回调,并提供静态函数以便发现挂载在 widget 树上的焦点节点。 + In its simplest form, wrapping the `Focus` widget around a widget subtree allows that widget subtree to obtain focus as part of the focus traversal process, or whenever `requestFocus` is called on the `FocusNode` passed to it. When combined with a gesture detector that calls `requestFocus`, it can receive focus when tapped or clicked. +最简单用法是用 `Focus` widget 包裹 widget 子树,使该子树在焦点遍历过程中,或在传入的 `FocusNode` 上调用 `requestFocus` 时获得焦点。若与调用 `requestFocus` 的手势检测器配合,可在点按或点击时获得焦点。 + You might pass a `FocusNode` object to the `Focus` widget to manage, but if you don't, it creates its own. The main reason to create your own `FocusNode` is to be able to call `requestFocus()` @@ -170,13 +233,19 @@ on the node to control the focus from a parent widget. Most of the other functionality of a `FocusNode` is best accessed by changing the attributes of the `Focus` widget itself. +你可以将 `FocusNode` 对象传给 `Focus` widget 由其管理;若不传,它会自行创建。自行创建 `FocusNode` 的主要原因是从父 widget 在节点上调用 `requestFocus()` 以控制焦点。`FocusNode` 的其他大部分功能最好通过修改 `Focus` widget 自身的属性来访问。 + The `Focus` widget is used in most of Flutter's own controls to implement their focus functionality. +Flutter 的大多数内置控件都使用 `Focus` widget 实现其焦点功能。 + Here is an example showing how to use the `Focus` widget to make a custom control focusable. It creates a container with text that reacts to receiving the focus. +下面示例展示如何使用 `Focus` widget 使自定义控件可获得焦点。它创建一个带文字的容器,在获得焦点时做出反应。 + ```dart import 'package:flutter/material.dart'; @@ -238,11 +307,15 @@ class _MyCustomWidgetState extends State { ### Key events +### 按键事件 + If you wish to listen for key events in a subtree, set the `onKeyEvent` attribute of the `Focus` widget to be a handler that either just listens to the key, or handles the key and stops its propagation to other widgets. +若要在子树中监听按键事件,将 `Focus` widget 的 `onKeyEvent` 属性设为处理器,该处理器仅监听按键,或处理按键并阻止其向其他 widget 传播。 + Key events start at the focus node with primary focus. If that node doesn't return `KeyEventResult.handled` from its `onKeyEvent` handler, then its parent focus node is given the event. @@ -255,9 +328,13 @@ the next native control in the application Events that are handled are not propagated to other Flutter widgets, and they are also not propagated to native widgets. +按键事件从拥有主焦点的焦点节点开始。若该节点的 `onKeyEvent` 处理器未返回 `KeyEventResult.handled`,则事件交给其父焦点节点。若父节点未处理,则继续向上,直至焦点树根。若事件到达焦点树根仍未被处理,则返回平台,交给应用中的下一个原生控件(当 Flutter UI 是更大原生应用 UI 的一部分时)。已处理的事件不会传播到其他 Flutter widget,也不会传播到原生 widget。 + Here's an example of a `Focus` widget that absorbs every key that its subtree doesn't handle, without being able to be the primary focus: +下面是一个 `Focus` widget 示例,它吸收子树未处理的每个按键,且自身不能成为主焦点: + ```dart @override @@ -274,9 +351,13 @@ Focus key events are processed before text entry events, so handling a key event when the focus widget surrounds a text field prevents that key from being entered into the text field. +焦点按键事件在文本输入事件之前处理,因此在焦点 widget 包裹文本字段时处理按键事件会阻止该键输入到文本字段。 + Here's an example of a widget that won't allow the letter "a" to be typed into the text field: +下面是一个不允许在文本字段中输入字母「a」的 widget 示例: + ```dart @override @@ -297,67 +378,99 @@ be better implemented using a `TextInputFormatter`, but the technique can still be useful: the `Shortcuts` widget uses this method to handle shortcuts before they become text input, for instance. +若目的是输入校验,该示例功能用 `TextInputFormatter` 实现可能更合适,但该技巧仍有用处:例如 `Shortcuts` widget 用此方法在快捷键成为文本输入之前处理它们。 + ### Controlling what gets focus +### 控制哪些可获得焦点 + One of the main aspects of focus is controlling what can receive focus and how. The attributes `canRequestFocus`, `skipTraversal,` and `descendantsAreFocusable` control how this node and its descendants participate in the focus process. +焦点的主要方面之一是控制什么可以获得焦点以及如何获得。属性 `canRequestFocus`、`skipTraversal` 和 `descendantsAreFocusable` 控制该节点及其子代如何参与焦点过程。 + If the `skipTraversal` attribute true, then this focus node doesn't participate in focus traversal. It is still focusable if `requestFocus` is called on its focus node, but is otherwise skipped when the focus traversal system is looking for the next thing to focus on. +若 `skipTraversal` 属性为 true,则该焦点节点不参与焦点遍历。若在其焦点节点上调用 `requestFocus`,仍可获得焦点,但在焦点遍历系统寻找下一个焦点目标时会被跳过。 + The `canRequestFocus` attribute, unsurprisingly, controls whether or not the focus node that this `Focus` widget manages can be used to request focus. If this attribute is false, then calling `requestFocus` on the node has no effect. It also implies that this node is skipped for focus traversal, since it can't request focus. +`canRequestFocus` 属性(顾名思义)控制该 `Focus` widget 管理的焦点节点是否可用于请求焦点。若该属性为 false,在节点上调用 `requestFocus` 无效。这也意味着该节点在焦点遍历中被跳过,因为它无法请求焦点。 + The `descendantsAreFocusable` attribute controls whether the descendants of this node can receive focus, but still allows this node to receive focus. This attribute can be used to turn off focusability for an entire widget subtree. This is how the `ExcludeFocus` widget works: it's just a `Focus` widget with this attribute set. +`descendantsAreFocusable` 属性控制该节点的子代是否可获得焦点,但仍允许该节点自身获得焦点。该属性可用于关闭整个 widget 子树的焦点能力。`ExcludeFocus` widget 就是这样工作的:它只是将该属性设好的 `Focus` widget。 + ### Autofocus +### 自动焦点 + Setting the `autofocus` attribute of a `Focus` widget tells the widget to request the focus the first time the focus scope it belongs to is focused. If more than one widget has `autofocus` set, then it is arbitrary which one receives the focus, so try to only set it on one widget per focus scope. +将 `Focus` widget 的 `autofocus` 属性设为 true 会告诉 widget 在其所属焦点作用域第一次获得焦点时请求焦点。若有多个 widget 设置了 `autofocus`,哪个获得焦点是任意的,因此尽量每个焦点作用域只在一个 widget 上设置。 + The `autofocus` attribute only takes effect if there isn't already a focus in the scope that the node belongs to. +仅当节点所属作用域内尚无焦点时,`autofocus` 属性才会生效。 + Setting the `autofocus` attribute on two nodes that belong to different focus scopes is well defined: each one becomes the focused widget when their corresponding scopes are focused. +在两个属于不同焦点作用域的节点上设置 `autofocus` 是明确定义的:各自在对应作用域获得焦点时成为获得焦点的 widget。 + ### Change notifications +### 变化通知 + The `Focus.onFocusChanged` callback can be used to get notifications that the focus state for a particular node has changed. It notifies if the node is added to or removed from the focus chain, which means it gets notifications even if it isn't the primary focus. If you only want to know if you have received the primary focus, check and see if `hasPrimaryFocus` is true on the focus node. +`Focus.onFocusChanged` 回调可用于在特定节点的焦点状态变化时收到通知。节点被加入或移出焦点链时都会通知,这意味着即使不是主焦点也会收到通知。若你只想知道是否获得了主焦点,请检查焦点节点上的 `hasPrimaryFocus` 是否为 true。 + ### Obtaining the FocusNode +### 获取 FocusNode + Sometimes, it is useful to obtain the focus node of a `Focus` widget to interrogate its attributes. +有时需要获取 `Focus` widget 的焦点节点以查询其属性。 + To access the focus node from an ancestor of the `Focus` widget, create and pass in a `FocusNode` as the `Focus` widget's `focusNode` attribute. Because it needs to be disposed of, the focus node you pass needs to be owned by a stateful widget, so don't just create one each time it is built. +要从 `Focus` widget 的祖先访问焦点节点,创建 `FocusNode` 并作为 `Focus` widget 的 `focusNode` 属性传入。因其需要 dispose,你传入的焦点节点应由有状态 widget 拥有,不要在每次 build 时新建。 + If you need access to the focus node from the descendant of a `Focus` widget, you can call `Focus.of(context)` to obtain the focus node of the nearest `Focus `widget to the given context. If you need to obtain the `FocusNode` of a `Focus` widget within the same build function, use a [`Builder`][] to make sure you have the correct context. This is shown in the following example: +若要从 `Focus` widget 的子代访问焦点节点,可调用 `Focus.of(context)` 获取距给定 context 最近的 `Focus` widget 的焦点节点。若要在同一 build 函数内获取 `Focus` widget 的 `FocusNode`,请使用 [`Builder`][] 确保 context 正确。如下例所示: + ```dart @override @@ -376,6 +489,8 @@ Widget build(BuildContext context) { ### Timing +### 时机 + One of the details of the focus system is that when focus is requested, it only takes effect after the current build phase completes. This means that focus changes are always delayed by one frame, because changing focus can @@ -384,6 +499,10 @@ widget currently requesting focus. Because descendants cannot dirty their ancestors, it has to happen between frames, so that any needed changes can happen on the next frame. +焦点系统的一个细节是:请求焦点时,仅在当前 build 阶段完成后才生效。这意味着焦点变化总是延迟一帧,因为改变焦点可能导致 widget 树任意部分(包括当前请求焦点的 widget 的祖先)重建。子代不能使祖先变脏,因此必须在帧之间进行,以便所需变化在下一帧发生。 + +## FocusScope widget + ## FocusScope widget The `FocusScope` widget is a special version of the `Focus` widget that manages @@ -392,19 +511,29 @@ node in the focus tree that serves as a grouping mechanism for the focus nodes in a subtree. Focus traversal stays within a focus scope unless a node outside of the scope is explicitly focused. +`FocusScope` widget 是 `Focus` widget 的特殊版本,管理 `FocusScopeNode` 而非 `FocusNode`。`FocusScopeNode` 是焦点树中的特殊节点,作为子树中焦点节点的分组机制。除非显式聚焦作用域外的节点,否则焦点遍历停留在焦点作用域内。 + The focus scope also keeps track of the current focus and history of the nodes focused within its subtree. That way, if a node releases focus or is removed when it had focus, the focus can be returned to the node that had focus previously. +焦点作用域还跟踪其子树内当前焦点及曾获得焦点的节点历史。这样,若某节点在拥有焦点时释放焦点或被移除,焦点可返回到先前拥有焦点的节点。 + Focus scopes also serve as a place to return focus to if none of the descendants have focus. This allows the focus traversal code to have a starting context for finding the next (or first) focusable control to move to. +当没有子代拥有焦点时,焦点作用域也作为焦点返回的落脚点。这使焦点遍历代码有起始上下文,用于查找下一个(或第一个)可移到的可获得焦点的控件。 + If you focus a focus scope node, it first attempts to focus the current, or most recently focused node in its subtree, or the node in its subtree that requested autofocus (if any). If there is no such node, it receives the focus itself. +若你聚焦焦点作用域节点,它会先尝试聚焦其子树中当前或最近拥有焦点的节点,或请求了 autofocus 的节点(若有)。若没有此类节点,则由作用域节点自身获得焦点。 + +## FocusableActionDetector widget + ## FocusableActionDetector widget The [`FocusableActionDetector`][] is a widget that combines the functionality of @@ -416,15 +545,23 @@ constituent widgets, so if you don't need all of its functionality, you can just use the ones you need, but it is a convenient way to build these behaviors into your custom controls. +[`FocusableActionDetector`][] 是将 [`Actions`][]、[`Shortcuts`][]、[`MouseRegion`][] 与 `Focus` widget 的功能组合在一起的 widget,用于创建定义动作与按键绑定、并提供处理焦点与悬停高亮回调的检测器。Flutter 控件用它实现控件的上述所有方面。它仅用组成 widget 实现,因此若你不需要全部功能,可只使用需要的部分,但它是将这些行为融入自定义控件的便捷方式。 + :::note To learn more, watch this short Widget of the Week video on the `FocusableActionDetector` widget: + + +想了解更多,请观看关于 `FocusableActionDetector` widget 的简短「每周 Widget」视频: + ::: ## Controlling focus traversal +## 控制焦点遍历 + Once an application has the ability to focus, the next thing many apps want to do is to allow the user to control the focus using the keyboard or another input device. The most common example of this is "tab traversal" where the user @@ -432,12 +569,16 @@ presses Tab to go to the "next" control. Controlling what "next" means is the subject of this section. This kind of traversal is provided by Flutter by default. +应用具备焦点能力后,许多应用接下来希望让用户用键盘或其他输入设备控制焦点。最常见的是「Tab 遍历」:用户按 Tab 移到「下一个」控件。控制「下一个」的含义是本节主题。Flutter 默认提供此类遍历。 + In a simple grid layout, it's fairly easy to decide which control is next. If you're not at the end of the row, then it's the one to the right (or left for right-to-left locales). If you are at the end of a row, it's the first control in the next row. Unfortunately, applications are rarely laid out in grids, so more guidance is often needed. +在简单网格布局中,较容易决定下一个控件。若不在行末,则是右侧(从右到左语言环境则为左侧)的控件。若在行末,则是下一行第一个控件。遗憾的是,应用很少按网格布局,因此往往需要更多指引。 + The default algorithm in Flutter ([`ReadingOrderTraversalPolicy`][]) for focus traversal is pretty good: It gives the right answer for most applications. However, there are always pathological cases, or cases where the context or @@ -445,6 +586,10 @@ design requires a different order than the one the default ordering algorithm arrives at. For those cases, there are other mechanisms for achieving the desired order. +Flutter 用于焦点遍历的默认算法([`ReadingOrderTraversalPolicy`][])相当好:对大多数应用能给出正确结果。但总有极端情况,或上下文/设计要求的顺序与默认排序算法不同。对这些情况,有其他机制可实现所需顺序。 + +### FocusTraversalGroup widget + ### FocusTraversalGroup widget The [`FocusTraversalGroup`][] widget should be placed in the tree around widget @@ -453,6 +598,8 @@ group of widgets. Just grouping widgets into related groups is often enough to resolve many tab traversal ordering problems. If not, the group can also be given a [`FocusTraversalPolicy`][] to determine the ordering within the group. +[`FocusTraversalGroup`][] widget 应放在 widget 树中,包裹应在移到其他 widget 或 widget 组之前完整遍历的 widget 子树。仅将 widget 分组为相关组往往足以解决许多 Tab 遍历顺序问题。若不够,还可为组指定 [`FocusTraversalPolicy`][] 以确定组内顺序。 + The default [`ReadingOrderTraversalPolicy`][] is usually sufficient, but in cases where more control over ordering is needed, an [`OrderedTraversalPolicy`][] can be used. The `order` argument of the @@ -460,13 +607,19 @@ cases where more control over ordering is needed, an determines the order. The order can be any subclass of [`FocusOrder`][], but [`NumericFocusOrder`][] and [`LexicalFocusOrder`][] are provided. +默认的 [`ReadingOrderTraversalPolicy`][] 通常足够,但若需要更多顺序控制,可使用 [`OrderedTraversalPolicy`][]。包裹可获得焦点的 widget 的 [`FocusTraversalOrder`][] widget 的 `order` 参数决定顺序。顺序可以是 [`FocusOrder`][] 的任意子类,但提供了 [`NumericFocusOrder`][] 和 [`LexicalFocusOrder`][]。 + If none of the provided focus traversal policies are sufficient for your application, you could also write your own policy and use it to determine any custom ordering you want. +若提供的焦点遍历策略都不满足应用需求,你也可以编写自己的策略,以确定任意自定义顺序。 + Here's an example of how to use the `FocusTraversalOrder` widget to traverse a row of buttons in the order TWO, ONE, THREE using `NumericFocusOrder`. +下面示例展示如何使用 `FocusTraversalOrder` widget,通过 `NumericFocusOrder` 按 TWO、ONE、THREE 顺序遍历一行按钮: + ```dart class OrderedButtonRow extends StatelessWidget { @@ -503,28 +656,40 @@ class OrderedButtonRow extends StatelessWidget { ### FocusTraversalPolicy +### FocusTraversalPolicy + The `FocusTraversalPolicy` is the object that determines which widget is next, given a request and the current focus node. The requests (member functions) are things like `findFirstFocus`, `findLastFocus`, `next`, `previous`, and `inDirection`. +`FocusTraversalPolicy` 是根据请求和当前焦点节点决定下一个 widget 的对象。请求(成员函数)包括 `findFirstFocus`、`findLastFocus`、`next`、`previous` 和 `inDirection` 等。 + `FocusTraversalPolicy` is the abstract base class for concrete policies, like `ReadingOrderTraversalPolicy`, `OrderedTraversalPolicy` and the [`DirectionalFocusTraversalPolicyMixin`][] classes. +`FocusTraversalPolicy` 是具体策略的抽象基类,例如 `ReadingOrderTraversalPolicy`、`OrderedTraversalPolicy` 以及 [`DirectionalFocusTraversalPolicyMixin`][] 类。 + In order to use a `FocusTraversalPolicy`, you give one to a `FocusTraversalGroup`, which determines the widget subtree in which the policy will be effective. The member functions of the class are rarely called directly: they are meant to be used by the focus system. +要使用 `FocusTraversalPolicy`,需将其交给 `FocusTraversalGroup`,由后者确定策略生效的 widget 子树。该类的成员函数很少直接调用:它们供焦点系统使用。 + ## The focus manager +## 焦点管理器 + The [`FocusManager`][] maintains the current primary focus for the system. It only has a few pieces of API that are useful to users of the focus system. One is the `FocusManager.instance.primaryFocus` property, which contains the currently focused focus node and is also accessible from the global `primaryFocus` field. +[`FocusManager`][] 维护系统当前的主焦点。它对焦点系统用户仅有少量有用 API。之一是 `FocusManager.instance.primaryFocus` 属性,包含当前获得焦点的焦点节点,也可通过全局 `primaryFocus` 字段访问。 + Other useful properties are `FocusManager.instance.highlightMode` and `FocusManager.instance.highlightStrategy`. These are used by widgets that need to switch between a "touch" mode and a "traditional" (mouse and keyboard) mode @@ -539,6 +704,8 @@ information, so you only need it if you're writing your own controls from scratch. You can use `addHighlightModeListener` callback to listen for changes in the highlight mode. +其他有用属性包括 `FocusManager.instance.highlightMode` 和 `FocusManager.instance.highlightStrategy`。需要在其焦点高亮之间切换「触摸」模式与「传统」(鼠标和键盘)模式的 widget 会使用它们。用户用触摸导航时,焦点高亮通常隐藏;切换到鼠标或键盘时,需再次显示焦点高亮,以便知道当前焦点在哪。`highlightStrategy` 告诉焦点管理器如何解释设备使用模式的变化:可根据最近输入事件自动在两种模式间切换,或锁定为触摸或传统模式。Flutter 提供的 widget 已知道如何使用这些信息,因此仅在你从零编写自己的控件时才需要。可用 `addHighlightModeListener` 回调监听高亮模式变化。 + [`Actions`]: {{site.api}}/flutter/widgets/Actions-class.html [`Builder`]: {{site.api}}/flutter/widgets/Builder-class.html [`DirectionalFocusTraversalPolicyMixin`]: {{site.api}}/flutter/widgets/DirectionalFocusTraversalPolicyMixin-mixin.html diff --git a/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md b/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md index 155b7d780a..4a262f1c5e 100644 --- a/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md +++ b/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md @@ -1,32 +1,54 @@ --- -title: Drag outside an app -description: How to drag from an app to another app or the operating system. +# title: Drag outside an app +title: 在应用外拖拽 +# description: How to drag from an app to another app or the operating system. +description: 如何从应用拖拽到另一个应用或操作系统。 +ai-translated: true --- You might want to implement drag and drop somewhere in your app. +你可能希望在应用的某个位置实现 +拖拽(drag and drop)功能。 + You have a couple potential approaches that you can take. One directly uses Flutter widgets and the other uses a package ([super_drag_and_drop][]), available on [pub.dev][]. +你可以采用几种潜在方案。 +一种直接使用 Flutter widget,另一种使用 [pub.dev][] 上的 package +([super_drag_and_drop][])。 + [pub.dev]: {{site.pub}} [super_drag_and_drop]: {{site.pub-pkg}}/super_drag_and_drop ## Create draggable widgets within your app +## 在应用内创建可拖拽 widget + If you want to implement drag and drop within your application, you can use the [`Draggable`][] widget. For insight into this approach, see the [Drag a UI element within an app][] recipe. +若要在应用内实现拖拽,可以使用 [`Draggable`][] +widget。要了解该做法,请参阅 +[在应用内拖拽 UI 元素][Drag a UI element within an app] Cookbook 教程。 + An advantage of using `Draggable` and `DragTarget` is that you can supply Dart code to decide whether to accept a drop. +使用 `Draggable` 和 `DragTarget` 的一个优点是, +你可以编写 Dart 代码来决定是否接受放置。 + For more information, check out the [`Draggable` widget of the week][video] video. +更多信息请参阅 +[`Draggable` widget of the week][video] 视频。 + [Drag a UI element within an app]: /cookbook/effects/drag-a-widget [`Draggable`]: {{site.api}}/flutter/widgets/Draggable-class.html [`DragTarget`]: {{site.api}}/flutter/widgets/DragTarget-class.html @@ -35,17 +57,27 @@ For more information, check out the ## Implement drag and drop between apps +## 在应用之间实现拖拽 + If you want to implement drag and drop within your application and _also_ between your application and another (possibly non-Flutter) app, check out the [super_drag_and_drop][] package. +若要在应用内实现拖拽,**并且**还要在 +你的应用与另一个(可能非 Flutter)应用之间实现拖拽, +请查看 [super_drag_and_drop][] package。 + To avoid implementing two styles of drag and drop, one for drags outside of the app and another for dragging inside the app, you can supply [local data][] to the package to perform drags within your app. +为避免实现两套拖拽方式——一套用于应用外拖拽、另一套用于应用内拖拽—— +你可以向该 package 提供 [local data][], +以在应用内执行拖拽。 + Another difference between this approach and using `Draggable` directly, is that you must tell the package up front @@ -53,5 +85,11 @@ what data your app accepts because the platform APIs need a synchronous response, which doesn't allow an asynchronous response from the framework. +该做法与直接使用 `Draggable` 的另一项区别是, +你必须事先告知 package 你的应用接受哪些数据, +因为平台 API 需要同步响应,而框架无法提供异步响应。 + An advantage of using this approach is that it works across desktop, mobile, _and_ web. + +该做法的一个优点是它适用于桌面、移动端**以及** Web。 diff --git a/sites/docs/src/content/ui/interactivity/index.md b/sites/docs/src/content/ui/interactivity/index.md index c145ffbe59..cf7567852d 100644 --- a/sites/docs/src/content/ui/interactivity/index.md +++ b/sites/docs/src/content/ui/interactivity/index.md @@ -7,6 +7,7 @@ description: 如何实现一个能够响应点击事件的有状态 widget。 shortTitle: 交互性 tags: 用户界面,Flutter UI,布局 keywords: 交互,Flutter交互,有状态的widget,无状态,StatefulWidget,状态管理 +ai-translated: true --- :::secondary 你将学到什么 @@ -711,7 +712,7 @@ widget needs to know whether the button has been tapped, so it can take appropriate action. 一般来说父 widget 管理状态并告诉其子 widget 何时更新通常是最合适的。 -例如,[`IconButton`][] 允许你将图标视为可点按的按钮。 +例如,[`IconButton`][] 让你将图标视为可点按的按钮。 `IconButton` 是一个无状态 widget, 因为我们认为父 widget 需要知道该按钮是否被点击来采取相应的处理。 @@ -1039,7 +1040,7 @@ in [Handle taps][], a recipe in the Flutter cookbook. Flutter also provides a set of iOS-style widgets called [`Cupertino`][]. -Flutter 还提供了一组名为 [`Cupertino`][] 的 iOS 风格的小部件。 +Flutter 还提供了一组名为 [`Cupertino`][] 的 iOS 风格的 widget。 ::: diff --git a/sites/docs/src/content/ui/interactivity/input/index.md b/sites/docs/src/content/ui/interactivity/input/index.md index 0f7a79a007..858764d231 100644 --- a/sites/docs/src/content/ui/interactivity/input/index.md +++ b/sites/docs/src/content/ui/interactivity/input/index.md @@ -1,6 +1,11 @@ --- +# layout: toc layout: toc -title: Input & forms -description: Content covering handling input and adding forms to Flutter apps. +# title: Input & forms +title: 输入与表单 +# description: Content covering handling input and adding forms to Flutter apps. +description: 涵盖在 Flutter 应用中处理输入和添加表单的内容。 +# sitemap: false sitemap: false +ai-translated: true --- diff --git a/sites/docs/src/content/ui/layout/index.md b/sites/docs/src/content/ui/layout/index.md index 9ceae47d54..01f7caa631 100644 --- a/sites/docs/src/content/ui/layout/index.md +++ b/sites/docs/src/content/ui/layout/index.md @@ -89,7 +89,7 @@ you want to add padding, margins, borders, or background color, to name some of its capabilities. 图上大部分应该和你预想的一样,但你可能会疑惑 container(图上粉色显示的)是什么。 -[`Container`][] 是一个 widget,允许你自定义其子 widget。 +[`Container`][] 是一个 widget,让你自定义其子 widget。 举几个例子,如果要添加 padding、margin、边框或背景颜色, 你就可以用上 `Container` 了。 @@ -842,7 +842,7 @@ inside of rows and columns as deeply as you need. Let's look at the code for the outlined section of the following layout: -布局框架允许你根据需要在行和列内嵌套行和列。 +布局框架让你根据需要在行和列内嵌套行和列。 让我们看看以下布局的概述部分的代码: Screenshot of the pavlova app, with the ratings and icon rows outlined in red @@ -1329,12 +1329,12 @@ it automatically scrolls. * `GridView.count` allows you to specify the number of columns - `GridView.count` 允许你制定列的数量 + `GridView.count` 让你制定列的数量 * `GridView.extent` allows you to specify the maximum pixel width of a tile - `GridView.extent` 允许你制定单元格的最大宽度 + `GridView.extent` 让你制定单元格的最大宽度 {% comment %} * Use `MediaQuery.of(context).orientation` to create a grid diff --git a/sites/docs/src/content/ui/layout/lists/index.md b/sites/docs/src/content/ui/layout/lists/index.md index cb2f1f0953..288e68c19c 100644 --- a/sites/docs/src/content/ui/layout/lists/index.md +++ b/sites/docs/src/content/ui/layout/lists/index.md @@ -1,6 +1,11 @@ --- +# layout: toc layout: toc -title: Lists & grids -description: Content covering adding lists and grids of items to Flutter apps. +# title: Lists & grids +title: 列表与网格 +# description: Content covering adding lists and grids of items to Flutter apps. +description: 涵盖向 Flutter 应用添加列表和网格项的内容。 +# sitemap: false sitemap: false +ai-translated: true --- diff --git a/sites/docs/src/content/ui/layout/scrolling/index.md b/sites/docs/src/content/ui/layout/scrolling/index.md index b72c12e54b..3113078d8f 100644 --- a/sites/docs/src/content/ui/layout/scrolling/index.md +++ b/sites/docs/src/content/ui/layout/scrolling/index.md @@ -1,6 +1,9 @@ --- -title: Scrolling -description: Overview of Flutter's scrolling support +# title: Scrolling +title: 滚动 +# description: Overview of Flutter's scrolling support +description: Flutter 滚动支持概览 +ai-translated: true --- Flutter has many built-in widgets that automatically @@ -8,8 +11,14 @@ scroll and also offers a variety of widgets that you can customize to create specific scrolling behavior. +Flutter 有许多内置 widget 可自动滚动, +还提供多种可自定义的 widget, +用于实现特定的滚动行为。 + ## Basic scrolling +## 基础滚动 + Many Flutter widgets support scrolling out of the box and do most of the work for you. For example, [`SingleChildScrollView`][] automatically scrolls its @@ -18,12 +27,19 @@ child when necessary. Other useful widgets include You can check out more of these widgets on the [scrolling page][] of the Widget catalog. +许多 Flutter widget 开箱即用地支持滚动,并为你完成大部分工作。例如, +[`SingleChildScrollView`][] 会在必要时自动滚动其子 widget。 +其他实用 widget 包括 [`ListView`][] 和 [`GridView`][]。 +你可以在 Widget 目录的[滚动页面][scrolling page]查看更多此类 widget。 + ### Infinite scrolling +### 无限滚动 + When you have a long list of items in your `ListView` or `GridView` (including an _infinite_ list), you can build the items on demand @@ -32,20 +48,33 @@ more performant scrolling experience. For more information, check out [`ListView.builder`][] or [`GridView.builder`][]. +当你的 `ListView` 或 `GridView` 中有很长的项列表(包括*无限*列表)时, +可以在项滚动进入可视区域时按需构建。 +这能带来性能更好的滚动体验。 +更多信息请参阅 [`ListView.builder`][] 或 [`GridView.builder`][]。 + [`ListView.builder`]: {{site.api}}/flutter/widgets/ListView/ListView.builder.html [`GridView.builder`]: {{site.api}}/flutter/widgets/GridView/GridView.builder.html ### Specialized scrollable widgets +### 专用可滚动 widget + The following widgets provide more specific scrolling behavior. +以下 widget 提供更具体的滚动行为。 + A video on using [`DraggableScrollableSheet`][]: +关于使用 [`DraggableScrollableSheet`][] 的视频: + Turn the scrollable area into a wheel with [`ListWheelScrollView`][]! +使用 [`ListWheelScrollView`][] 将可滚动区域变成滚轮! + [`DraggableScrollableSheet`]: {{site.api}}/flutter/widgets/DraggableScrollableSheet-class.html @@ -63,34 +92,57 @@ Turn the scrollable area into a wheel with [`ListWheelScrollView`][]! ## Fancy scrolling +## 高级滚动 + Perhaps you want to implement _elastic_ scrolling, also called _scroll bouncing_. Or maybe you want to implement other dynamic scrolling effects, like parallax scrolling. Or perhaps you want a scrolling header with very specific behavior, such as shrinking or disappearing. +也许你希望实现*弹性*滚动,也称为*滚动回弹*。 +或者你想实现其他动态滚动效果,例如视差滚动。 +又或者你需要具有特定行为的滚动页眉,例如收缩或消失。 + You can achieve all this and more using the Flutter `Sliver*` classes. A _sliver_ refers to a piece of the scrollable area. You can define and insert a sliver into a [`CustomScrollView`][] to have finer-grained control over that area. +你可以使用 Flutter 的 `Sliver*` 类实现以上所有效果及更多。 +*sliver* 指可滚动区域中的一块。 +你可以定义 sliver 并将其插入 [`CustomScrollView`][], +以对该区域进行更细粒度的控制。 + For more information, check out [Using slivers to achieve fancy scrolling][] and the [Sliver classes][]. +更多信息请参阅 +[使用 sliver 实现出色的滚动效果][Using slivers to achieve fancy scrolling] +和 [Sliver 类][Sliver classes]。 + [`CustomScrollView`]: {{site.api}}/flutter/widgets/CustomScrollView-class.html [Sliver classes]: /ui/widgets/layout#sliver-widgets [Using slivers to achieve fancy scrolling]: /ui/layout/scrolling/slivers ## Nested scrolling widgets +## 嵌套滚动 widget + How do you nest a scrolling widget inside another scrolling widget without hurting scrolling performance? Do you set the `ShrinkWrap` property to true, or do you use a sliver? +如何将一个滚动 widget 嵌套在另一个滚动 widget 内, +同时不影响滚动性能? +是将 `ShrinkWrap` 属性设为 true,还是使用 sliver? + Check out the "ShrinkWrap vs Slivers" video: +请观看「ShrinkWrap vs Slivers」视频: + diff --git a/sites/docs/src/content/ui/layout/tutorial.md b/sites/docs/src/content/ui/layout/tutorial.md index 9e5e9c54dc..a9f84fa091 100644 --- a/sites/docs/src/content/ui/layout/tutorial.md +++ b/sites/docs/src/content/ui/layout/tutorial.md @@ -7,18 +7,27 @@ shortTitle: 布局教程 description: 学习如何在 Flutter 中构建布局。 tags: 用户界面,Flutter UI,布局 keywords: 布局教程,自动换行 +ai-translated: true --- :::secondary What you'll learn * How to lay out widgets next to each other. * How to add space between widgets. * How adding and nesting widgets results in a Flutter layout. + + 如何将 widget 并排放置。 +* 如何在 widget 之间添加间距。 +* 如何通过添加与嵌套 widget 构成 Flutter 布局。 ::: This tutorial explains how to design and build layouts in Flutter. +本教程说明如何在 Flutter 中设计并构建布局。 + If you use the example code provided, you can build the following app. +若使用提供的示例代码,你可以构建如下应用。 +
@@ -26,40 +35,75 @@ If you use the example code provided, you can build the following app. Photo by [Dino Reichmuth][ch-photo] on [Unsplash][]. Text by [Switzerland Tourism][]. +图片来自 [Unsplash][] 上的 [Dino Reichmuth][ch-photo]。 +文字来自 [Switzerland Tourism][]。 +
To get a better overview of the layout mechanism, start with [Flutter's approach to layout][]. +要更好理解布局机制,请先阅读 [Flutter 的布局方法][Flutter's approach to layout]。 + [Switzerland Tourism]: https://www.myswitzerland.com/en-us/destinations/lake-oeschinen [Flutter's approach to layout]: /ui/layout +Consider how to position the components of your user interface. +A layout consists of the total end result of these positionings. +Consider planning your layout to speed up your coding. +Using visual cues to know where something goes on screen can be a great help. + +考虑如何摆放用户界面各组件的位置。布局由这些摆放的最终结果构成。规划布局有助于加快编码。用视觉线索判断元素在屏幕上的位置会很有帮助。 + +Use whichever method you prefer, like an interface design tool or a pencil +and a sheet of paper. Figure out where you want to place elements on your +screen before writing code. It's the programming version of the adage: +"Measure twice, cut once." + +你可用喜欢的方式,如界面设计工具或纸笔,在写代码前想好元素在屏幕上的位置。这是「量两次,裁一次」这句俗语在编程中的体现。 + ## Diagram the layout +## 绘制布局草图 + In this section, consider what type of user experience you want for your app users. +在本节中,考虑你希望为应用用户提供怎样的体验。 + Consider how to position the components of your user interface. A layout consists of the total end result of these positionings. Consider planning your layout to speed up your coding. Using visual cues to know where something goes on screen can be a great help. +考虑如何摆放用户界面各组件的位置。布局由这些摆放的最终结果构成。规划布局有助于加快编码。用视觉线索判断元素在屏幕上的位置会很有帮助。 + Use whichever method you prefer, like an interface design tool or a pencil and a sheet of paper. Figure out where you want to place elements on your screen before writing code. It's the programming version of the adage: "Measure twice, cut once." +你可用喜欢的方式,如界面设计工具或纸笔,在写代码前想好元素在屏幕上的位置。这是「量两次,裁一次」这句俗语在编程中的体现。 +
  1. Ask these questions to break the layout down to its basic elements. +用以下问题将布局分解为基本元素。 + * Can you identify the rows and columns? * Does the layout include a grid? * Are there overlapping elements? * Does the UI need tabs? * What do you need to align, pad, or border? + 能否识别出行与列? +* 布局是否包含网格? +* 是否有重叠元素? +* UI 是否需要标签页? +* 需要对齐、内边距或边框的是什么? +
  2. @@ -67,6 +111,8 @@ Ask these questions to break the layout down to its basic elements. Identify the larger elements. In this example, you arrange the image, title, buttons, and description into a column. +识别较大的元素。本例中,你将图片、标题、按钮和描述排成一列。 +
  3. @@ -74,6 +120,8 @@ buttons, and description into a column. Diagram each row. +绘制每一行。 +
    1. @@ -83,6 +131,8 @@ a column of text, a star icon, and a number. Its first child, the column, contains two lines of text. That first column might need more space. +第 1 行 **Title** 区域有三个子节点:一列文字、星形图标和一个数字。其第一个子节点(列)包含两行文字,该列可能需要更多空间。 +
    2. @@ -92,6 +142,8 @@ That first column might need more space. Row 2, the **Button** section, has three children: each child contains a column which then contains an icon and text. +第 2 行 **Button** 区域有三个子节点:每个子节点包含一列,列内再有图标和文字。 + @@ -103,9 +155,13 @@ a column which then contains an icon and text. After diagramming the layout, consider how you would code it. +绘制布局草图后,考虑如何编码实现。 + Would you write all the code in one class? Or, would you create one class for each part of the layout? +你会把所有代码写在一个类里,还是为布局的每个部分各创建一个类? + To follow Flutter best practices, create one class, or Widget, to contain each part of your layout. When Flutter needs to re-render part of a UI, @@ -114,12 +170,20 @@ This is why Flutter makes "everything a widget". If only the text changes in a `Text` widget, Flutter redraws only that text. Flutter changes the least amount of the UI possible in response to user input. -For this tutorial, write each element you have identified as its own widget. +遵循 Flutter 最佳实践时,为布局的每个部分创建一个类或 Widget。当 Flutter 需要重新渲染 UI 的某一部分时,只更新变化的最小部分。这就是 Flutter「万物皆 widget」的原因。若 `Text` widget 中只有文字变化,Flutter 只重绘该文字。Flutter 响应用户输入时尽可能少地改变 UI。 + +In this tutorial, write a widget for each element you identify. + +本教程中,将你识别的每个元素写成各自的 widget。 ## Create the app base code +## 创建应用基础代码 + In this section, shell out the basic Flutter app code to start your app. +本节搭建启动应用所需的基础 Flutter 应用代码。 + 1. [Set up your Flutter environment][]. @@ -158,19 +222,33 @@ In this section, shell out the basic Flutter app code to start your app. [Set up your Flutter environment]: /install [new-flutter-app]: /reference/create-new-app +1. [配置 Flutter 开发环境][Set up your Flutter environment]。 + +1. [创建新的 Flutter 应用][new-flutter-app]。 + +1. 将 `lib/main.dart` 的内容替换为以下代码。此应用使用参数设置应用标题以及在 `appBar` 中显示的标题,以简化代码。 + ## Add the Title section +## 添加标题区域 + In this section, create a `TitleSection` widget that resembles the following layout. +本节创建一个与下列布局相似的 `TitleSection` widget。 + ### Add the `TitleSection` Widget +### 添加 `TitleSection` Widget + Add the following code after the `MyApp` class. +在 `MyApp` 类之后添加以下代码。 + ```dart class TitleSection extends StatelessWidget { @@ -223,13 +301,21 @@ class TitleSection extends StatelessWidget { The entire row falls inside a `Padding` widget and pads each edge by 32 pixels. +1. 要在行中使用所有剩余空闲空间,用 `Expanded` widget 拉伸 `Column` widget。要将列放在行首,将 `crossAxisAlignment` 设为 `CrossAxisAlignment.start`。 +2. 要在文本行之间添加间距,将这些行放在 `Padding` widget 中。 +3. 标题行以红色星形图标和文本 `41` 结束。整行位于 `Padding` widget 内,四边各留 32 像素内边距。 + ### Change the app body to a scrolling view +### 将应用 body 改为可滚动视图 + In the `body` property, replace the `Center` widget with a `SingleChildScrollView` widget. Within the [`SingleChildScrollView`][] widget, replace the `Text` widget with a `Column` widget. +在 `body` 属性中,将 `Center` widget 替换为 `SingleChildScrollView` widget。在 [`SingleChildScrollView`][] widget 内,将 `Text` widget 替换为 `Column` widget。 + ```dart diff - body: const Center( - child: Text('Hello World'), @@ -240,6 +326,8 @@ Within the [`SingleChildScrollView`][] widget, replace the `Text` widget with a These code updates change the app in the following ways. +这些代码更新会以如下方式改变应用。 + * A `SingleChildScrollView` widget can scroll. This allows elements that don't fit on the current screen to display. * A `Column` widget displays any elements within its `children` property @@ -248,14 +336,21 @@ These code updates change the app in the following ways. the top of the list. Elements in the `children` list display in array order on the screen from top to bottom. + `SingleChildScrollView` widget 可以滚动,使放不进当前屏幕的元素得以显示。 +* `Column` widget 按 `children` 属性中列出的顺序显示其中的元素。`children` 列表中的第一项显示在顶部,列表中的元素按数组顺序自上而下显示在屏幕上。 + [`SingleChildScrollView`]: {{site.api}}/flutter/widgets/SingleChildScrollView-class.html ### Update the app to display the title section +### 更新应用以显示标题区域 + Add the `TitleSection` widget as the first element in the `children` list. This places it at the top of the screen. Pass the provided name and location to the `TitleSection` constructor. +将 `TitleSection` widget 作为 `children` 列表的第一项添加,使其显示在屏幕顶部。将提供的名称和位置传给 `TitleSection` 构造函数。 + ```dart diff + children: [ + TitleSection( @@ -276,25 +371,43 @@ Pass the provided name and location to the `TitleSection` constructor. [hot reload]: /tools/hot-reload [`lib/main.dart`]: {{site.repo.this}}/blob/main/examples/layout/lakes/step2/lib/main.dart +:::tip +* 将代码粘贴到应用时,缩进可能错乱。在 Flutter 编辑器中使用[自动重新格式化支持][automatic reformatting support]可修复。 +* 要加快开发,可尝试 Flutter 的[热重载][hot reload]功能。 +* 若遇到问题,请将代码与 [`lib/main.dart`][] 对比。 +::: + ## Add the Button section +## 添加按钮区域 + In this section, add the buttons that will add functionality to your app. +本节添加为应用增加功能的按钮。 + The **Button** section contains three columns that use the same layout: an icon over a row of text. +**Button** 区域包含三列,使用相同布局:图标在上、文字在下。 + Plan to distribute these columns in one row so each takes the same amount of space. Paint all text and icons with the primary color. +计划将这些列放在一行中,使每列占用相同空间。将所有文字和图标绘制为主题主色。 + ### Add the `ButtonSection` widget +### 添加 `ButtonSection` widget + Add the following code after the `TitleSection` widget to contain the code to build the row of buttons. +在 `TitleSection` widget 之后添加以下代码,用于构建按钮行。 + ```dart class ButtonSection extends StatelessWidget { @@ -311,6 +424,8 @@ class ButtonSection extends StatelessWidget { ### Create a widget to make buttons +### 创建用于制作按钮的 widget + As the code for each column could use the same syntax, create a widget named `ButtonWithText`. The widget's constructor accepts a color, icon data, and a label for the button. @@ -319,8 +434,12 @@ Using these values, the widget builds a `Column` with an `Icon` and a stylized To help separate these children, a `Padding` widget the `Text` widget is wrapped with a `Padding` widget. +由于每列代码可使用相同写法,创建一个名为 `ButtonWithText` 的 widget。其构造函数接受颜色、图标数据和按钮标签。widget 用这些值构建包含 `Icon` 和样式化 `Text` widget 的 `Column`。为分隔子节点,用 `Padding` widget 包裹 `Text` widget。 + Add the following code after the `ButtonSection` class. +在 `ButtonSection` 类之后添加以下代码。 + ```dart class ButtonSection extends StatelessWidget { @@ -366,8 +485,12 @@ class ButtonWithText extends StatelessWidget { ### Position the buttons with a `Row` widget +### 用 `Row` widget 摆放按钮 + Add the following code into the `ButtonSection` widget. +将以下代码添加到 `ButtonSection` widget 中。 + 1. Add three instances of the `ButtonWithText` widget, once for each button. 1. Pass the color, `Icon`, and text for that specific button. 1. Align the columns along the main axis with the @@ -377,6 +500,10 @@ Add the following code into the `ButtonSection` widget. This value, then, tells Flutter to arrange the free space in equal amounts before, between, and after each column along the `Row`. +1. 为每个按钮各添加一个 `ButtonWithText` widget 实例。 +1. 传入该按钮对应的颜色、`Icon` 和文字。 +1. 用 `MainAxisAlignment.spaceEvenly` 沿主轴对齐各列。`Row` widget 的主轴是水平的,`Column` widget 的主轴是垂直的。该值告诉 Flutter 在 `Row` 上于各列之前、之间和之后均分空闲空间。 + ```dart class ButtonSection extends StatelessWidget { @@ -422,8 +549,12 @@ class ButtonWithText extends StatelessWidget { ### Update the app to display the button section +### 更新应用以显示按钮区域 + Add the button section to the `children` list. +将按钮区域添加到 `children` 列表。 + ```dart diff @@ -437,16 +568,24 @@ Add the button section to the `children` list. ## Add the Text section +## 添加文本区域 + In this section, add the text description to this app. +本节为应用添加文字描述。 + ### Add the `TextSection` widget +### 添加 `TextSection` widget + Add the following code as a separate widget after the `ButtonSection` widget. +在 `ButtonSection` widget 之后作为独立 widget 添加以下代码。 + ```dart class TextSection extends StatelessWidget { @@ -467,14 +606,20 @@ class TextSection extends StatelessWidget { By setting [`softWrap`][] to `true`, text lines fill the column width before wrapping at a word boundary. +将 [`softWrap`][] 设为 `true` 时,文字行会先填满列宽,再在词边界处换行。 + [`softWrap`]: {{site.api}}/flutter/widgets/Text/softWrap.html ### Update the app to display the text section +### 更新应用以显示文本区域 + Add a new `TextSection` widget as a child after the `ButtonSection`. When adding the `TextSection` widget, set its `description` property to the text of the location description. +在 `ButtonSection` 之后添加新的 `TextSection` widget 作为子节点。添加 `TextSection` widget 时,将其 `description` 属性设为地点描述文字。 + ```dart diff location: 'Kandersteg, Switzerland', ), @@ -494,27 +639,45 @@ the text of the location description. ## Add the Image section +## 添加图片区域 + In this section, add the image file to complete your layout. +本节添加图片文件以完成布局。 + ### Configure your app to use supplied images +### 配置应用以使用提供的图片 + To configure your app to reference images, modify its `pubspec.yaml` file. +要配置应用引用图片,请修改其 `pubspec.yaml` 文件。 + 1. Create an `images` directory at the top of the project. +1. 在项目顶层创建 `images` 目录。 + 1. Download the [`lake.jpg`][] image and add it to the new `images` directory. +1. 下载 [`lake.jpg`][] 图片并添加到新的 `images` 目录。 + :::note You can't use `wget` to save this binary file. You can download the [image][ch-photo] from [Unsplash][] under the Unsplash License. The small size comes in at 94.4 kB. ::: + :::note + 你不能用 `wget` 保存此二进制文件。可在 [Unsplash][] 上按 Unsplash 许可下载[图片][ch-photo]。小尺寸约为 94.4 kB。 + ::: + 1. To include images, add an `assets` tag to the `pubspec.yaml` file at the root directory of your app. When you add `assets`, it serves as the set of pointers to the images available to your code. +1. 要包含图片,在应用根目录的 `pubspec.yaml` 文件中添加 `assets` 标签。添加 `assets` 后,它作为代码可用图片的指针集合。 + ```yaml title="pubspec.yaml" diff flutter: uses-material-design: true @@ -528,14 +691,20 @@ Write the changes to the file as given in the previous example. This change might require you to restart the running program to display the image. + +`pubspec.yaml` 中的文字区分空白和大小写。请按上一示例所示修改文件。此更改可能需要重启正在运行的程序才能显示图片。 ::: [`lake.jpg`]: https://raw.githubusercontent.com/flutter/website/main/examples/layout/lakes/step5/images/lake.jpg ### Create the `ImageSection` widget +### 创建 `ImageSection` widget + Define the following `ImageSection` widget after the other declarations. +在其他声明之后定义以下 `ImageSection` widget。 + ```dart class ImageSection extends StatelessWidget { @@ -554,12 +723,18 @@ The `BoxFit.cover` value tells Flutter to display the image with two constraints. First, display the image as small as possible. Second, cover all the space that the layout allotted, called the render box. +`BoxFit.cover` 值告诉 Flutter 在两项约束下显示图片:首先尽可能小地显示图片;其次覆盖布局分配的全部空间,即 render box。 + ### Update the app to display the image section +### 更新应用以显示图片区域 + Add an `ImageSection` widget as the first child in the `children` list. Set the `image` property to the path of the image you added in [Configure your app to use supplied images](#configure-your-app-to-use-supplied-images). +将 `ImageSection` widget 作为 `children` 列表的第一项添加。将 `image` 属性设为你于[配置应用以使用提供的图片](#configure-your-app-to-use-supplied-images)中添加的图片路径。 + ```dart diff children: [ + ImageSection( @@ -572,14 +747,22 @@ Set the `image` property to the path of the image you added in ## Congratulations +## 恭喜 + That's it! When you hot reload the app, your app should look like this. +就是这样!热重载应用后,应用应如下所示。 + ## Resources +## 资源 + You can access the resources used in this tutorial from these locations: +可从以下位置访问本教程使用的资源: + **Dart code:** [`main.dart`][]
      **Image:** [ch-photo][]
      **Pubspec:** [`pubspec.yaml`][]
      @@ -590,8 +773,12 @@ You can access the resources used in this tutorial from these locations: ## Next Steps +## 下一步 + To add interactivity to this layout, follow the [interactivity tutorial][]. +要为该布局添加交互性,请参阅[交互性教程][interactivity tutorial]。 + [interactivity tutorial]: /ui/interactivity [Unsplash]: https://unsplash.com diff --git a/sites/docs/src/content/ui/navigation/deep-linking.md b/sites/docs/src/content/ui/navigation/deep-linking.md index 3979b083d4..a164270e88 100644 --- a/sites/docs/src/content/ui/navigation/deep-linking.md +++ b/sites/docs/src/content/ui/navigation/deep-linking.md @@ -1,6 +1,9 @@ --- -title: Deep linking -description: Navigate to routes when the app receives a new URL. +# title: Deep linking +title: 深度链接 +# description: Navigate to routes when the app receives a new URL. +description: 当应用收到新 URL 时导航到对应路由。 +ai-translated: true --- Deep links are links that not only open an app, but also take the @@ -8,6 +11,10 @@ user to a specific location "deep" inside the app. For example, a deep link from an advertisement for a pair of sneakers might open a shopping app and display the product page for those particular shoes. +深度链接不仅能打开应用,还能将用户带到应用内「深层」的特定位置。例如, +一双运动鞋广告中的深度链接可能会打开购物应用, +并显示那双鞋的商品页面。 + Flutter supports deep linking on iOS, Android, and the web. Opening a URL displays that screen in your app. With the following steps, @@ -16,10 +23,20 @@ you can launch and display routes by using named routes [`onGenerateRoute`][onGenerateRoute]), or by using the [`Router`][Router] widget. +Flutter 在 iOS、Android 和 Web 上均支持深度链接。 +打开 URL 会在你的应用中显示对应界面。 +按照以下步骤, +你可以使用命名路由(通过 [`routes`][routes] 参数或 +[`onGenerateRoute`][onGenerateRoute]),或使用 [`Router`][Router] widget +来启动并显示路由。 + :::note Named routes are no longer recommended for most applications. For more information, see [Limitations][] in the [navigation overview][] page. + +对于大多数应用,不再推荐使用命名路由。 +更多信息请参阅[导航概览][navigation overview]页面中的[局限性][Limitations]。 ::: [Limitations]: /ui/navigation#limitations @@ -31,14 +48,25 @@ link. By default, web apps read the deep link path from the url fragment using the pattern: `/#/path/to/app/screen`, but this can be changed by [configuring the URL strategy][] for your app. +若在 Web 浏览器中运行应用,无需额外配置。 +路由路径的处理方式与 iOS 或 Android 深度链接相同。 +默认情况下,Web 应用使用 `/#/path/to/app/screen` 模式从 URL 片段读取深度链接路径, +但你可以通过[配置应用的 URL 策略][configuring the URL strategy]来更改这一点。 + If you are a visual learner, check out the following video: +若你偏好视觉学习,请观看以下视频: + ## Get started +## 入门 + To get started, see our cookbooks for Android and iOS: +入门请参阅我们为 Android 和 iOS 准备的 Cookbook 教程: +
      @@ -54,6 +82,8 @@ To get started, see our cookbooks for Android and iOS: ## Migrating from plugin-based deep linking +## 从基于 plugin 的深度链接迁移 + If you have written a plugin to handle deep links, as described in [Deep Links and Flutter applications][plugin-linking] (a free article on Medium), @@ -61,11 +91,21 @@ you should opt out the Flutter's default deep link handler. To do this, set `FlutterDeepLinkingEnabled` to false in `Info.plist` _or_ `flutter_deeplinking_enabled` to false in `AndroidManifest.xml`. +若你已按 Medium 上的免费文章 +[Deep Links and Flutter applications][plugin-linking] 所述编写了处理深度链接的 plugin, +应退出 Flutter 的默认深度链接处理器。 +为此,在 `Info.plist` 中将 `FlutterDeepLinkingEnabled` 设为 false,**或** +在 `AndroidManifest.xml` 中将 `flutter_deeplinking_enabled` 设为 false。 + ## Behavior +## 行为 + The behavior varies slightly based on the platform and whether the app is launched and running. +行为会因平台以及应用是否已启动并运行而略有不同。 + | Platform / Scenario | Using Navigator | Using Router | |--------------------------|---------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | iOS (not launched) | App gets initialRoute ("/") and a short time after gets a pushRoute | App gets initialRoute ("/") and a short time after uses the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Page. | @@ -73,6 +113,13 @@ launched and running. | iOS (launched) | pushRoute is called | Path is parsed, and the Navigator is configured with a new set of Pages. | | Android (launched) | pushRoute is called | Path is parsed, and the Navigator is configured with a new set of Pages. | +| 平台 / 场景 | 使用 Navigator | 使用 Router | +|--------------------------|---------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| iOS(未启动) | 应用获得 initialRoute("/"),稍后收到 pushRoute | 应用获得 initialRoute("/"),稍后使用 RouteInformationParser 解析路由并调用 RouterDelegate.setNewRoutePath,从而用对应的 Page 配置 Navigator。 | +| Android(未启动) | 应用获得包含路由("/deeplink")的 initialRoute | 应用获得 initialRoute("/deeplink")并传给 RouteInformationParser 解析路由,调用 RouterDelegate.setNewRoutePath,从而用对应的 Pages 配置 Navigator。 | +| iOS(已启动) | 调用 pushRoute | 解析路径,并用新的 Pages 集合配置 Navigator。 | +| Android(已启动) | 调用 pushRoute | 解析路径,并用新的 Pages 集合配置 Navigator。 | + {:.table .table-striped} When using the [`Router`][Router] widget, @@ -80,14 +127,25 @@ your app has the ability to replace the current set of pages when a new deep link is opened while the app is running. +使用 [`Router`][Router] widget 时, +当应用运行期间打开新的深度链接, +你的应用可以替换当前的页面集合。 + ## To learn more +## 延伸阅读 + * [Learning Flutter's new navigation and routing system][] provides an introduction to the Router system. * [Deep dive into Flutter deep linking][io-dl] video from Google I/O 2023 * [Flutter Deep Linking: The Ultimate Guide][], a step-by-step tutorial showing how to implement deep links in Flutter. + [Learning Flutter's new navigation and routing system][] 介绍了 Router 系统。 +* Google I/O 2023 的 [Deep dive into Flutter deep linking][io-dl] 视频 +* [Flutter Deep Linking: The Ultimate Guide][], + 分步教程,展示如何在 Flutter 中实现深度链接。 + [io-dl]: {{site.yt.watch}}?v=6RxuDcs6jVw&t=3s [Learning Flutter's new navigation and routing system]: {{site.flutter-blog}}/learning-flutters-new-navigation-and-routing-system-7c9068155ade [routes]: {{site.api}}/flutter/material/MaterialApp/routes.html diff --git a/sites/docs/src/content/ui/navigation/index.md b/sites/docs/src/content/ui/navigation/index.md index 016343bb95..81d24666b4 100644 --- a/sites/docs/src/content/ui/navigation/index.md +++ b/sites/docs/src/content/ui/navigation/index.md @@ -5,6 +5,7 @@ title: 路由和导航 description: Flutter路由和导航的概览。 tags: 用户界面,Flutter UI,布局 keywords: 路由和导航 +ai-translated: true --- Flutter provides a complete system for navigating between screens and handling @@ -14,16 +15,24 @@ requirements should also use the [`Router`][] to correctly handle deep links on Android and iOS, and to stay in sync with the address bar when the app is running on the web. +Flutter 提供了一套完整的系统在界面之间导航并处理深层链接。没有复杂深层链接的小型应用可以使用 [`Navigator`][],而有特定深层链接与导航需求的应用还应使用 [`Router`][],以便在 Android 和 iOS 上正确处理深层链接,并在 Web 上运行时与地址栏保持同步。 + To configure your Android or iOS application to handle deep links, see [Deep linking][]. +要为 Android 或 iOS 应用配置深层链接处理,请参阅[深层链接][Deep linking]。 + ## Using the Navigator +## 使用 Navigator + The `Navigator` widget displays screens as a stack using the correct transition animations for the target platform. To navigate to a new screen, access the `Navigator` through the route's `BuildContext` and call imperative methods such as `push()` `or pop()`: +`Navigator` widget 以栈的形式显示界面,并使用目标平台对应的过渡动画。要导航到新界面,通过路由的 `BuildContext` 访问 `Navigator`,并调用 `push()` 或 `pop()` 等命令式方法: + ```dart child: const Text('Open second screen'), @@ -43,19 +52,30 @@ Material Design. For more examples of how to use the `Navigator`, follow the [navigation recipes][] from the Flutter Cookbook or visit the [Navigator API documentation][`Navigator`]. +由于 `Navigator` 维护 `Route` 对象栈(表示历史栈),`push()` 方法也需要传入 `Route` 对象。`MaterialPageRoute` 是 `Route` 的子类,用于指定 Material Design 的过渡动画。更多 `Navigator` 用法示例,请参阅 Flutter Cookbook 中的[导航示例][navigation recipes],或访问 [Navigator API 文档][`Navigator`]。 + ## Using named routes +## 使用命名路由 + :::note We don't recommend using named routes for most applications. Instead, use [go_router][] (or another routing package) or use `Navigator` with [`MaterialPageRoute`][]. For more information, see the [Limitations](#limitations) section. + +我们不建议大多数应用使用命名路由。 +请改用 [go_router][](或其他路由 package), +或将 `Navigator` 与 [`MaterialPageRoute`][] 配合使用。 +更多信息请参阅[局限性](#limitations)一节。 ::: Applications with simple navigation and deep linking requirements can use the `Navigator` for navigation and the [`MaterialApp.routes`][] parameter for deep links: +导航与深层链接需求较简单的应用,可用 `Navigator` 进行导航,并用 [`MaterialApp.routes`][] 参数处理深层链接: + ```dart child: const Text('Open second screen'), @@ -68,31 +88,44 @@ onPressed: () { `MaterialApp.routes` list. For a complete example, follow the [Navigate with named routes][] recipe from the Flutter Cookbook. +`/second` 表示在 `MaterialApp.routes` 列表中声明的*命名路由*。完整示例请参阅 Flutter Cookbook 中的[使用命名路由导航][Navigate with named routes]。 ### Limitations {: #limitations} +### 局限性 {: #limitations} + Although named routes can handle deep links, the behavior is always the same and can't be customized. When a new deep link is received by the platform, Flutter pushes a new `Route` onto the Navigator regardless of where the user currently is. +尽管命名路由可以处理深层链接,但行为始终相同且无法自定义。当平台收到新的深层链接时,无论用户当前在何处,Flutter 都会在 `Navigator` 上压入新的 `Route`。 + Flutter also doesn't support the browser forward button for applications using named routes. For these reasons, we don't recommend using named routes in most applications. Instead, use a routing package like [go_router][] or use `Navigator` with [`MaterialPageRoute`][]. +使用命名路由的应用也不支持浏览器的前进按钮。因此,我们不建议大多数应用使用命名路由。请改用 [go_router][] 等路由 package,或将 `Navigator` 与 [`MaterialPageRoute`][] 配合使用。 + ## Using the Router +## 使用 Router + Flutter applications with advanced navigation and routing requirements (such as a web app that uses direct links to each screen, or an app with multiple `Navigator` widgets) should use a routing package such as [go_router][] that can parse the route path and configure the `Navigator` whenever the app receives a new deep link. +具有高级导航与路由需求(例如 Web 应用为每个界面提供直接链接,或应用包含多个 `Navigator` widget)的 Flutter 应用,应使用 [go_router][] 等路由 package,以便解析路由路径,并在收到新深层链接时配置 `Navigator`。 + To use the Router, switch to the `router` constructor on `MaterialApp` or `CupertinoApp` and provide it with a `Router` configuration. Routing packages, such as [go_router][], typically provide route configuration and routes can be used as follows: +要使用 Router,请在 `MaterialApp` 或 `CupertinoApp` 上改用 `router` 构造函数,并提供 `Router` 配置。[go_router][] 等路由 package 通常提供路由配置,可按如下方式使用路由: + ```dart child: const Text('Open second screen'), @@ -102,6 +135,8 @@ onPressed: () => context.go('/second'), Because packages like go_router are _declarative_, they will always display the same screen(s) when a deep link is received. +由于 go_router 等 package 是*声明式*的,收到深层链接时始终会显示相同的界面。 + :::note Note for advanced developers If you prefer not to use a routing package and would like full control over navigation and routing in your app, override @@ -109,15 +144,21 @@ and would like full control over navigation and routing in your app, override changes, you can precisely control the stack of screens by providing a list of `Page` objects using the `Navigator.pages` parameter. For more details, see the `Router` API documentation. + +若不想使用路由 package,而希望对应用中的导航与路由完全控制,可重写 `RouteInformationParser` 和 `RouterDelegate`。当应用状态变化时,可通过 `Navigator.pages` 参数提供 `Page` 对象列表,精确控制界面栈。更多细节请参阅 `Router` API 文档。 ::: ## Using Router and Navigator together +## 同时使用 Router 与 Navigator + The `Router` and `Navigator` are designed to work together. You can navigate using the `Router` API through a declarative routing package, such as `go_router`, or by calling imperative methods such as `push()` and `pop()` on the `Navigator`. +`Router` 与 `Navigator` 设计为协同工作。你可以通过声明式路由 package(如 `go_router`)使用 `Router` API 导航,也可以在 `Navigator` 上调用 `push()`、`pop()` 等命令式方法。 + When you navigate using the `Router` or a declarative routing package, each route on the Navigator is _page-backed_, meaning it was created from a [`Page`][] using the [`pages`][] @@ -127,18 +168,27 @@ route to the Navigator. If you are using a routing package, Routes that are _page-backed_ are always deep-linkable, whereas _pageless_ routes are not. +使用 `Router` 或声明式路由 package 导航时,`Navigator` 上的每条路由都是*由 Page 支持*(page-backed)的,即通过 `Navigator` 构造函数的 [`pages`][] 参数由 [`Page`][] 创建。反之,通过 `Navigator.push` 或 `showDialog` 创建的 `Route` 会向 `Navigator` 添加*无 Page*(pageless)路由。若使用路由 package,*由 Page 支持*的路由始终可深层链接,而*无 Page* 的路由则不行。 + When a _page-backed_ `Route` is removed from the `Navigator`, all of the _pageless_ routes after it are also removed. For example, if a deep link navigates by removing a _page-backed_ route from the Navigator, all _pageless_ routes after (up until the next _page-backed_ route) are removed too. +当从 `Navigator` 移除*由 Page 支持*的 `Route` 时,其后的所有*无 Page* 路由也会一并移除。例如,若深层链接通过从 `Navigator` 移除*由 Page 支持*的路由进行导航,其后(直到下一条*由 Page 支持*的路由之前)的所有*无 Page* 路由也会被移除。 + :::note You can't prevent navigation from page-backed screens using `WillPopScope`. Instead, you should consult your routing package's API documentation. + +你无法使用 `WillPopScope` 阻止由 Page 支持的界面的导航。 +请查阅你所用路由 package 的 API 文档。 ::: ## Web support +## Web 支持 + Apps using the `Router` class integrate with the browser History API to provide a consistent experience when using the browser's back and forward buttons. Whenever you navigate using the `Router`, a History API entry is added to the @@ -148,23 +198,34 @@ visited location that was shown using the `Router`. This means that if the user pops a page from the `Navigator` and then presses the browser **back** button the previous page is pushed back onto the stack. +使用 `Router` 类的应用会与浏览器 History API 集成,在使用浏览器后退与前进按钮时提供一致的体验。每当你通过 `Router` 导航,都会在浏览器历史栈中添加一条 History API 记录。按下**后退**按钮会使用*[逆时间顺序导航][reverse chronological navigation]*,即用户会回到先前通过 `Router` 显示的访问位置。这意味着若用户从 `Navigator` 弹出页面后再按浏览器**后退**按钮,先前的页面会重新压入栈中。 + ## More information +## 更多信息 + For more information on navigation and routing, check out the following resources: +有关导航与路由的更多信息,请参阅以下资源: + * The Flutter cookbook includes multiple [navigation recipes][] that show how to use the `Navigator`. + Flutter Cookbook 包含多个[导航示例][navigation recipes],展示如何使用 `Navigator`。 * The [`Navigator`][] and [`Router`][] API documentation contain details on how to set up declarative navigation without a routing package. + [`Navigator`][] 与 [`Router`][] API 文档说明如何在不使用路由 package 的情况下设置声明式导航。 * [Understanding navigation][], a page from the Material Design documentation, outlines concepts for designing the navigation in your app, including explanations for forward, upward, and chronological navigation. + [理解导航][Understanding navigation](Material Design 文档)概述应用导航的设计概念,包括前向、向上与时间顺序导航的说明。 * [Learning Flutter's new navigation and routing system][], an article on Medium, describes how to use the `Router` widget directly, without a routing package. + [学习 Flutter 的新导航与路由系统][Learning Flutter's new navigation and routing system](Medium 文章)介绍如何在不使用路由 package 的情况下直接使用 `Router` widget。 * The [Router design document][] contains the motivation and design of the `Router` API. + [Router 设计文档][Router design document]包含 `Router` API 的动机与设计。 [`Navigator`]: {{site.api}}/flutter/widgets/Navigator-class.html [`Router`]: {{site.api}}/flutter/widgets/Router-class.html diff --git a/sites/docs/src/content/ui/widgets/index.md b/sites/docs/src/content/ui/widgets/index.md index c7cf3a1f5c..a4e5c698df 100644 --- a/sites/docs/src/content/ui/widgets/index.md +++ b/sites/docs/src/content/ui/widgets/index.md @@ -6,6 +6,7 @@ description: 一组丰富的 Flutter widget 目录。 # shortTitle: Widgets shortTitle: 核心 Widget showToc: false +ai-translated: true --- Create beautiful apps faster with Flutter's collection of visual, structural, @@ -29,10 +30,12 @@ Flutter SDK 中附带了两套设计系统。 Beautiful and high-fidelity widgets that align with Apple's Human Interface Guidelines for iOS and macOS. + 精美且高保真的 widget,符合 Apple 针对 iOS 和 macOS 的人机界面指南。 Visual, behavioral, and motion-rich widgets implementing the Material 3 design specification. + 实现 Material 3 设计规范的视觉、行为与动效丰富的 widget。
      @@ -42,6 +45,9 @@ For example, the Windows-inspired [fluent_ui][], macOS-inspired [macos_ui][], and the Ubuntu-inspired [yaru][] widgets. +你还可以在 Dart 和 Flutter 的 package 资源库 [pub.dev]({{site.pub}}) 上找到更多由 Flutter 社区创建的设计系统。 +例如,受 Windows 启发的 [fluent_ui][]、受 macOS 启发的 [macos_ui][],以及受 Ubuntu 启发的 [yaru][] widget。 + [fluent_ui]: {{site.pub-pkg}}/fluent_ui [macos_ui]: {{site.pub-pkg}}/macos_ui [yaru]: {{site.pub-pkg}}/yaru diff --git a/tool/translator/AGENT_TRANSLATION_GUIDE.md b/tool/translator/AGENT_TRANSLATION_GUIDE.md new file mode 100644 index 0000000000..41f2feaf3e --- /dev/null +++ b/tool/translator/AGENT_TRANSLATION_GUIDE.md @@ -0,0 +1,486 @@ +# Flutter 中文文档翻译 Agent 指南 + +> 本文档是给 AI 翻译 Agent 的 **最高优先级指令**。 +> 不要自主发挥,所有翻译行为必须严格遵守本文档规则。 + +--- + +## 核心理念 + +你是一名精通 Flutter & Dart 英文翻译中文的顶尖专家。 +本仓库采用 **中英文对照** 模式管理翻译:英文原文永远保留在源文件中,中文译文作为追加层嵌入。 +翻译完成后,构建工具链(`nt` tool)会自动折叠英文,只对外展示中文。 + +**这意味着:** +- 英文原文是"锚"——上游文档更新时,维护者通过 diff 英文原文定位变更位置 +- 你 **绝对不能** 修改、删除、移动任何英文原文内容 +- 中文译文的位置和格式直接决定 `nt` 折叠是否正确 + +--- + +## 第一原则:翻译质量 + +| 准则 | 说明 | +| ---------- | -------------------------------------------------------------------------------------- | +| 信达雅 | 准确理解原意,用地道中文表达,拒绝逐字机翻 | +| 人称 | **一律用「你」**,绝对禁止「您」 | +| 排版 | 中英文之间、中文与数字之间保留一个半角空格 | +| 术语一致 | 参考已翻译页面的用词,保持全站统一 | +| 禁止翻译腔 | 例如:"allows you to" → **不要** 翻译成"允许你",用 “让你”、“借助 X 你可以” 等自然表达 | +| 代码不译 | 代码块、API 名、类名、变量名保持英文原样 | + +--- + +## 第二原则:格式规范(按元素类型) + +### 1. 标题与段落 + +英文原文下方空一行,追加中文: + +```markdown +## English Heading + +## 中文标题 + +English paragraph here. + +中文段落翻译放在这里。 +``` + +**特殊情况——标题内容相同时必须区分:** +如果英文标题是纯平台名/API 名(iOS、macOS、`HtmlElementView`),中文标题必须加后缀区分,否则页面出现两个相同锚点: + +```markdown +## iOS + +## iOS 平台 + +## `HtmlElementView` + +## `HtmlElementView` 组件 +``` + +### 2. 列表项(`-`、`*`、`1.`) + +**⚠️ 这是最常见的错误!** 中文 **不能** 作为独立列表项,必须是英文项的 **缩进延续段**: + +```markdown +- English list item one + + 中文列表项一 + +- English list item two + + 中文列表项二 + +1. English list item two + + 中文列表项二 +``` + +**错误示范(绝对禁止):** + +```markdown +- English list item one +- 中文列表项一 + +1. English list item one +1. 中文列表项一 +``` + +多行列表项同理——缩进对齐: + +```markdown +* **Predictable performance**: + Impeller compiles all shaders and reflection offline at build time. + It builds all pipeline state objects upfront. + + **可预测的性能**: + Impeller 在构建时离线编译所有着色器与反射,预先构建所有管线状态对象。 +``` + +### 3. 提示框(`:::note`、`:::tip`、`:::warning`) + +中英文必须在**同一个 `:::` 块内**,绝对不能拆成两个独立块: + +```markdown +:::tip +To integrate Flutter code into an **existing** iOS app, +check out [Add Flutter to existing app][]. + +若要将 Flutter 代码集成到**现有** iOS 应用,请参阅 [Add Flutter to existing app][]。 +::: +``` + +**错误示范(绝对禁止):** + +```markdown +:::tip +To integrate Flutter code into an **existing** iOS app, +check out [Add Flutter to existing app][]. +::: + +:::tip +若要将 Flutter 代码集成到**现有** iOS 应用,请参阅 [Add Flutter to existing app][]。 +::: +``` + +如果提示框有标题: + +```markdown +:::secondary Why large screens matter +Large screen demand continues to grow. + +:::secondary 为何大屏尤为重要 +大屏需求持续增长。 +::: +``` + +### 4. 表格 + +表头用 `` 标签包裹,表体在英文下方直接追加中文行: + +```markdown +| Property属性 | Description说明 | +|---|---| +| Row 1 text | Valid | +| 行 1 文本 | 有效 | +``` + +### 5. 链接引用 + +**绝对不要修改链接定义的路径。** 翻译时只翻译显示文本: + +```markdown +[Learn more about widgets][] + +[了解更多关于 widget 的内容][Learn more about widgets] +``` + +链接定义部分(`[id]: /path/to/page`)保持原样,不翻译、不修改路径。 + +### 6. 脚注 + +脚注定义(`[^N]:`)**不要重复**。英文和中文合并为一条: + +```markdown +[^1]: The original English explanation. + 中文解释翻译。 +``` + +在正文中,如果是中英对照的行(如表格行),脚注标记 `[^N]` 只在中文行保留,英文行注释掉: + +```markdown +| Access sensor data | Yes | +| 访问传感器数据[^4] | 是 | +``` + +### 7. YAML Frontmatter + +- 标题字段直接在下方追加中文行,并注释原文行: + +```yaml +# title: Understanding constraints +title: 理解约束 +# description: A description of how constraints work. +description: 约束工作原理说明。 +``` + +- 对于 AI 翻译的文件,添加标记: + +```yaml +ai-translated: true +``` + +### 8. 组件内嵌 YAML(``、`` 等) + +这些组件内的 YAML 是结构化数据,**只写中文,不做中英对照**,否则 YAML 解析会报错: + +```html + +``` + +### 9. 代码块和 code-excerpt + +` +```dart +Widget build(BuildContext context) { + return const Center(child: Text('Hello')); +} +``` +``` + +### 10. HTML 块元素 + +`
    3. `、`
      ` 等内部的翻译规则同 Markdown——中文追加在英文下方: + +```html +
    4. +English description here. + +中文说明在这里。 +
    5. +``` + +### 11. Markdown 隐式链接引用 + +如果在原文中使用了 markdown 隐式链接名("[链接名][]",第一组方括号链接名本身作为链接引用,第二组方括号为空) 的情况, +你需要先复制第一组方括号内的 "链接名" 放在第二组方括号内,再翻译第一组方括号内的 "链接名",组成 "[译文链接名][原始链接名]": + +```markdown +The following resources might help when writing layout code, [Layout tutorial][], [Widget catalog][]. + +当写布局代码时,这些资源可能会帮助到你:[Layout 教程][Layout tutorial]、[核心 Widget 目录][Widget catalog]。 +``` + +```markdown +[Layout tutorial][] + +[Layout 教程][Layout tutorial] + +[Widget catalog][] + +[核心 Widget 目录][Widget catalog] +``` + +### 12. Markdown 斜体转换为加粗 + +将原文中 Markdown 斜体(使用单个星号 `*` 或 下划线 `_` 包围的文本)转换为加粗(使用双星号 `**` 包围的文本) +被包围的文本左右需要与其他文本间隔一个空格: + +``` +When you want to _deploy_ your app to the App Store, +you'll need to upgrade your personal Apple Developer account to +a professional account. + +当你想要将你的应用 **部署** 到 App Store 时, +你需要将你的个人 Apple Developer 帐户升级到专业帐户。 +``` + +--- + +## 第三原则:翻译的中文排版规则 +以下会提供给你正确以及错误的示例: +### 空格 +#### 中英文之间应增加空格 +[正确]: +- 在 LeanCloud 上,数据存储是围绕 AVObject 进行的。 +- 在 LeanCloud 上,数据存储是围绕 AVObject 进行的。 + 每个 AVObject 都包含了与 JSON 兼容的 key-value 对应的数据。 + 数据是 schema-free 的,你不需要在每个 AVObject 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。 + +[错误]: +- 在LeanCloud上,数据存储是围绕AVObject进行的。 +- 在 LeanCloud上,数据存储是围绕AVObject 进行的。 + +#### 中文与数字之间应增加空格 +[正确]: +- 今天出去买菜花了 5000 元。 + +[错误]: +- 今天出去买菜花了 5000元。 +- 今天出去买菜花了5000元。 + +#### 数字与单位之间应增加空格 +[正确]: +- 我家的光纤入屋宽频有 10 Gbps,SSD 一共有 20 TB。 + +[错误]: +- 我家的光纤入屋宽频有 10Gbps,SSD 一共有 20TB。 + +例外:度 (°) 或者百分比 (%) 与数字之间不需要增加空格 + +[正确]: +- 今天是 233° 的高温。 +- 新 MacBook Pro 有 15% 的 CPU 性能提升。 + +[错误]: +- 今天是 233 ° 的高温。 +- 新 MacBook Pro 有 15 % 的 CPU 性能提升。 + +#### 全角标点与其他字符之间应避免加空格 +[正确]: +- 刚刚买了一部 iPhone,好开心! + +[错误]: +- 刚刚买了一部 iPhone ,好开心! +- 刚刚买了一部 iPhone, 好开心! + +### 标点符号 +#### 应避免重复使用标点符号 +[正确]: +- 德国队竟然战胜了巴西队! +- 她竟然对你说「喵」?! + +[错误]: +- 德国队竟然战胜了巴西队!! +- 德国队竟然战胜了巴西队!!!!!!!! +- 她竟然对你说「喵」??!! +- 她竟然对你说「喵」?!?!??!! + +### 全角和半角 +#### 应使用全角中文标点 +[正确]: +- 嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎! +- 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +[错误]: +- 嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎! +- 嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎! +- 核磁共振成像 (NMRI) 是什么原理都不知道? JFGI! +- 核磁共振成像(NMRI)是什么原理都不知道?JFGI! + +#### 数字应使用半角字符 +[正确]: +- 这件蛋糕只卖 1000 元。 + +[错误]: +- 这件蛋糕只卖 1000 元。 + +#### 遇到完整的英文整句、特殊名词,其内容应使用半角标点 +[正确]: +- 贾伯斯那句话是怎么说的?「Stay hungry, stay foolish.」 +- 推荐你阅读《Hackers & Painters: Big Ideas from the Computer Age》,非常的有趣。 + +[错误]: +- 贾伯斯那句话是怎么说的?「Stay hungry,stay foolish。」 +- 推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。 + +### 名词 +#### 专有名词应使用正确的大小写。 +[正确]: +- 使用 GitHub 登录 +- 我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。 + +[错误]: +- 使用 github 登录 +- 使用 GITHUB 登录 +- 使用 Github 登录 +- 使用 gitHub 登录 +- 使用 gイんĤЦ8 登录 +- 我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。 +- 我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。 +- 我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。 +- 我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。 +- 我们的客户有 gイんĤЦ8、キouЯƧquムгє、๓เςг๏ร๏Ŧt ς๏гק๏гคtเ๏ภn、900913、ƒ4ᄃëв๏๏к, IПᄃ.。 + +#### 应避免使用不地道的缩写 +[正确]: +- 我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者。 + +[错误]: +- 我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。 + +### 普遍适用的规则 +#### 链接之间应增加空格 +[正确]: +- 请 [提交一个 issue](https://xxx) 并分配给相关同事。 +- 访问我们网站的最新动态,请 [点击这里](https://xxx) 进行订阅! + +[错误]: +- 请[提交一个issue](https://xxx)并分配给相关同事。 +- 访问我们网站的最新动态,请[点击这里](https://xxx)进行订阅! + +#### 简体中文应使用直角引号 +[正确]: +- 「老师,『有条不紊』的『紊』是什么意思?」 + +[错误]: +- “老师,‘有条不紊’的‘紊’是什么意思?” + +#### 全角 / 半角括号的用法 +括号是用来做解释和提示用,译者在描述和翻译一个没有共识翻译的英文时,也常放在后面作为原文提示。 +当括号里的内容是纯英文的时候,应使用半角括号,如: +今天我们一起去了星巴克 (StarBucks) 点了一份隐藏菜单里的咖啡。 + +当括号里的内容是纯中文或者中英混合的时候,应使用全角括号,如: +使用 Flutter 可以方便的发布多平台的应用(Android 和 iOS),它对所有开发者(前端、后端、移动端)们都非常友好。 + +--- + +## 第四原则:禁止事项清单 + +| # | 禁止行为 | 后果 | +| --- | ---------------------------- | -------------------------- | +| 1 | 删除或修改英文原文 | 无法 diff 上游更新 | +| 2 | 列表中文作为独立 `*`/`-` 项 | 页面渲染出两个列表项 | +| 3 | 提示框拆成两个 `:::` 块 | 页面渲染出两个提示框 | +| 4 | 使用「您」 | 违反全站人称规范 | +| 5 | 修改链接路径 | CI 报 404 | +| 6 | 修改 ` { // 宽显示屏右侧 / 移动端显示屏顶部 目录 正则匹配 const wideRegexp = /
    6. \s*\s*(?!.*[\u4e00-\u9fa5])(.*?)<\/a>\s*<\/span>\s*<\/li>\s*
    7. \s*\s*(?=.*[\u4e00-\u9fa5])(.*?)<\/a>\s*<\/span>/g; - return gulp.src(gulpSrc) + return gulp.src(gulpSrc, { allowEmpty: true }) // 宽显示屏右侧目录 .pipe( replace(wideRegexp, (match, p1, p2, p3, p4, p5) => { @@ -45,7 +45,7 @@ gulp.task('mark-side-level-title', () => { // 正文分级标题(no_toc) 正则匹配 const titleNoTocRegexp = /