diff --git a/src/content/ai-toolkit/chat-client-sample.md b/src/content/ai-toolkit/chat-client-sample.md index 911d4bb302..e8292901d8 100644 --- a/src/content/ai-toolkit/chat-client-sample.md +++ b/src/content/ai-toolkit/chat-client-sample.md @@ -1,9 +1,13 @@ --- title: Chat client sample +title: 聊天客户端示例 description: > Learn about the chat client sample included in the AI Toolkit. +description: > + 了解 AI Toolkit 中包含的聊天客户端示例。 prev: title: Custom LLM providers + title: 自定义 LLM 提供者 path: /ai-toolkit/custom-llm-providers --- @@ -15,17 +19,30 @@ 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 Toolkit 和 Firebase 的 Vertex AI 构建。 +除了从 AI Toolkit 获得的多轮对话、多媒体、 +流式传输等功能外, +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 @@ -33,9 +50,17 @@ 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 diff --git a/src/content/ai-toolkit/custom-llm-providers.md b/src/content/ai-toolkit/custom-llm-providers.md index b64c23c71e..a094706e80 100644 --- a/src/content/ai-toolkit/custom-llm-providers.md +++ b/src/content/ai-toolkit/custom-llm-providers.md @@ -1,18 +1,27 @@ --- title: Custom LLM providers +title: 自定义 LLM provider description: > How to integrate with other Flutter features. +description: > + 如何与其他 Flutter 功能集成。 prev: title: Feature integration + title: 功能集成 path: /ai-toolkit/feature-integration next: title: Chat client sample + title: 聊天客户端示例 path: /ai-toolkit/chat-client-sample --- The protocol connecting an LLM and the `LlmChatView` is expressed in the [`LlmProvider` interface][]: +连接 LLM 和 `LlmChatView` 的协议在 [`LlmProvider` 接口][] 中定义: + +[`LlmProvider` 接口]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html + ```dart abstract class LlmProvider implements Listenable { Stream generateStream(String prompt, {Iterable attachments}); @@ -33,33 +42,60 @@ comes with three providers out of the box, all of which implement the `LlmProvider` interface that is required to plug the provider into the following: +LLM 可以是云端的或本地的,可以托管在 Google Cloud Platform 或其他云服务商上,可以是专有的 LLM 或开源的。任何可以用来实现此接口的 LLM 或类似 LLM 的端点都可以作为 LLM provider 插入到聊天视图中。AI Toolkit 开箱即用地提供了三个提供者,它们都实现了 `LlmProvider` 接口,这是将提供者插入到以下内容所需的: + * The [Gemini provider][], which wraps the `google_generative_ai` package + + [Gemini provider][],它封装了 `google_generative_ai` 包 + * The [Vertex provider][], which wraps the `firebase_vertexai` package + + [Vertex provider][],它封装了 `firebase_vertexai` 包 + * 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 [Gemini provider]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/GeminiProvider-class.html -[`LlmProvider` interface]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html [Vertex provider]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/VertexProvider-class.html ## Implementation +## 实现 + To build your own provider, you need to implement the `LlmProvider` interface with these things in mind: +要构建你自己的 provider,你需要实现 `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 Gemini provider does: + 配置 + 为了在自定义提供者中支持完整的可配置性,你应该允许用户创建底层模型并将其作为参数传入,就像 Gemini provider 所做的那样: + ```dart class GeminiProvider extends LlmProvider ... { @immutable @@ -79,6 +115,8 @@ to the underlying model in the future, the configuration knobs will all be available to the user of your custom provider. +通过这种方式,无论底层模型未来发生什么变化,配置选项都将对你的自定义 provider 的用户可用。 + 2. History History is a big part of any provider—not only does the provider need to allow history to be @@ -89,6 +127,11 @@ to the user of your custom provider. The Gemini provider handles this as shown: + 历史记录 + 历史记录是任何 provider 的重要组成部分—— provider 不仅需要允许直接操作历史记录,还必须在历史记录发生变化时通知监听器。此外,为了支持序列化和更改 provider 参数,它还必须支持在构造过程中保存历史记录。 + + Gemini provider 如下所示处理这个问题: + ```dart class GeminiProvider extends LlmProvider with ChangeNotifier { @immutable @@ -143,14 +186,31 @@ class GeminiProvider 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 @@ -159,8 +219,12 @@ needs to be kept up to date automatically the underlying chat-specific methods) or manually recreated (as the Gemini provider does whenever the history is set manually). +本质上,自定义 provider 管理与底层 LLM 的单个聊天会话的历史记录。随着历史记录的变化,底层聊天需要自动保持最新(就像当你调用底层聊天特定方法时 Gemini AI SDK for Dart 所做的那样)或手动重新创建(就像每当手动设置历史记录时 Gemini 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. @@ -169,6 +233,8 @@ For example, the Gemini provider maps from the `Content` type provided by the Gemini AI SDK for Dart, as shown in the following example: +附件必须从 `LlmProvider` 类型公开的标准 `ChatMessage` 类映射到底层 LLM 处理的任何内容。例如,Gemini provider 将 AI Toolkit 的 `ChatMessage` 类映射到 Gemini AI SDK for Dart 提供的 `Content` 类型,如下例所示: + ```dart import 'package:google_generative_ai/google_generative_ai.dart'; ... @@ -194,8 +260,12 @@ 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. @@ -204,6 +274,8 @@ handles configuration and history but calls to `generateStream` and `sendMessageStream` each end up in a call to an API from the Gemini AI SDK for Dart: +如何调用底层 LLM 来实现 `generateStream` 和 `sendMessageStream` 方法取决于它公开的协议。AI Toolkit 中的 Gemini provider 处理配置和历史记录,但对 `generateStream` 和 `sendMessageStream` 的调用最终都会调用 Gemini AI SDK for Dart 的 API: + ```dart class GeminiProvider extends LlmProvider with ChangeNotifier { ... @@ -275,6 +347,8 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { ## Examples +## 示例 + The [Gemini provider][] and [Vertex provider][] implementations are nearly identical and provide a good starting point for your own custom provider. @@ -284,4 +358,7 @@ 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. +[Gemini provider][] 和 [Vertex provider][] 的实现几乎相同,为你自己的自定义 provider 提供了一个很好的起点。如果你想看一个剥离了所有对底层 LLM 调用的 provider 实现示例,请查看 [Echo 示例应用][Echo example app],它只是将用户的提示和附件格式化为 Markdown 并将其作为响应发送回用户。 + [Echo example app]: {{site.github}}/flutter/ai/blob/main/lib/src/providers/implementations/echo_provider.dart +[Echo 示例应用]: {{site.github}}/flutter/ai/blob/main/lib/src/providers/implementations/echo_provider.dart diff --git a/src/content/ai-toolkit/feature-integration.md b/src/content/ai-toolkit/feature-integration.md index bf7725a18c..29445173bf 100644 --- a/src/content/ai-toolkit/feature-integration.md +++ b/src/content/ai-toolkit/feature-integration.md @@ -1,12 +1,17 @@ --- title: Feature integration +title: 功能集成 description: > How to integrate with other Flutter features. +description: > + 如何与其他 Flutter 功能集成。 prev: title: User experience + title: 用户体验 path: /ai-toolkit/user-experience next: title: Custom LLM providers + title: 自定义 LLM provider path: /ai-toolkit/custom-llm-providers --- @@ -16,39 +21,89 @@ a number of integration points allow your app to blend seamlessly with other features to provide additional functionality: +除了 [`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. + + **自定义响应组件**:引入专门的 UI 组件来呈现 LLM 响应。 + * **Custom styling**: Define unique visual styles to match the chat appearance to the overall app. + + **自定义样式**:定义独特的视觉样式,使聊天外观与整体应用匹配。 + * **Chat w/o 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}); @@ -72,13 +127,21 @@ class ChatPage extends StatelessWidget { To see a complete example of setting the welcome message, check out the [welcome example][]. +要查看设置欢迎消息的完整示例, +请查看 [欢迎示例][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) The suggestions are only shown when there is no existing @@ -86,6 +149,10 @@ chat history. Clicking one copies the text into the user's prompt editing area. To set the list of suggestions, construct the `LlmChatView` with the `suggestions` parameter: +建议仅在没有现有聊天历史记录时显示。 +点击其中一个会将文本复制到用户的提示编辑区域。 +要设置建议列表,请使用 `suggestions` 参数构造 `LlmChatView`: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -113,10 +180,15 @@ class ChatPage extends StatelessWidget { To see a complete example of setting up suggestions for the user, take a look at the [suggestions example][]. +要查看为用户设置建议的完整示例, +请查看 [建议示例][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 @@ -124,6 +196,11 @@ For example, the [recipes example app][] uses the class to tailor the LLM to focus on delivering recipes based on the user's instructions: +要根据你的应用需求优化 LLM 的响应, +你需要给它提供指令。 +例如,[食谱示例应用][recipes example app] 使用 `GenerativeModel` 类的 `systemInstructions` 参数, +将 LLM 定制为专注于根据用户的指令提供食谱: + ```dart class _HomePageState extends State { ... @@ -153,6 +230,9 @@ Setting system instructions is unique to each provider; both the `GeminiProvider` and the `VertexProvider` allow you to provide them through the `systemInstruction` parameter. +设置系统指令对于每个提供者来说都是独特的; +`GeminiProvider` 和 `VertexProvider` 都允许你通过 `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 @@ -160,11 +240,18 @@ 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 提供者创建过程的一部分。 +每次用户更改其偏好时,我们都会在创建过程中设置指令。 +食谱应用允许用户使用脚手架上的抽屉更改其食物偏好: + ![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: +每当用户更改其食物偏好时, +食谱应用会创建一个新模型以使用新的偏好: + ```dart class _HomePageState extends State { ... @@ -178,10 +265,15 @@ class _HomePageState extends State { ## 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}); @@ -204,19 +296,31 @@ class ChatPage extends StatelessWidget { Both of these flags default to `true`. +这两个标志默认都为 `true`。 + ## 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 错误(例如网络连接断开)时,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 { // ... @@ -242,12 +346,19 @@ 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( @@ -273,6 +384,11 @@ it calls the `notifyListener` method exposed by the 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 @@ -281,8 +397,16 @@ 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 = []; ``` @@ -290,6 +414,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 { ... @@ -310,6 +436,11 @@ but now the LLM gives them responses taking their new food preferences into account. For example: +`_createProvider` 方法使用前一个提供者的历史记录和新的用户偏好创建一个新的提供者。 +对于用户来说这是无缝的;他们可以继续聊天, +但现在 LLM 会考虑他们的新食物偏好来给出响应。 +例如: + ```dart class _HomePageState extends State { @@ -327,11 +458,16 @@ class _HomePageState extends State { To see history in action, check out the [recipes example app][] and the [history example app][]. +要查看历史记录的实际应用, +请查看 [食谱示例应用][recipes example app] 和 [历史记录示例应用][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, @@ -341,6 +477,11 @@ are exposed in the `ChatMessage` class. Serialization can be accomplished by using the `toJson` method of each `ChatMessage` instance. +要在应用的会话之间保存和恢复聊天历史记录, +需要能够序列化和反序列化每个用户提示(包括附件)和每个 LLM 响应。 +这两种消息(用户提示和 LLM 响应)都在 `ChatMessage` 类中公开。 +可以使用每个 `ChatMessage` 实例的 `toJson` 方法来完成序列化。 + ```dart Future _saveHistory() async { // get the latest history @@ -363,6 +504,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 @@ -387,12 +530,22 @@ 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 example app]。 + [history example app]: {{site.github}}/flutter/ai/blob/main/example/lib/history/history.dart ## Custom response widgets +## 自定义响应组件 + 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 @@ -404,11 +557,20 @@ 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。 +但是,在某些情况下,你希望创建一个自定义组件来显示特定于你的应用并与之集成的 LLM 响应。 +例如,当用户在 [食谱示例应用][recipes example app] 中请求食谱时, +LLM 响应被用于创建一个专门用于显示食谱的组件, +就像应用的其余部分所做的那样, +并提供一个 **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, @@ -423,6 +585,9 @@ In this particular example, the `RecipeReponseView` widget is constructed with the LLM provider's response text and uses that to implement its `build` method: +在这个特定示例中,`RecipeReponseView` 组件使用 LLM 提供者的响应文本构造, +并使用它来实现其 `build` 方法: + ```dart class RecipeResponseView extends StatelessWidget { const RecipeResponseView(this.response, {super.key}); @@ -486,6 +651,10 @@ 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 @@ -495,6 +664,14 @@ but both the `GeminiProvider` and `VertexProvider` classes enable this with a `GenerationConfig` object that the recipes example uses as follows: +请注意,我们将 LLM 响应解析为 JSON。 +通常会将提供者设置为 JSON 模式, +并提供一个模式来限制其响应的格式, +以确保我们有可以解析的内容。 +每个提供者都以自己的方式公开此功能, +但 `GeminiProvider` 和 `VertexProvider` 类都通过 `GenerationConfig` 对象启用此功能, +食谱示例使用如下: + ```dart class _HomePageState extends State { ... @@ -545,16 +722,28 @@ 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` 类实例来初始化 `GenerationConfig` 对象。 +此外,在系统指令中请求 JSON 并提供该 JSON 模式的描述也是一个好习惯, +我们在这里就是这样做的。 + To see this in action, check out the [recipes example app][]. +要查看实际应用,请查看 [食谱示例应用][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: GeminiProvider(...), @@ -565,6 +754,8 @@ LlmChatView( For example, the [custom styles example app][custom-ex] uses this feature to implement an app with a Halloween theme: +例如,[自定义样式示例应用][custom-ex] 使用此功能实现了一个万圣节主题的应用: + ![Halloween-themed demo app](/assets/images/docs/ai-toolkit/demo-app.png) For a complete list of the styles available in the @@ -573,6 +764,12 @@ To see custom styles in action, in addition to the [custom styles example][custom-ex], check out the [dark mode example][] and the [demo app][]. +有关 `LlmChatViewStyle` 类中可用样式的完整列表, +请查看 [参考文档][reference documentation]。 +要查看自定义样式的实际应用, +除了 [自定义样式示例][custom-ex], +还可以查看 [暗黑模式示例][dark mode example] 和 [演示应用][demo app]。 + [custom-ex]: {{site.github}}/flutter/ai/blob/main/example/lib/custom_styles/custom_styles.dart [dark mode example]: {{site.github}}/flutter/ai/blob/main/example/lib/dark_mode/dark_mode.dart [demo app]: {{site.github}}/flutter/ai#online-demo @@ -580,12 +777,18 @@ check out the [dark 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 As an example, the recipes example app provides a @@ -595,6 +798,11 @@ in 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: +例如,食谱示例应用在编辑食谱的页面上提供了一个 Magic 按钮。 +该按钮的目的是根据你当前的食物偏好更新数据库中的现有食谱。 +按下按钮可以让你预览推荐的更改, +并决定是否应用它们: + ![User decides whether to update recipe in database](/assets/images/docs/ai-toolkit/apply-changes-decision.png) Instead of using the same provider that the chat portion @@ -603,6 +811,9 @@ and LLM responses into the user's chat history, the Edit Recipe page instead creates its own provider and uses it directly: +与其使用应用的聊天部分使用的同一个提供者(这会将虚假的用户消息和 LLM 响应插入到用户的聊天历史记录中), +Edit Recipe 页面改为创建自己的提供者并直接使用它: + ```dart class _EditRecipePageState extends State { ... @@ -661,19 +872,34 @@ 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. +要查看实际应用, +请查看食谱示例的 [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`][] 函数的实现来实现。 +然后将该函数传递给 `LlmChatView` 的 `messageSender` 参数: + [`LlmStreamGenerator`]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmStreamGenerator.html ```dart @@ -721,6 +947,14 @@ 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 example app]。 + [logging example app]: {{site.github}}/flutter/ai/blob/main/example/lib/logging/logging.dart diff --git a/src/content/ai-toolkit/index.md b/src/content/ai-toolkit/index.md index 3a52c2834b..a52068aee0 100644 --- a/src/content/ai-toolkit/index.md +++ b/src/content/ai-toolkit/index.md @@ -1,20 +1,30 @@ --- title: AI Toolkit +title: AI 工具包 description: > Learn how to add the AI Toolkit chatbot to your Flutter application. +description: > + 了解如何在你的 Flutter 应用中添加 AI 工具包聊天机器人。 next: title: User experience + title: 用户体验 path: /ai-toolkit/user-experience --- Hello and welcome to the Flutter AI Toolkit! +你好,欢迎使用 Flutter AI 工具包! + :::note These pages are now out of date. They will be updated soon but, in the meantime, be aware that the `google_generative_ai` and `vertexai_firebase` packages are deprecated and replaced with [`package:firebase_ai`][]. + +这些页面目前已过时。它们将很快更新,但在此期间,请注意 +`google_generative_ai` 和 `vertexai_firebase` 包已被弃用, +并由 [`package:firebase_ai`][] 替代。 ::: [`package:firebase_ai`]: {{site.pub-pkg}}/firebase_ai @@ -27,39 +37,83 @@ LLM provider that you'd like your chat provider to use. Out of the box, it comes with support for two LLM provider integrations: Google Gemini AI and Firebase Vertex AI. +AI 工具包是一组与 AI 聊天相关的 widget,使你能够轻松地在 Flutter 应用中添加 AI 聊天窗口。 +AI 工具包围绕抽象的 LLM 提供商 API 组织,可以轻松切换你的聊天提供商所使用的 LLM 提供商。 +开箱即用,它支持两种 LLM 提供商集成:Google Gemini AI 和 Firebase Vertex AI。 + ## Key features +## 主要功能 + * **Multi-turn 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. + + **多媒体附件**:支持发送和接收各种媒体类型。 + * **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 平台。 + ## Online Demo +## 在线演示 + Here's the online demo 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 仓库中获取。 + Or, you can open it in [Firebase Studio][], Google's full-stack AI workspace and IDE that runs in the cloud: +或者,你可以在 [Firebase Studio][] 中打开它, +这是 Google 的全栈 AI 工作空间和云端 IDE: +
  • Installation +安装 + Add the following dependencies to your `pubspec.yaml` file: +将以下依赖项添加到你的 `pubspec.yaml` 文件中: + ```yaml dependencies: flutter_ai_toolkit: ^latest_version @@ -95,6 +155,8 @@ dependencies:
  • Gemini AI configuration +Gemini AI 配置 + The toolkit supports both Google Gemini AI and Firebase Vertex AI as LLM providers. To use Google Gemini AI, @@ -102,6 +164,10 @@ To use Google Gemini AI, Be careful not to check this key into your source code repository to prevent unauthorized access. +该工具包支持 Google Gemini AI 和 Firebase Vertex AI 作为 LLM 提供商。 +要使用 Google Gemini AI,请从 Gemini AI Studio [获取 API 密钥][obtain an API key]。 +注意不要将此密钥检入源代码仓库,以防止未经授权的访问。 + [obtain an API key]: https://aistudio.google.com/app/apikey You'll also need to choose a specific Gemini model name @@ -109,6 +175,10 @@ to use in creating an instance of the Gemini model. The following example uses `gemini-2.0-flash`, but you can choose from an [ever-expanding set of models][models]. +你还需要选择一个特定的 Gemini 模型名称来创建 Gemini 模型实例。 +以下示例使用 `gemini-2.0-flash`, +但你可以从[不断扩展的模型集][models]中选择。 + [models]: https://ai.google.dev/gemini-api/docs/models/gemini @@ -143,13 +213,22 @@ the `GeminiProvider`, which plugs Gemini AI into the `LlmChatView`, the top-level widget that provides an LLM-based chat conversation with your users. +`GenerativeModel` 类来自 `google_generative_ai` 包。 +AI 工具包在此包的基础上构建了 `GeminiProvider`, +它将 Gemini AI 插入到 `LlmChatView` 中, +后者是为用户提供基于 LLM 的聊天对话的顶级 widget。 + For a complete example, check out [`gemini.dart`][] on GitHub. +有关完整示例,请查看 GitHub 上的 [`gemini.dart`][]。 + [`gemini.dart`]: {{site.github}}/flutter/ai/blob/main/example/lib/gemini/gemini.dart
  • Vertex AI configuration +Vertex AI 配置 + While Gemini AI is useful for quick prototyping, the recommended solution for production apps is Vertex AI in Firebase. This eliminates the need @@ -159,18 +238,32 @@ To use Vertex AI in your project, follow the steps described in the [Get started with the Gemini API using the Vertex AI in Firebase SDKs][vertex] docs. +虽然 Gemini AI 对快速原型设计很有用, +但生产应用的推荐解决方案是 Firebase 中的 Vertex AI。 +这消除了在客户端应用中使用 API 密钥的需要, +并用更安全的 Firebase 项目替代它。 +要在项目中使用 Vertex AI, +请按照[使用 Firebase SDK 中的 Vertex AI 开始使用 Gemini API][vertex] 文档中描述的步骤操作。 + [vertex]: https://firebase.google.com/docs/vertex-ai/get-started?platform=flutter 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 Vertex AI in your Flutter app. Start by initializing Firebase: +按照这些说明操作后, +你就可以在 Flutter 应用中使用 Firebase Vertex AI 了。 +首先初始化 Firebase: + ```dart import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_vertexai/firebase_vertexai.dart'; @@ -192,6 +285,9 @@ void main() async { With Firebase properly initialized in your Flutter app, you're now ready to create an instance of the Vertex provider: +在 Flutter 应用中正确初始化 Firebase 后, +你现在可以创建 Vertex 提供商的实例了: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -221,21 +317,40 @@ Note that you provide a model name but you do not provide an API key. All of that is handled as part of the Firebase project. +`FirebaseVertexAI` 类来自 `firebase_vertexai` 包。 +AI 工具包构建了 `VertexProvider` 类, +将 Vertex AI 暴露给 `LlmChatView`。 +请注意,你需要提供一个模型名称 +([有多个选项][options]可供选择), +但不需要提供 API 密钥。 +所有这些都作为 Firebase 项目的一部分处理。 + For a complete example, check out [vertex.dart][] on GitHub. +有关完整示例,请查看 GitHub 上的 [vertex.dart][]。 + [options]: https://firebase.google.com/docs/vertex-ai/gemini-models#available-model-names [vertex.dart]: {{site.github}}/flutter/ai/blob/main/example/lib/vertex/vertex.dart
  • 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 @@ -249,6 +364,9 @@ 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 ... @@ -258,13 +376,25 @@ 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 @@ -274,12 +404,19 @@ attachments, ensure that your app has the necessary permissions: ## Examples +## 示例 + To execute the [example apps][] in the repo, you'll need to replace the `example/lib/gemini_api_key.dart` and `example/lib/firebase_options.dart` files, both of which are just placeholders. They're needed to enable the example projects in the `example/lib` folder. +要执行仓库中的[示例应用][example apps], +你需要替换 `example/lib/gemini_api_key.dart` +和 `example/lib/firebase_options.dart` 文件, +这两个文件都只是占位符。需要它们来启用 `example/lib` 文件夹中的示例项目。 + **gemini_api_key.dart** Most of the example apps rely on a Gemini API key, @@ -287,8 +424,14 @@ so for those to work, you'll need to plug your API key in the `example/lib/gemini_api_key.dart` file. You can get an API key in [Gemini AI Studio][]. +大多数示例应用依赖于 Gemini API 密钥, +因此要让它们正常工作,你需要在 `example/lib/gemini_api_key.dart` 文件中插入你的 API 密钥。 +你可以在 [Gemini AI Studio][] 中获取 API 密钥。 + :::note **Be careful not to check the `gemini_api_key.dart` file into your git repo.** + +**注意不要将 `gemini_api_key.dart` 文件检入你的 git 仓库。** ::: **firebase_options.dart** @@ -300,13 +443,23 @@ 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` 文件中。 +你可以使用 `flutterfire CLI` 工具完成此操作, +如[将 Firebase 添加到 Flutter 应用][add-fb]文档中所述, +**在 `example` 目录中执行**。 + :::note **Be careful not to check the `firebase_options.dart` file into your git repo.** + +**注意不要将 `firebase_options.dart` 文件检入你的 git 仓库。** ::: ## 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]. @@ -314,6 +467,12 @@ 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. +在使用此包的过程中, +请[记录问题和功能请求][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/src/content/ai-toolkit/user-experience.md b/src/content/ai-toolkit/user-experience.md index e1196aa548..8f1ae110e0 100644 --- a/src/content/ai-toolkit/user-experience.md +++ b/src/content/ai-toolkit/user-experience.md @@ -1,12 +1,17 @@ --- title: User experience +title: 用户体验 description: > How the user will experience the AI Toolkit in your app. +description: > + 用户将如何在你的应用中体验 AI 工具包。 prev: title: AI Toolkit overview + title: AI 工具包概览 path: /ai-toolkit/ next: title: Feature integration + title: 功能集成 path: /ai-toolkit/feature-integration --- @@ -16,74 +21,119 @@ Hosting an instance of the `LlmChatView` enables a number of user experience features that don't require any additional code to use: +[`LlmChatView`][] widget 是 AI 工具包提供的交互式聊天体验的入口点。 +托管一个 `LlmChatView` 实例可以启用许多用户体验功能,无需编写任何额外代码即可使用: + * **Multi-line 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 and send images and other file types. + **多媒体输入**:使用户能够拍照并发送图像和其他文件类型。 * **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 ## Multi-line 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 + **移动端**:点击 **提交** 按钮 * **Web**: Press **Enter** or tap the **Submit** button + **Web 端**:按 **Enter** 或点击 **提交** 按钮 * **Desktop**: Press **Enter** or tap the **Submit** button + **桌面端**:按 **Enter** 或点击 **提交** 按钮 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 + **移动端**:在虚拟键盘上点击回车键 * **Web**: Unsupported + **Web 端**:不支持 * **Desktop**: Press `Ctrl+Enter` or `Opt/Alt+Enter` + **桌面端**:按 `Ctrl+Enter` 或 `Opt/Alt+Enter` These options look like the following: +这些选项如下所示: + **Desktop**: +**桌面端**: + ![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. +除了文本输入,聊天视图还可以通过点击麦克风按钮来接受音频录制作为输入, +该按钮在尚未输入任何文本时可见。 + Tapping the **Mic** button starts the recording: +点击 **麦克风** 按钮开始录音: + ![Screenshot of entering text](/assets/images/docs/ai-toolkit/enter-textfield.png) Pressing the **Stop** button translates the user's voice input into text: +按 **停止** 按钮将用户的语音输入转换为文本: + 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) ## Multi-media Input +## 多媒体输入 + ![Textfield containing "Testing, testing, one, two, three"](/assets/images/docs/ai-toolkit/multi-media-testing-testing.png) The chat view can also take images and files as input to pass along @@ -91,69 +141,108 @@ to the underlying LLM. The user can press the **Plus** button to the left of the text input and choose from the **Take Photo**, **Image Gallery**, and **Attach File** icons: +聊天视图还可以接受图像和文件作为输入,传递给底层 LLM。 +用户可以按文本输入左侧的 **加号** 按钮,并从 **拍照**、**图库** 和 **附加文件** 图标中进行选择: + ![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: +**拍照** 按钮允许用户使用设备的相机拍照: + ![Selfie image](/assets/images/docs/ai-toolkit/selfie.png) Pressing the **Image Gallery** button lets the user upload from their device's image gallery: +按 **图库** 按钮可让用户从设备的图库中上传: + ![Download image from gallery](/assets/images/docs/ai-toolkit/download-from-gallery.png) Pressing the **Attach File** button lets the user select a file of any type available on their device, like a PDF or TXT file. +按 **附加文件** 按钮可让用户选择设备上可用的任何类型的文件,例如 PDF 或 TXT 文件。 + Once a photo, image, or file 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 press the **Copy** button that pops up when they hover their mouse: +此外,在每个提示或响应的底部, +用户可以按鼠标悬停时弹出的 **复制** 按钮: + ![Press 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: +在移动平台上,用户可以长按提示或响应并选择复制选项: + ![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 重新处理它,他们可以这样做。 +在桌面端,用户可以点击最近提示旁边的 **编辑** 按钮, +该按钮与 **复制** 按钮并列: + ![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: +在移动设备上,用户可以长按并访问最近提示上的 **编辑** 选项: + ![How to access edit menu](/assets/images/docs/ai-toolkit/accessing-edit-menu.png) Once the user taps the **Edit** button, they enter Editing mode, @@ -162,6 +251,10 @@ last response from the chat history, puts the text of the prompt into the text field, and provides an Editing indicator: +一旦用户点击 **编辑** 按钮,他们就会进入编辑模式, +该模式会从聊天历史记录中删除用户的最后一条提示和 LLM 的最后一个响应, +将提示的文本放入文本字段,并提供一个编辑指示器: + ![How to exit editing mode](/assets/images/docs/ai-toolkit/how-to-exit-editing-mode.png) In Editing mode, the user can edit the prompt as they choose @@ -170,14 +263,26 @@ 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` widget 托管在 [Material app][] 中时, +它使用 Material 设计语言提供的工具,例如 Material 的 [`TextField`][]。 +同样,当托管在 [Cupertino app][] 中时, +它使用这些工具,例如 [`CupertinoTextField`][]。 + ![Cupertino example app](/assets/images/docs/ai-toolkit/cupertino-chat-app.png) However, while the chat view supports both the Material and @@ -185,6 +290,11 @@ Cupertino app types, 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 [Custom styling]: /ai-toolkit/feature-integration#custom-styling diff --git a/src/content/get-started/custom.md b/src/content/get-started/custom.md index 6aa3dfdc7f..f240233bca 100644 --- a/src/content/get-started/custom.md +++ b/src/content/get-started/custom.md @@ -1,9 +1,12 @@ --- title: Set up Flutter for your 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 --- @@ -12,45 +15,66 @@ 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, consider following the [Flutter quick start][] instead. +如果你计划使用 VS Code 或其他基于 Code - OSS 的编辑器, +可以考虑按照 [Flutter 快速开始][Flutter quick start] 进行操作。 + [Flutter quick start]: /get-started/quick ## Install and set up Flutter {: #install} +## 安装和设置 Flutter {: #install} + 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 或编辑器 {: #editor} + For the best development experience with Flutter, install an IDE or editor with support for Dart and Flutter. Some popular options include VS Code, Android Studio, Firebase Studio, and other Code OSS-based editors. +为了获得最佳的 Flutter 开发体验, +请安装支持 Dart 和 Flutter 的 IDE 或编辑器。 +一些常用的选项包括 VS Code、Android Studio、 +Firebase Studio 和其他基于 Code OSS 的编辑器。 +

    Set up Flutter support in Android Studio.

    +

    在 Android Studio 中设置 Flutter 支持。

    @@ -74,6 +100,7 @@ Firebase Studio, and other Code OSS-based editors.

    Set up Flutter support in an IntelliJ-based IDE.

    +

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

    @@ -82,73 +109,90 @@ Firebase Studio, and other Code OSS-based editors.

    Create a new Flutter workspace in Firebase Studio.

    +

    在 Firebase Studio 中创建新的 Flutter 工作区。

    ## Set up a target platform {: #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]{: target="_blank"} 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]{: target="_blank"}, +因为除了适当的浏览器外不需要额外的设置。 +你可以随时为其他目标平台设置开发环境。 + @@ -157,15 +201,26 @@ You can always set up development for additional target platforms later. ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 {: #next-steps} + **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 codelab on [Building your first app][], set up development for an [additional target platform][], or explore some of these other learning resources. +按照[构建你的第一个应用][Building your first app]的 codelab 继续学习, +为[其他目标平台][additional target platform]设置开发环境, +或探索这些其他学习资源。 + {% render "docs/get-started/setup-next-steps.html", site:site %} [Building your first app]: /get-started/codelab diff --git a/src/content/get-started/fundamentals/index.md b/src/content/get-started/fundamentals/index.md index f76f1fe6b9..07c7538e34 100644 --- a/src/content/get-started/fundamentals/index.md +++ b/src/content/get-started/fundamentals/index.md @@ -1,9 +1,12 @@ --- title: Learn the fundamentals +title: 学习基础知识 shortTitle: Fundamentals +shortTitle: 基础知识 description: > You've gotten a taste of using the Flutter framework; now go beyond to learn the basics of Flutter. +description: 你已经初步体验了 Flutter 框架,现在来深入学习 Flutter 的基础知识吧。 showToc: false --- @@ -14,15 +17,24 @@ showToc: false ## Find your way with Flutter! +## 找到你的 Flutter 学习之路! + If you are new to Flutter, and have already worked through [your first Flutter codelab][], this section of the website is for you! +如果你是 Flutter 新手,并且已经完成了 +[你的第一个 Flutter codelab][your first Flutter codelab], +那么本网站的这一部分就是为你准备的! + The goal here is to guide you through some next steps of learning Flutter. It's not about teaching you how to _program_, it's about teaching you how Flutter works. +这里的目标是引导你完成学习 Flutter 的下一步。 +这不是教你如何*编程*,而是教你 Flutter 是如何工作的。 +
    @@ -32,17 +44,27 @@ how Flutter works. :::note + These fundamentals docs are still a work in progress and we welcome your feedback! Please consider filling out the survey listed at the bottom of this page and on the new subject pages in this section. + +这些基础文档仍在完善中,我们欢迎你的反馈! +请考虑填写本页底部和本节新主题页面上列出的调查问卷。 + ::: We suggest that you work through the following subjects in the listed order. +我们建议你按照下列顺序学习以下主题。 + 1. [Intro to Dart][] _(Optional)_ + + [Dart 入门][Intro to Dart] _(可选)_ + As you might know, Flutter uses the [Dart language][]. If you have experience with other object-oriented languages, like Java, C++, or Swift, @@ -50,10 +72,28 @@ following subjects in the listed order. As of this writing, [Dart is one of the fastest growing languages][dart-lang], in part, thanks to Flutter. + + 你可能知道,Flutter 使用 [Dart 语言][Dart language]。 + 如果你有其他面向对象语言的经验, + 如 Java、C++ 或 Swift, + Dart 对你来说应该很熟悉。 + 截至撰写本文时, + [Dart 是增长最快的语言之一][dart-lang], + 这在一定程度上要归功于 Flutter。 + 2. [Widget fundamentals][] + + [Widget 基础][Widget fundamentals] + Learn about one of the primary building blocks of a Flutter application, widgets. + + 了解 Flutter 应用的主要构建块之一——widget。 + 3. [Layout][] + + [布局][Layout] + Flutter is different from other UI frameworks in that you create the layout programmatically. This allows you to compose widgets, @@ -61,17 +101,44 @@ following subjects in the listed order. to realize your own layout vision. It also facilitates designing a UI to optimize any screen where your app might be used. + + Flutter 与其他 UI 框架不同, + 你可以通过编程方式创建布局。 + 这允许你组合 widget(Flutter 的基本构建块) + 来实现你自己的布局愿景。 + 它还有助于设计 UI 以优化应用可能使用的任何屏幕。 + 4. [State management][] + + [状态管理][State management] + Learn how to share state between widgets and notify other parts of your app when the state changes. See how to implement MVVM in Flutter to manage state effectively for small to medium-sized apps. + + 了解如何在 widget 之间共享状态, + 以及当状态变化时如何通知应用的其他部分。 + 了解如何在 Flutter 中实现 MVVM, + 以有效管理中小型应用的状态。 + 5. [Handling user input][] + + [处理用户输入][Handling user input] + Learn about Flutter's widgets that support interactivity, like buttons and text. Also, learn how to add interactivity to a widget that doesn't already support it. + + 了解 Flutter 中支持交互的 widget, + 如按钮和文本。 + 同时,了解如何为尚不支持交互的 widget 添加交互功能。 + 6. [Networking and data][] + + [网络和数据][Networking and data] + Networking is a very large topic, so this section focuses on basic networking functionality, such as how to retrieve @@ -79,10 +146,23 @@ following subjects in the listed order. how to convert to and from JSON, how to use authentication, how to implement asynchronicity, and more. + + 网络是一个非常广泛的主题, + 因此本节重点介绍基本的网络功能, + 例如如何使用 HTTP 检索或提交数据、 + 如何与 JSON 相互转换、 + 如何使用身份验证、 + 如何实现异步等。 + 7. [Local data and caching][] + + [本地数据和缓存][Local data and caching] + Learn about different techniques for caching local data. + 了解缓存本地数据的不同技术。 + [Dart language]: {{site.dart-site}} [dart-lang]: https://twitter.com/MiSvTh/status/1732002450641400276?cxt [Intro to Dart]: /get-started/fundamentals/dart @@ -96,7 +176,12 @@ following subjects in the listed order. ## 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_6A9KxXR7XmMrNsy?page="index" diff --git a/src/content/get-started/fundamentals/layout.md b/src/content/get-started/fundamentals/layout.md index 7184ea9f18..5c21d5c8ce 100644 --- a/src/content/get-started/fundamentals/layout.md +++ b/src/content/get-started/fundamentals/layout.md @@ -1,11 +1,15 @@ --- title: Layouts +title: 布局 description: Learn how to create layouts in Flutter. +description: 了解如何在 Flutter 中创建布局。 prev: title: Widgets + title: Widget path: /get-started/fundamentals/widgets next: title: State management + title: 状态管理 path: /get-started/fundamentals/state-management --- @@ -21,8 +25,18 @@ Finally, you'll encounter and debug one of Flutter's most common layout errors, the dreaded "unbounded constraints" error. +由于 Flutter 是一个 UI 工具包, +你将花费大量时间使用 Flutter widget 创建布局。 +在本节中,你将学习如何使用一些最常用的布局 widget 来构建布局。 +你将使用 Flutter DevTools(也称为 Dart DevTools) +来了解 Flutter 是如何创建布局的。 +最后,你将遇到并调试 Flutter 中最常见的布局错误之一—— +令人头疼的「无界约束」错误。 + ## Understanding layout in Flutter +## 理解 Flutter 中的布局 + The core of Flutter's layout mechanism is widgets. In Flutter, almost everything is a widget — even layout models are widgets. @@ -32,11 +46,21 @@ Things you don't see are also widgets, such as the rows, columns, and grids that arrange, constrain, and align the visible widgets. +Flutter 布局机制的核心是 widget。 +在 Flutter 中,几乎一切都是 widget——甚至布局模型也是 widget。 +你在 Flutter 应用中看到的图像、图标和文本都是 widget。 +你看不到的东西也是 widget, +例如用于排列、约束和对齐可见 widget 的行、列和网格。 + You create a layout by composing widgets to build more complex widgets. For example, the diagram below shows 3 icons with a label under each one, and the corresponding widget tree: +你可以通过组合 widget 来构建更复杂的 widget,从而创建布局。 +例如,下图展示了 3 个图标,每个图标下方都有一个标签, +以及对应的 widget 树: + A diagram that shows widget composition with a series of lines and nodes. In this example, there's a row of 3 columns where @@ -44,12 +68,19 @@ each column contains an icon and a label. All layouts, no matter how complex, are created by composing these layout widgets. +在这个例子中,有一行 3 列,每列包含一个图标和一个标签。 +所有布局,无论多么复杂,都是通过组合这些布局 widget 来创建的。 + ### Constraints +### 约束 + Understanding constraints in Flutter is an important part of understanding how layout works in Flutter. +理解 Flutter 中的约束是理解 Flutter 布局工作原理的重要部分。 + Layout, in a general sense, refers to the size of the widgets and their positions on the screen. The size and position of any given widget is @@ -59,16 +90,35 @@ and it doesn't decide its own place on the screen. Instead, size and position are determined by a conversation between a widget and its parent. +从一般意义上讲,布局是指 widget 的大小及其在屏幕上的位置。 +任何给定 widget 的大小和位置都受其父级约束; +它不能拥有任何它想要的大小, +也不能自己决定在屏幕上的位置。 +相反,大小和位置是由 widget 与其父级之间的「对话」决定的。 + In the simplest example, the layout conversation looks like this: +在最简单的例子中,布局对话看起来像这样: + 1. A widget receives its constraints from its parent. + + widget 从其父级接收约束。 + 2. A constraint is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height. + + 约束只是 4 个 double 值的集合: + 最小和最大宽度,以及最小和最大高度。 + 3. The widget determines what size it should be within those constraints, and passes its width and height back to the parent. + + widget 在这些约束范围内确定自己应该是什么大小, + 并将其宽度和高度传回给父级。 + 4. The parent looks at the size it wants to be and how it should be aligned, and sets the widget's position accordingly. @@ -76,29 +126,56 @@ the layout conversation looks like this: using a variety of widgets like `Center`, and the alignment properties on `Row` and `Column`. + 父级查看它想要的大小以及应该如何对齐, + 并相应地设置 widget 的位置。 + 可以使用各种 widget(如 `Center`) + 以及 `Row` 和 `Column` 上的对齐属性来显式设置对齐方式。 + In Flutter, this layout conversation is often expressed with the simplified phrase, "Constraints go down. Sizes go up. Parent sets the position." +在 Flutter 中,这种布局对话通常用简化的短语表示: +「约束向下传递。尺寸向上传递。父级设置位置。」 + ### Box types +### 盒子类型 + In Flutter, widgets are rendered by their underlying [`RenderBox`][] objects. These objects determine how to handle the constraints they're passed. +在 Flutter 中,widget 由其底层的 [`RenderBox`][] 对象渲染。 +这些对象决定如何处理传递给它们的约束。 + Generally, there are three kinds of boxes: + +一般来说,有三种类型的盒子: + * Those that try to be as big as possible. For example, the boxes used by [`Center`][] and [`ListView`][]. + +* 尽可能大的盒子。 +例如,[`Center`][] 和 [`ListView`][] 使用的盒子。 + * Those that try to be the same size as their children. For example, the boxes used by [`Transform`][] and [`Opacity`][] + +* 尝试与其子级大小相同的盒子。 +例如,[`Transform`][] 和 [`Opacity`][] 使用的盒子。 + * Those that try to be a particular size. For example, the boxes used by [`Image`][] and [`Text`][]. +* 尝试成为特定大小的盒子。 +例如,[`Image`][] 和 [`Text`][] 使用的盒子。 + Some widgets, for example [`Container`][], vary from type to type based on their constructor arguments. @@ -107,24 +184,49 @@ be as big as possible, but if you give it a width, for instance, it tries to honor that and be that particular size. +某些 widget,例如 [`Container`][], +会根据其构造函数参数改变类型。 +`Container` 构造函数默认尝试尽可能大, +但如果你给它一个宽度,它就会尝试遵守并成为那个特定大小。 + Others, for example [`Row`][] and [`Column`][] (flex boxes) vary based on the constraints they are given. Read more about flex boxes and constraints in the [Understanding Constraints article][]. +其他的,例如 [`Row`][] 和 [`Column`][](flex 盒子), +会根据给定的约束而变化。 +在[理解约束][Understanding Constraints article]文章中阅读更多关于 +flex 盒子和约束的内容。 + ## Lay out a single widget +## 布局单个 widget + To lay out a single widget in Flutter, wrap a visible widget, such as `Text` or `Image` with a widget that can change its position on a screen, such as a `Center` widget. +要在 Flutter 中布局单个 widget, +请用可以改变其在屏幕上位置的 widget(如 `Center` widget) +包裹一个可见的 widget(如 `Text` 或 `Image`)。 + :::note Note + + +:::note 注意 + The examples on the page use a widget called `BorderedImage`. This is a custom widget, and is used here to hide the code that isn't relevant to this topic. + +本页的示例使用了一个名为 `BorderedImage` 的 widget。 +这是一个自定义 widget, +在这里使用它是为了隐藏与本主题无关的代码。 + ::: ```dart @@ -139,16 +241,28 @@ The following figure shows a widget that isn't aligned on the left, and a widget that has been centered on the right. +下图显示了左侧未对齐的 widget 和右侧已居中的 widget。 + A screenshot of a centered widget and a screenshot of a widget that hasn't been centered. All layout widgets have either of the following: + +所有布局 widget 都具有以下属性之一: + * A `child` property if they take a single child—for example, `Center`, `Container`, or `Padding`. + +* 如果它们接受单个子级,则具有 `child` 属性—— +例如 `Center`、`Container` 或 `Padding`。 + * A `children` property if they take a list of widgets—for example, `Row`, `Column`, `ListView`, or `Stack`. +* 如果它们接受 widget 列表,则具有 `children` 属性—— +例如 `Row`、`Column`、`ListView` 或 `Stack`。 + ### Container `Container` is a convenience widget that's @@ -161,6 +275,12 @@ There is also a `Padding` widget that could be used here to the same effect. The following example uses a `Container`. +`Container` 是一个便捷的 widget, +由多个负责布局、绘制、定位和大小调整的 widget 组成。 +在布局方面,它可用于向 widget 添加内边距和外边距。 +也可以使用 `Padding` widget 来达到相同的效果。 +以下示例使用了 `Container`。 + ```dart Widget build(BuildContext context) { return Container( @@ -174,12 +294,17 @@ The following figure shows a widget without padding on the left, and a widget with padding on the right. +下图显示了左侧没有内边距的 widget 和右侧有内边距的 widget。 + A screenshot of a widget with padding and a screenshot of a widget without padding. To create more complex layouts in Flutter, you can compose many widgets. For example, you can combine `Container` and `Center`: +要在 Flutter 中创建更复杂的布局,你可以组合多个 widget。 +例如,你可以组合 `Container` 和 `Center`: + ```dart Widget build(BuildContext context) { return Center( @@ -193,6 +318,8 @@ Widget build(BuildContext context) { ## Layout multiple widgets vertically or horizontally +## 垂直或水平布局多个 widget + One of the most common layout patterns is to arrange widgets vertically or horizontally. You can use a `Row` widget to arrange widgets @@ -200,8 +327,15 @@ horizontally, and a `Column` widget to arrange widgets vertically. The first figure on this page used both. +最常见的布局模式之一是垂直或水平排列 widget。 +你可以使用 `Row` widget 水平排列 widget, +使用 `Column` widget 垂直排列 widget。 +本页的第一个图就同时使用了这两者。 + This is the most basic example of using a `Row` widget. +这是使用 `Row` widget 的最基本示例。 + {% render "docs/code-and-image.md", image:"fwe/layout/row.png", caption: "This figure shows a row widget with three children." @@ -226,6 +360,10 @@ combining to make a complex layout. For example, you could add labels to each of the images in the example above using columns. +`Row` 或 `Column` 的每个子级本身也可以是行和列, +组合起来形成复杂的布局。 +例如,你可以使用列为上面示例中的每个图像添加标签。 + {% render "docs/code-and-image.md", image:"fwe/layout/nested_row_column.png", @@ -263,6 +401,8 @@ Widget build(BuildContext context) { ### Align widgets within rows and columns +### 在行和列中对齐 widget + In the following example, the widgets are each 200 pixels wide, and the viewport is 700 pixels wide. @@ -270,6 +410,9 @@ The widgets are consequently aligned to the left, one after the other, with all the extra space on the right. +在下面的示例中,每个 widget 宽 200 像素,视口宽 700 像素。 +因此,widget 会依次向左对齐,所有额外的空间都在右侧。 + A diagram that shows three widgets laid out in a row. Each child widget is labeled as 200px wide, and the blank space on the right is labeled as 100px wide. You control how a row or column aligns its @@ -280,12 +423,20 @@ the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally. +你可以使用 `mainAxisAlignment` 和 `crossAxisAlignment` 属性 +来控制行或列如何对齐其子级。 +对于行,主轴水平运行,交叉轴垂直运行。 +对于列,主轴垂直运行,交叉轴水平运行。 + A diagram that shows the direction of the main axis and cross axis in both rows and columns Setting the main axis alignment to `spaceEvenly` divides the free horizontal space evenly between, before, and after each image. +将主轴对齐设置为 `spaceEvenly` 会将可用的水平空间 +均匀分配到每个图像之间、之前和之后。 + {% render "docs/code-and-image.md", image:"fwe/layout/space_evenly.png", caption: "This figure shows a row widget with three children, which are aligned with the MainAxisAlignment.spaceEvenly constant." @@ -314,23 +465,40 @@ so setting the main axis alignment to `spaceEvenly` divides the free vertical space evenly between, above, and below each image. +列的工作方式与行相同。 +下面的示例显示了一列 3 个图像,每个图像高 100 像素。 +渲染框的高度(在本例中为整个屏幕)超过 300 像素, +因此将主轴对齐设置为 `spaceEvenly` 会将可用的垂直空间 +均匀分配到每个图像之间、之上和之下。 + A screenshot of a three widgets laid out vertically, using a column widget. The [`MainAxisAlignment`][] and [`CrossAxisAlignment`][] enums offer a variety of constants for controlling alignment. +[`MainAxisAlignment`][] 和 [`CrossAxisAlignment`][] +枚举提供了各种用于控制对齐的常量。 + Flutter includes other widgets that can be used for alignment, notably the `Align` widget. +Flutter 还包括其他可用于对齐的 widget,特别是 `Align` widget。 + ### Sizing widgets within rows and columns +### 在行和列中调整 widget 大小 + When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. In this example, the viewport is 400 pixels wide, and each child is 150 pixels wide. +当布局太大而无法适应设备时, +受影响的边缘会出现黄黑条纹图案。 +在此示例中,视口宽 400 像素,每个子级宽 150 像素。 + A screenshot of a row of widgets that are wider than their viewport. Widgets can be sized to fit within a @@ -339,6 +507,10 @@ To fix the previous example where the row of images is too wide for its render box, wrap each image with an [`Expanded`][] widget. +可以使用 `Expanded` widget 调整 widget 的大小以适应行或列。 +要修复前面示例中图像行对于其渲染框来说太宽的问题, +请用 [`Expanded`][] widget 包裹每个图像。 + {% render "docs/code-and-image.md", image:"fwe/layout/expanded_row.png", caption: "This figure shows a row widget with three children that are wrapped with `Expanded` widgets." @@ -374,6 +546,13 @@ for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2: +`Expanded` widget 还可以指定一个 widget 相对于其兄弟 widget +应该占用多少空间。例如,也许你希望一个 widget +占用其兄弟 widget 两倍的空间。 +为此,请使用 `Expanded` widget 的 `flex` 属性, +这是一个确定 widget 弹性因子的整数。默认弹性因子为 1。 +以下代码将中间图像的弹性因子设置为 2: + {% render "docs/code-and-image.md", image:"fwe/layout/flex_2_row.png", caption: "This figure shows a row widget with three children which are wrapped with `Expanded` widgets. The center child has it's `flex` property set to 2." @@ -401,6 +580,8 @@ Widget build(BuildContext context) { ## DevTools and debugging layout +## DevTools 和调试布局 + In certain situations, a box's constraint is unbounded, or infinite. This means that either the maximum width or the @@ -410,6 +591,11 @@ function usefully when given an unbounded constraint and, in debug mode, throws an exception. +在某些情况下,盒子的约束是无界的或无限的。 +这意味着最大宽度或最大高度被设置为 [`double.infinity`][]。 +当给定无界约束时,尝试尽可能大的盒子将无法正常工作, +并且在调试模式下会抛出异常。 + The most common case where a render box ends up with an unbounded constraint is within a flex box ([`Row`][] or [`Column`][]), @@ -425,30 +611,62 @@ the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction. +渲染框最终获得无界约束的最常见情况是在 +flex 盒子([`Row`][] 或 [`Column`][])中, +以及在可滚动区域 +(如 [`ListView`][] 和其他 [`ScrollView`][] 子类)中。 +例如,`ListView` 会尝试扩展以适应其交叉方向上的可用空间 +(也许它是一个垂直滚动的块,并尝试与其父级一样宽)。 +如果你将一个垂直滚动的 `ListView` +嵌套在一个水平滚动的 `ListView` 中, +内部列表会尝试尽可能宽, +即无限宽,因为外部列表在该方向上是可滚动的。 + Perhaps the most common error you'll run into while building a Flutter application is due to incorrectly using layout widgets, and is referred to as the "unbounded constraints" error. +也许你在构建 Flutter 应用时遇到的最常见错误 +是由于错误使用布局 widget 造成的, +这被称为「无界约束」错误。 + If there was only one type error you should be prepared to confront when you first start building Flutter apps, it would be this one. +如果你刚开始构建 Flutter 应用时只需要准备应对一种类型的错误, +那就是这个错误。 + :::note The Widget inspector + + +:::note Widget 检查器 + Flutter has a robust suite of DevTools that help you work with any number of aspects of Flutter development. The "Widget Inspector" tool is particularly useful when building and debugging layouts (and working with widgets in general). +Flutter 拥有一套强大的 DevTools, +可以帮助你处理 Flutter 开发的各个方面。 +「Widget 检查器」工具在构建和调试布局 +(以及一般情况下使用 widget)时特别有用。 + [Learn more about the Flutter inspector][]. + +[了解更多关于 Flutter 检查器的信息][Learn more about the Flutter inspector]。 + ::: ## Scrolling widgets +## 滚动 widget + Flutter has many built-in widgets that automatically scroll and also offers a variety of widgets that you can customize to @@ -457,6 +675,11 @@ On this page, you'll see how to use the most common widget for making any page scrollable, as well as a widget for creating scrollable lists. +Flutter 有许多内置的 widget 可以自动滚动, +还提供了各种可以自定义的 widget 来创建特定的滚动行为。 +在本页中,你将看到如何使用最常见的 widget +使任何页面可滚动,以及用于创建可滚动列表的 widget。 + ### ListView `ListView` is a column-like widget that @@ -469,6 +692,12 @@ a `ListView` requires its children to take up all the available space on the cross axis, as shown in the example below. +`ListView` 是一个类似列的 widget, +当其内容长于其渲染框时会自动提供滚动。 +使用 `ListView` 的最基本方式与使用 `Column` 或 `Row` 非常相似。 +与列或行不同,`ListView` 要求其子级占用交叉轴上的所有可用空间, +如下面的示例所示。 + {% render "docs/code-and-image.md", image:"fwe/layout/basic_listview.png", caption: "This figure shows a ListView widget with three children." @@ -494,11 +723,19 @@ it's best to use the `ListView.builder` constructor. The builder constructor only builds the children that are currently visible on screen. +当你有未知数量或非常大(或无限)数量的列表项时, +通常会使用 `ListView`。 +在这种情况下,最好使用 `ListView.builder` 构造函数。 +builder 构造函数只构建当前在屏幕上可见的子级。 + In the following example, the `ListView` is displaying a list of to-do items. The todo items are being fetched from a repository, and therefore the number of todos is unknown. +在下面的示例中,`ListView` 显示了一个待办事项列表。 +待办事项是从存储库中获取的,因此待办事项的数量是未知的。 + {% render "docs/code-and-image.md", image:"fwe/layout/listview_builder.png", @@ -531,6 +768,8 @@ Widget build(BuildContext context) { ## Adaptive layouts +## 自适应布局 + Because Flutter is used to create mobile, tablet, desktop, _and_ web apps, it's likely you'll need to adjust your @@ -539,18 +778,32 @@ things like screen size or input device. This is referred to as making an app _adaptive_ and _responsive_. +由于 Flutter 用于创建移动端、平板电脑、桌面端*和* Web 应用, +你可能需要根据屏幕大小或输入设备等因素 +调整应用的行为。 +这被称为使应用具有*自适应性*和*响应性*。 + One of the most useful widgets in making adaptive layouts is the [`LayoutBuilder`][] widget. `LayoutBuilder` is one of many widgets that uses the "builder" pattern in Flutter. +在创建自适应布局时,最有用的 widget 之一是 [`LayoutBuilder`][] widget。 +`LayoutBuilder` 是 Flutter 中使用「构建器」模式的众多 widget 之一。 + ### The builder pattern +### 构建器模式 + In Flutter, you'll find several widgets that use the word "builder" in their names or in their constructors. The following list isn't exhaustive: +在 Flutter 中,你会发现有几个 widget +在其名称或构造函数中使用了「builder」一词。 +以下列表并不详尽: + * [`ListView.builder`][] * [`GridView.builder`][] * [`Builder`][] @@ -564,6 +817,12 @@ to lazily render items in a list, while the `Builder` widget is useful for gaining access to the `BuildContext` in deeply widget code. +这些不同的「构建器」用于解决不同的问题。 +例如,`ListView.builder` 构造函数主要用于 +延迟渲染列表中的项目, +而 `Builder` widget 则用于在深层 widget 代码中 +获取对 `BuildContext` 的访问。 + Despite their different use cases, these builders are unified by how they work. Builder widgets and builder constructors all have @@ -581,6 +840,16 @@ Builder functions always pass in at least one argument–the build context– and generally at least one other argument. +尽管它们的用例不同,这些构建器的工作方式是统一的。 +构建器 widget 和构建器构造函数都有名为「builder」的参数 +(或类似的名称,如 `ListView.builder` 中的 `itemBuilder`), +并且 builder 参数始终接受一个回调。 +这个回调是一个**构建器函数**。 +构建器函数是将数据传递给父 widget 的回调, +父 widget 使用这些参数来构建和返回子 widget。 +构建器函数始终至少传入一个参数——构建上下文—— +并且通常至少还有一个其他参数。 + For example, the `LayoutBuilder` widget is used to create responsive layouts based on the size of the viewport. The builder callback @@ -589,6 +858,11 @@ from its parent, along with the widgets 'BuildContext'. With these constraints, you can return a different widget based on the available space. +例如,`LayoutBuilder` widget 用于根据视口大小创建响应式布局。 +构建器回调体会接收从其父级传递的 [`BoxConstraints`][], +以及 widget 的「BuildContext」。 +通过这些约束,你可以根据可用空间返回不同的 widget。 + In the following example, @@ -597,6 +871,9 @@ changes based on whether the viewport is less than or equal 600 pixels, or greater than 600 pixels. +在下面的示例中,`LayoutBuilder` 返回的 widget +会根据视口是小于等于 600 像素还是大于 600 像素而改变。 + {% render "docs/code-and-image.md", image:"fwe/layout/layout_builder.png", @@ -629,10 +906,20 @@ when Flutter is building the UI, the int passed to the function is 0, the second time it's 1, and so on. +同时,`ListView.builder` 构造函数上的 `itemBuilder` 回调 +会接收构建上下文和一个 `int`。 +对于列表中的每个项目,这个回调都会被调用一次, +int 参数表示列表项的索引。 +当 Flutter 构建 UI 时,第一次调用 itemBuilder 回调时, +传递给函数的 int 是 0,第二次是 1,依此类推。 + This allows you to provide specific configuration based on the index. Recall the example above using the`ListView.builder` constructor: +这允许你根据索引提供特定的配置。 +回忆一下上面使用 `ListView.builder` 构造函数的示例: + ```dart final List items = Repository.fetchTodos(); @@ -662,10 +949,16 @@ todo from the list of items, and then displays that todo's data in the widget that is returned from the builder. +此示例代码使用传递到构建器中的索引 +从项目列表中获取正确的待办事项, +然后在从构建器返回的 widget 中显示该待办事项的数据。 + To exemplify this, the following example changes the background color of every other list item. +为了说明这一点,以下示例更改了每隔一个列表项的背景颜色。 + {% render "docs/code-and-image.md", image:"fwe/layout/alternating_list_items.png" caption:"This figure shows a `ListView`, in which its children have alternating background colors. The background colors were determined programmatically based on the index of the child within the `ListView`." @@ -697,21 +990,38 @@ Widget build(BuildContext context) { ## Additional resources +## 其他资源 + * Common layout widgets and concepts + + 常见布局 widget 和概念 + * Video: [OverlayPortal—Flutter Widget of the Week][] * Video: [Stack—Flutter Widget of the Week][] * Tutorial: [Layouts in Flutter][] * Documentation: [Stack documentation][] + * Sizing and positioning widgets + + 调整大小和定位 widget + * Video: [Expanded—Flutter Widget of the Week][] * Video: [Flexible—Flutter Widget of the Week][] * Video: [Intrinsic widgets—Decoding Flutter][] + * Scrollable widgets + + 可滚动 widget + * Example code: [Work with long lists][] * Example code: [Create a horizontal list][] * Example code: [Create a grid list][] * Video: [ListView—Flutter Widget of the Week][] + * Adaptive Apps + + 自适应应用 + * Tutorial: [Adaptive Apps codelab][] * Video: [MediaQuery—Flutter Widget of the Week][] * Video: [Building platform adaptive apps][] @@ -719,8 +1029,12 @@ Widget build(BuildContext context) { ### API reference +### API 参考 + The following resources explain individual APIs. +以下资源解释了各个 API。 + * [`Builder`][] * [`Row`][] * [`Column`][] @@ -789,7 +1103,12 @@ The following resources explain individual APIs. ## 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_6A9KxXR7XmMrNsy?page="layout" diff --git a/src/content/get-started/fundamentals/local-caching.md b/src/content/get-started/fundamentals/local-caching.md index 39df4b8279..d640715e75 100644 --- a/src/content/get-started/fundamentals/local-caching.md +++ b/src/content/get-started/fundamentals/local-caching.md @@ -1,11 +1,15 @@ --- title: Local caching +title: 本地缓存 description: Learn how to persist data locally. +description: 学习如何在本地持久化数据。 prev: title: Networking and data + title: 网络和数据 path: /get-started/fundamentals/networking next: title: Learn more + title: 了解更多 path: /get-started/learn-flutter --- @@ -19,21 +23,31 @@ it completes again. This technique of retaining application data to show again at a future time is called *caching*, and this page covers how to approach this task in your Flutter app. +现在你已经学习了如何通过网络从服务器加载数据,你的 Flutter 应用应该感觉更加生动了。 +然而,仅仅因为你**能够**从远程服务器加载数据,并不意味着你总是**应该**这样做。有时候,重新渲染从之前网络请求中接收到的数据,而不是重复请求并让用户等待直到再次完成,会更好。这种保留应用数据以便在将来再次显示的技术被称为**缓存**,本页面介绍如何在你的 Flutter 应用中实现这一任务。 + ## Introduction to caching +## 缓存简介 + At its most basic, all caching strategies amount to the same three-step operation, represented with the following pseudocode: +从最基本的层面来说,所有缓存策略都归结为相同的三步操作,用以下伪代码表示: + ```dart Data? _cachedData; Future get data async { // Step 1: Check whether your cache already contains the desired data + // 步骤 1: 检查你的缓存是否已经包含所需的数据 if (_cachedData == null) { // Step 2: Load the data if the cache was empty + // 步骤 2: 如果缓存为空则加载数据 _cachedData = await _readData(); } // Step 3: Return the value in the cache + // 步骤 3: 返回缓存中的值 return _cachedData!; } ``` @@ -42,27 +56,43 @@ There are many interesting ways to vary this strategy, including the location of the cache, the extent to which you preemptively write values to, or "warm", the cache; and others. +有很多有趣的方式可以改变这个策略,包括缓存的位置、你预先向缓存写入值或「预热」缓存的程度,以及其他方式。 + ## Common caching terminology +## 常见的缓存术语 + Caching comes with its own terminology, some of which is defined and explained below. +缓存有其自己的术语,以下定义和解释了其中一些。 + **Cache hit** : An app is said to have had a cache hit when the cache already contained their desired information and loading it from the real source of truth was unnecessary. +**缓存命中** +: 当缓存中已经包含所需信息,无需从真实数据源加载时,应用就被称为产生了缓存命中。 + **Cache miss** : An app is said to have had a cache miss when the cache was empty and the desired data is loaded from the real source of truth, and then saved to the cache for future reads. +**缓存未命中** +: 当缓存为空,所需数据从真实数据源加载,然后保存到缓存以供将来读取时,应用就被称为产生了缓存未命中。 + ## Risks of caching data +## 缓存数据的风险 + An app is said to have a **stale cache** when the data within the source of truth has changed, which puts the app at risk of rendering old, outdated information. +当数据源中的数据已经改变时,应用就被称为拥有**过期缓存**,这会使应用面临渲染旧的、过时信息的风险。 + All caching strategies run the risk of holding onto stale data. Unfortunately, the action of verifying the freshness of a cache often takes as much time to complete as fully loading the data @@ -70,23 +100,34 @@ in question. This means that most apps tend to only benefit from caching data if they trust the data to be fresh at runtime without verification. +所有缓存策略都存在保留过期数据的风险。 +不幸的是,验证缓存新鲜度的操作通常需要花费与完全加载相关数据一样多的时间。这意味着大多数应用往往只有在信任数据在运行时是新鲜的而无需验证的情况下,才能从缓存数据中受益。 + To deal with this, most caching systems include a time limit on any individual piece of cached data. After this time limit is exceeded, would-be cache hits are treated as cache misses until fresh data is loaded. +为了解决这个问题,大多数缓存系统对任何单个缓存数据片段都包含一个时间限制。在超过这个时间限制后,原本应该是缓存命中的情况会被视为缓存未命中,直到加载新鲜数据为止。 + A popular joke among computer scientists is that "The two hardest things in computer science are cache invalidation, naming things, and off-by-one errors." 😄 +计算机科学家中流行的一个笑话是「计算机科学中最难的两件事是缓存失效、命名和差一错误」。😄 + Despite the risks, almost every app in the world makes heavy use of data caching. The rest of this page explores multiple approaches to caching data in your Flutter app, but know that all of these approaches can be tweaked or combined for your situation. +尽管存在风险,世界上几乎每个应用都大量使用数据缓存。本页面的其余部分探讨了在你的 Flutter 应用中缓存数据的多种方法,但要知道所有这些方法都可以根据你的情况进行调整或组合。 + ## Caching data in local memory +## 在本地内存中缓存数据 + The simplest and most performant caching strategy is an in-memory cache. The downside of this strategy is that, because the cache is only held in system memory, no data is @@ -94,16 +135,22 @@ retained beyond the session in which it is originally cached. (Of course, this "downside" also has the upside of automatically solving most stale cache problems!) +最简单和最高性能的缓存策略是内存缓存。这种策略的缺点是,由于缓存仅保存在系统内存中,因此没有数据会在最初缓存的会话之外保留。(当然,这个「缺点」也有自动解决大多数过期缓存问题的好处!) + Due to their simplicity, in-memory caches closely mimic the pseudocode seen above. That said, it is best to use proven design principles, like the [repository pattern][], to organize your code and prevent cache checks like the above from appearing all over your code base. +由于其简单性,内存缓存非常接近上面看到的伪代码。也就是说,最好使用经过验证的设计原则,比如 [repository 模式][],来组织你的代码,并防止像上面那样的缓存检查出现在你的代码库的各处。 + Imagine a `UserRepository` class that is also tasked with caching users in memory to avoid duplicate network requests. Its implementation might look like this: +想象一个 `UserRepository` 类,它还负责在内存中缓存用户以避免重复的网络请求。它的实现可能看起来像这样: + ```dart class UserRepository { UserRepository(this.api); @@ -128,20 +175,29 @@ class UserRepository { This `UserRepository` follows multiple proven design principles including: +这个 `UserRepository` 遵循多个经过验证的设计原则,包括: + * [dependency injection][], which helps with testing +* [依赖注入][dependency injection],有助于测试 * [loose coupling][], which protects surrounding code from its implementation details, and +* [松耦合][loose coupling],保护周围的代码免受其实现细节的影响,以及 * [separation of concerns][], which prevents its implementation from juggling too many concerns. +* [关注点分离][separation of concerns],防止其实现处理过多的关注点。 And best of all, no matter how many times within a single session a user visits pages in your Flutter app that load a given user, the `UserRepository` class only loads that data over the network *once*. +最重要的是,无论用户在单个会话中访问你的 Flutter 应用中加载给定用户的页面多少次,`UserRepository` 类只通过网络加载该数据**一次**。 + However, your users might eventually tire of waiting for data to load every time they relaunch your app. For that, you should choose from one of the persistent caching strategies found below. +然而,你的用户可能最终会厌倦每次重新启动应用时都要等待数据加载。为此,你应该从下面介绍的持久化缓存策略中选择一种。 + [dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection [loose coupling]: https://en.wikipedia.org/wiki/Loose_coupling [repository Pattern]: https://medium.com/@pererikbergman/repository-design-pattern-e28c0f3e4a30 @@ -149,14 +205,21 @@ choose from one of the persistent caching strategies found below. ## Persistent caches +## 持久化缓存 + Caching data in memory will never see your precious cache outlive a single user session. To enjoy the performance benefits of cache hits on fresh launches of your application, you need to cache data somewhere on the device's hard drive. +在内存中缓存数据永远不会让你宝贵的缓存超越单个用户会话。 +要在应用的全新启动时享受缓存命中的性能优势,你需要在设备的硬盘驱动器上某处缓存数据。 + ### Caching data with `shared_preferences` +### 使用 `shared_preferences` 缓存数据 + [`shared_preferences`][] is a Flutter plugin that wraps platform-specific [key-value storage][] on all six of Flutter's target platforms. @@ -165,8 +228,14 @@ for small data sizes, they are still suitable for a caching strategy for most applications. For a complete guide, see our other resources on using key-value stores. +[`shared_preferences`][] 是一个 Flutter 插件,它在 Flutter 的所有六个目标平台上封装了特定平台的 [键值存储][key-value storage]。 +尽管这些底层平台键值存储是为小数据量设计的,但它们仍然适合大多数应用的缓存策略。 +要获取完整指南,请参阅我们关于使用键值存储的其他资源。 + * Cookbook: [Store key-value data on disk][] +* 实用教程:[在磁盘上存储键值数据][Store key-value data on disk] * Video: [Package of the Week: `shared_preferences`][] +* 视频:[每周 Package:`shared_preferences`][Package of the Week: `shared_preferences`] [key-value storage]: https://en.wikipedia.org/wiki/Key%E2%80%93value_database [Package of the Week: `shared_preferences`]: https://www.youtube.com/watch?v=sa_U0jffQII @@ -175,18 +244,26 @@ For a complete guide, see our other resources on using key-value stores. ### Caching data with the file system +### 使用文件系统缓存数据 + If your Flutter app outgrows the low-throughput scenarios ideal for `shared_preferences`, you might be ready to explore caching data with your device's file system. For a more thorough guide, see our other resources on file system caching. +如果你的 Flutter 应用超出了 `shared_preferences` 理想的低吞吐量场景,你可能已经准备好探索使用设备文件系统缓存数据。 +要获取更详尽的指南,请参阅我们关于文件系统缓存的其他资源。 + * Cookbook: [Read and write files][] +* 实用教程:[读写文件][Read and write files] [Read and write files]: /cookbook/persistence/reading-writing-files ### Caching data with an on-device database +### 使用设备上的数据库缓存数据 + The final boss of local data caching is any strategy that uses a proper database to read and write data. Multiple flavors exist, including relational and @@ -195,12 +272,23 @@ All approaches offer dramatically improved performance over simple files - especially for large datasets. For a more thorough guide, see the following resources: +本地数据缓存的终极方案是使用适当的数据库来读写数据的任何策略。 +存在多种类型,包括关系型和非关系型数据库。 +所有方法都比简单文件提供了显著改进的性能——特别是对于大型数据集。 +要获取更详尽的指南,请参阅以下资源: + * Cookbook: [Persist data with SQLite][] +* 实用教程:[使用 SQLite 持久化数据][Persist data with SQLite] * SQLite alternate: [`sqlite3` package][] +* SQLite 替代方案:[`sqlite3` package][] * Drift, a relational database: [`drift` package][] +* Drift,一个关系型数据库:[`drift` package][] * Hive CE, a non-relational database: [`hive_ce` package][] +* Hive CE,一个非关系型数据库:[`hive_ce` package][] * Isar Community, a fast non-relational database: [`isar_community` package][] +* Isar Community,一个快速的非关系型数据库:[`isar_community` package][] * Remote Caching, a lightweight caching system for API responses: [`remote_caching` package][] +* Remote Caching,一个用于 API 响应的轻量级缓存系统:[`remote_caching` package][] [`drift` package]: {{site.pub-pkg}}/drift [`hive_ce` package]: {{site.pub-pkg}}/hive_ce @@ -212,12 +300,18 @@ For a more thorough guide, see the following resources: ## Caching images +## 缓存图片 + Caching images is a similar problem space to caching regular data, though with a one-size-fits-all solution. To direct your Flutter app to use the file system to store images, use the [`cached_network_image` package][]. +缓存图片与缓存常规数据是一个类似的问题空间,不过有一个通用的解决方案。 +要指示你的 Flutter 应用使用文件系统存储图片,请使用 [`cached_network_image` package][]。 + * Video: [Package of the Week: `cached_network_image`][] +* 视频:[每周 Package:`cached_network_image`][Package of the Week: `cached_network_image`] {% comment %} TODO: My understanding is that we now recommend `Image.network` instead of cache_network_image. @@ -228,26 +322,38 @@ TODO: My understanding is that we now recommend `Image.network` instead of cache ## State restoration +## 状态恢复 + Along with application data, you might also want to persist other aspects of a user's session, like their navigation stack, scroll positions, and even partial progress filling out forms. This pattern is called "state restoration", and is built in to Flutter. +除了应用数据,你可能还想持久化用户会话的其他方面,比如他们的导航堆栈、滚动位置,甚至是填写表单的部分进度。这种模式被称为「状态恢复」,并且已内置在 Flutter 中。 + State restoration works by instructing the Flutter framework to sync data from its Element tree with the Flutter engine, which then caches it in platform-specific storage for future sessions. To enable state restoration on Flutter for Android and iOS, see the following documentation: +状态恢复的工作原理是指示 Flutter 框架将其 Element 树中的数据与 Flutter 引擎同步,然后将其缓存在特定平台的存储中以供将来的会话使用。要在 Android 和 iOS 上启用 Flutter 的状态恢复,请参阅以下文档: + * Android documentation: [Android state restoration][] +* Android 文档:[Android 状态恢复][Android state restoration] * iOS documentation: [iOS state restoration][] +* iOS 文档:[iOS 状态恢复][iOS state restoration] [Android state restoration]: /platform-integration/android/restore-state-android [iOS state restoration]: /platform-integration/ios/restore-state-ios ## 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_6A9KxXR7XmMrNsy?page="local-caching" diff --git a/src/content/get-started/fundamentals/networking.md b/src/content/get-started/fundamentals/networking.md index 717f4db488..128d901b9d 100644 --- a/src/content/get-started/fundamentals/networking.md +++ b/src/content/get-started/fundamentals/networking.md @@ -1,11 +1,15 @@ --- title: Networking and data +title: 网络和数据 description: Learn how to network your Flutter app. +description: 了解如何为你的 Flutter 应用添加网络功能。 prev: title: Handling user input + title: 处理用户输入 path: /get-started/fundamentals/user-input next: title: Local data and caching + title: 本地数据和缓存 path: /get-started/fundamentals/local-caching --- @@ -17,13 +21,27 @@ to your Flutter app. Your app will retrieve data, parse JSON into usable in memory representations, and then send data out again. +虽然有句话说「没有人是一座孤岛」, +但一个没有任何网络功能的 Flutter 应用 +可能会让人感觉有些孤立。 +本页介绍了如何为你的 Flutter 应用添加网络功能。 +你的应用将检索数据、 +将 JSON 解析为可在内存中使用的表示形式, +然后再将数据发送出去。 + ## Introduction to retrieving data over the network +## 通过网络检索数据简介 + At it's simplest, assuming you utilize the [`http`][] package to adapt to the differences between network access from Dart VM based platforms and web browser-based environments, making a HTTP `GET` request can be as simple as the following: +最简单的情况下,假设你使用 [`http`][] package +来适应基于 Dart VM 的平台和基于 Web 浏览器的环境之间 +网络访问的差异,发起一个 HTTP `GET` 请求可以像下面这样简单: + ```dart import 'package:http/http.dart' as http; @@ -49,12 +67,26 @@ enabling access to web servers requiring authorization. The article by the Mozilla Developer Network (MDN) gives more background on how authorization works on the web. +以下两个教程向你展示了将 [`http`][] package +添加到应用的所有细节, +无论你是在 Android、iOS、Web 浏览器中运行, +还是在 Windows、macOS 或 Linux 上本地运行。 +第一个教程向你展示如何向网站发起 +未经身份验证的 `GET` 请求, +将检索到的数据解析为 `JSON`,然后显示结果数据。 +第二个教程在第一个教程的基础上添加了身份验证头, +使你能够访问需要授权的 Web 服务器。 +Mozilla 开发者网络 (MDN) 的文章 +提供了有关 Web 上授权工作原理的更多背景信息。 + * Tutorial: [Fetch data from the internet][] * Tutorial: [Make authenticated requests][] * Article: [MDN's article on Authorization for websites][] ## Making data retrieved from the network useful +## 让从网络检索的数据变得有用 + Once you retrieve data from the network, you need a way to convert the data from the network into something that you can easily work with in Dart. @@ -67,11 +99,23 @@ of the [`freezed` package][]. The second links to a codelab that covers patterns and records using a case study of parsing JSON. +一旦你从网络检索到数据, +你需要一种方法将网络数据 +转换为你可以在 Dart 中轻松使用的内容。 +上一节中的教程使用手写 Dart 代码 +将网络数据转换为内存中的表示形式。 +在本节中, +你将看到处理此转换的其他选项。 +第一个链接到一个 YouTube 视频,展示了 [`freezed` package][] 的概述。 +第二个链接到一个 codelab,涵盖了使用解析 JSON 的案例研究来介绍模式和记录。 + * YouTube video: [Freezed (Package of the Week)][] * Codelab: [Dive into Dart's patterns and records][] ## Going both ways, getting data out again +## 双向通信,再次发送数据 + Now that you've mastered the art of retrieving data, it's time to look at pushing data out. This information starts with sending data to the network, @@ -89,6 +133,20 @@ Widget of the Week video. Once you complete that, you'll learn how to debug network traffic using DevTool's Network View. +既然你已经掌握了检索数据的技巧, +现在是时候看看如何推送数据了。 +本信息从向网络发送数据开始, +然后深入探讨异步性。事实是, +一旦你开始通过网络进行通信, +你需要处理这样一个事实:物理上距离很远的 Web 服务器 +可能需要一段时间才能响应, +你不能在等待数据包往返时停止屏幕渲染。 +Dart 对异步性有很好的支持, +Flutter 也是如此。 +你将在教程中了解有关 Dart 支持的所有信息, +然后在 Widget of the Week 视频中了解 Flutter 的功能。 +完成这些内容后,你将学习如何使用 DevTools 的网络视图调试网络流量。 + * Tutorial: [Send data to the internet][] * Tutorial: [Asynchronous programming: futures, async, await][] * YouTube video: [FutureBuilder (Widget of the Week)][] @@ -96,15 +154,26 @@ network traffic using DevTool's Network View. ## Extension material +## 扩展材料 + Now that you've mastered using Flutter's networking APIs, it helps to see Flutter's network usage in context. The first codelab (ostensibly on creating Adaptive apps in Flutter), uses a web server written in Dart to work around the web browsers' [Cross-Origin Resource Sharing (CORS) restrictions][]. +既然你已经掌握了使用 Flutter 的网络 API, +在实际情境中了解 Flutter 的网络使用会很有帮助。 +第一个 codelab(表面上是关于在 Flutter 中创建自适应应用), +使用 Dart 编写的 Web 服务器来解决 Web 浏览器的 +[跨域资源共享 (CORS) 限制][Cross-Origin Resource Sharing (CORS) restrictions]。 + :::note If you've already worked through this codelab on the [layout][] page, feel free to skip this step. + +如果你已经在[布局][layout]页面完成了这个 codelab, +可以跳过这一步。 ::: [layout]: /get-started/fundamentals/layout @@ -115,6 +184,12 @@ talks about how the location of data matters for Flutter apps. Finally, a really useful series of articles by Flutter GDE Anna (Domashych) Leushchenko covering advanced networking in Flutter. +接下来,是一个长篇 YouTube 视频, +Flutter DevRel 前成员 Fitz +讨论了数据位置对 Flutter 应用的重要性。 +最后,是 Flutter GDE Anna (Domashych) Leushchenko 撰写的 +一系列非常有用的文章,涵盖了 Flutter 中的高级网络功能。 + * Codelab: [Adaptive apps in Flutter][] * Video: [Keeping it local: Managing a Flutter app's data][] * Article series: [Basic and advanced networking in Dart and Flutter][] @@ -139,7 +214,12 @@ Anna (Domashych) Leushchenko covering advanced networking in Flutter. ## 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_6A9KxXR7XmMrNsy?page="networking" diff --git a/src/content/get-started/fundamentals/state-management.md b/src/content/get-started/fundamentals/state-management.md index 5150948e08..f95f2defd1 100644 --- a/src/content/get-started/fundamentals/state-management.md +++ b/src/content/get-started/fundamentals/state-management.md @@ -1,11 +1,15 @@ --- title: State management +title: 状态管理 description: Learn how to manage state in Flutter. +description: 学习如何在 Flutter 中管理状态。 prev: title: Layout + title: 布局 path: /get-started/fundamentals/layout next: title: Handling user input + title: 处理用户输入 path: /get-started/fundamentals/user-input --- @@ -15,35 +19,72 @@ State management is how we organize our app to most effectively access these objects and share them between different widgets. +Flutter 应用的**状态**指的是它用来显示 UI 或管理系统资源的所有对象。 +状态管理是指我们如何组织应用程序, +以最有效的方式访问这些对象, +并在不同的 widget 之间共享它们。 + This page explores many aspects of state management, including: +本页面探讨了状态管理的许多方面,包括: + * Using a [`StatefulWidget`][] + + 使用 [`StatefulWidget`][] + * Sharing state between widgets using constructors, [`InheritedWidget`][]s, and callbacks + + 使用构造函数、[`InheritedWidget`][] 和回调在 widget 之间共享状态 + * Using [`Listenable`][]s to notify other widgets when something changes + + 使用 [`Listenable`][] 在某些内容发生变化时通知其他 widget + * Using Model-View-ViewModel (MVVM) for your application's architecture + 使用 Model-View-ViewModel (MVVM) 作为你的应用程序架构 + For other introductions to state management, check out these resources: +有关状态管理的其他介绍,请查看以下资源: + * Video: [Managing state in Flutter][managing-state-video]. This video shows how to use the [riverpod][] package. + 视频:[Managing state in Flutter][managing-state-video]。 + 此视频展示了如何使用 [riverpod][] package。 + Tutorial: [State management][]. This shows how to use `ChangeNotifer` with the [provider][] package. + 教程: +[State management][]。 +展示了如何使用 `ChangeNotifer` 与 [provider][] package。 + This guide doesn't use third-party packages like provider or Riverpod. Instead, it only uses primitives available in the Flutter framework. +本指南不使用第三方 package, +如 provider 或 Riverpod。相反, +它只使用 Flutter 框架中可用的基础组件。 + ## Using a StatefulWidget +## 使用 StatefulWidget + The simplest way to manage state is to use a `StatefulWidget`, which stores state within itself. For example, consider the following widget: +管理状态最简单的方法是使用 `StatefulWidget`, +它将状态存储在自身内部。 +例如,考虑以下 widget: + ```dart class MyCounter extends StatefulWidget { const MyCounter({super.key}); @@ -77,49 +118,93 @@ class _MyCounterState extends State { This code illustrates two important concepts when thinking about state management: +这段代码说明了在考虑状态管理时的两个重要概念: + * **Encapsulation** : The widget that uses `MyCounter` has no visibility into the underlying `count` variable and no means to access or change it. + + **封装**:使用 `MyCounter` 的 widget 无法看到底层的 `count` 变量, + 也无法访问或更改它。 + * **Object lifecycle** : The `_MyCounterState` object and its `count` variable are created the first time that `MyCounter` is built, and exist until it's removed from the screen. This is an example of _ephemeral state_. + **对象生命周期**:`_MyCounterState` 对象及其 `count` 变量 + 在 `MyCounter` 首次构建时创建, + 并一直存在到它从屏幕上移除。 + 这是**临时状态**的一个例子。 + You might find the following resources to be useful: +你可能会发现以下资源很有用: + * Article: [Ephemeral state and app state][ephemeral-state] + + 文章:[Ephemeral state and app state][ephemeral-state] + * API docs: [StatefulWidget][] + API 文档:[StatefulWidget][] + ## Sharing state between widgets +## 在 widget 之间共享状态 + Some scenarios where an app needs to store state include the following: +应用程序需要存储状态的一些场景包括: + * To **update** the shared state and notify other parts of the app + + **更新**共享状态并通知应用程序的其他部分 + * To **listen** for changes to the shared state and rebuild the UI when it changes + **监听**共享状态的变化,并在其变化时重建 UI + This section explores how you can effectively share state between different widgets in your app. The most common patterns are: +本节探讨如何在应用程序中不同的 widget 之间有效地共享状态。 +最常见的模式是: + * **Using widget constructors** (sometimes called "prop drilling" in other frameworks) + + **使用 widget 构造函数**(在其他框架中有时称为「prop drilling」) + * **Using `InheritedWidget`** (or a similar API, such as the [provider][] package). + + **使用 `InheritedWidget`**(或类似的 API,例如 [provider][] package) + * **Using callbacks** to notify a parent widget that something has changed + **使用回调**来通知父 widget 某些内容已更改 + ### Using widget constructors +### 使用 widget 构造函数 + Since Dart objects are passed by reference, it's very common for widgets to define the objects they need to use in their constructor. Any state you pass into a widget's constructor can be used to build its UI: +由于 Dart 对象是通过引用传递的, +widget 在其构造函数中定义它们需要使用的对象是非常常见的。 +你传递到 widget 构造函数中的任何状态都可以用于构建其 UI: + ```dart class MyCounter extends StatelessWidget { final int count; @@ -135,6 +220,9 @@ class MyCounter extends StatelessWidget { This makes it obvious for other users of your widget to know what they need to provide in order to use it: +这使得你的 widget 的其他使用者很清楚地知道 +他们需要提供什么才能使用它: + ```dart Column( children: [ @@ -161,14 +249,27 @@ makes it clear to anyone reading the code that there are shared dependencies. This is a common design pattern called _dependency injection_ and many frameworks take advantage of it or provide tools to make it easier. +通过 widget 构造函数传递应用程序的共享数据, +使阅读代码的任何人都清楚地知道存在共享依赖项。 +这是一种常见的设计模式,称为**依赖注入**, +许多框架都利用了它或提供了工具来使其更容易。 + ### Using InheritedWidget +### 使用 InheritedWidget + Manually passing data down the widget tree can be verbose and cause unwanted boilerplate code, so Flutter provides _`InheritedWidget`_, which provides a way to efficiently host data in a parent widget so that child widgets can access them without storing them as a field. +手动在 widget 树中向下传递数据可能会很冗长, +并导致不必要的样板代码, +因此 Flutter 提供了 **`InheritedWidget`**, +它提供了一种在父 widget 中高效托管数据的方法, +以便子 widget 可以访问它们而无需将它们存储为字段。 + To use `InheritedWidget`, extend the `InheritedWidget` class and implement the static method `of()` using `dependOnInheritedWidgetOfExactType`. @@ -178,6 +279,14 @@ so that any widgets that depend on this `InheritedWidget` rebuild when this widget re-builds with new data and `updateShouldNotify` returns true. +要使用 `InheritedWidget`,扩展 `InheritedWidget` 类, +并使用 `dependOnInheritedWidgetOfExactType` 实现静态方法 `of()`。 +在 build 方法中调用 `of()` 的 widget +会创建一个由 Flutter 框架管理的依赖关系, +以便当此 widget 使用新数据重新构建 +并且 `updateShouldNotify` 返回 true 时, +任何依赖于此 `InheritedWidget` 的 widget 都会重建。 + ```dart class MyState extends InheritedWidget { const MyState({ @@ -189,7 +298,7 @@ class MyState extends InheritedWidget { final String data; static MyState of(BuildContext context) { - // This method looks for the nearest `MyState` widget ancestor. + // 此方法查找最近的 `MyState` widget 祖先。 final result = context.dependOnInheritedWidgetOfExactType(); assert(result != null, 'No MyState found in context'); @@ -198,9 +307,9 @@ class MyState extends InheritedWidget { } @override - // This method should return true if the old widget's data is different - // from this widget's data. If true, any widgets that depend on this widget - // by calling `of()` will be re-built. + // 如果旧 widget 的数据与此 widget 的数据不同,此方法应返回 true。 + // 如果返回 true,任何通过调用 `of()` 依赖于此 widget 的 widget + // 都将被重新构建。 bool updateShouldNotify(MyState oldWidget) => data != oldWidget.data; } ``` @@ -209,6 +318,9 @@ Next, call the `of()` method from the `build()`method of the widget that needs access to the shared state: +接下来,从需要访问共享状态的 widget 的 +`build()` 方法中调用 `of()` 方法: + ```dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @@ -228,10 +340,16 @@ class HomeScreen extends StatelessWidget { ### Using callbacks +### 使用回调 + You can notify other widgets when a value changes by exposing a callback. Flutter provides the `ValueChanged` type, which declares a function callback with a single parameter: +你可以通过暴露一个回调来在值发生变化时通知其他 widget。 +Flutter 提供了 `ValueChanged` 类型, +它声明了一个带有单个参数的函数回调: + ```dart typedef ValueChanged = void Function(T value); ``` @@ -240,6 +358,10 @@ By exposing `onChanged` in your widget's constructor, you provide a way for any widget that is using this widget to respond when your widget calls `onChanged`. +通过在 widget 的构造函数中暴露 `onChanged`, +你为使用此 widget 的任何 widget +提供了一种在你的 widget 调用 `onChanged` 时响应的方法。 + ```dart class MyCounter extends StatefulWidget { const MyCounter({super.key, required this.onChanged}); @@ -254,6 +376,9 @@ class MyCounter extends StatefulWidget { For example, this widget might handle the `onPressed` callback, and call `onChanged` with its latest internal state for the `count` variable: +例如,此 widget 可能会处理 `onPressed` 回调, +并使用 `count` 变量的最新内部状态调用 `onChanged`: + ```dart TextButton( onPressed: () { @@ -264,36 +389,79 @@ TextButton( ### Dive deeper +### 深入了解 + For more information on sharing state between widgets, check out the following resources: +有关在 widget 之间共享状态的更多信息, +请查看以下资源: + * Article: [Flutter Architectural Overview—State management][architecture-state] + + 文章:[Flutter Architectural Overview—State management][architecture-state] + * Video: [Pragmatic state management][] + + 视频:[Pragmatic state management][] + * Video: [InheritedWidgets][inherited-widget-video] + + 视频:[InheritedWidgets][inherited-widget-video] + * Video: [A guide to Inherited Widgets][] + + 视频:[A guide to Inherited Widgets][] + * Sample: [Provider shopper][] + + 示例:[Provider shopper][] + * Sample: [Provider counter][] + + 示例:[Provider counter][] + * API Docs: [`InheritedWidget`][] + API 文档:[`InheritedWidget`][] + ## Using listenables +## 使用 listenable + Now that you've chosen how you want to share state in your app, how do you update the UI when it changes? How do you change the shared state in a way that notifies other parts of the app? +现在你已经选择了如何在应用程序中共享状态, +当它发生变化时如何更新 UI? +如何以通知应用程序其他部分的方式更改共享状态? + Flutter provides an abstract class called `Listenable` that can update one or more listeners. Some useful ways to use listenables are: +Flutter 提供了一个名为 `Listenable` 的抽象类, +它可以更新一个或多个监听器。 +一些使用 listenable 的有用方法是: + * Use a `ChangeNotifier` and subscribe to it using a `ListenableBuilder` + + 使用 `ChangeNotifier` 并使用 `ListenableBuilder` 订阅它 + * Use a `ValueNotifier` with a `ValueListenableBuilder` + 使用 `ValueNotifier` 与 `ValueListenableBuilder` + ### ChangeNotifier To use `ChangeNotifier`, create a class that extends it, and call `notifyListeners` whenever the class needs to notify its listeners. +要使用 `ChangeNotifier`,创建一个扩展它的类, +并在类需要通知其监听器时调用 `notifyListeners`。 + ```dart class CounterNotifier extends ChangeNotifier { int _count = 0; @@ -310,6 +478,10 @@ Then pass it to `ListenableBuilder` to ensure that the subtree returned by the `builder` function is re-built whenever the `ChangeNotifier` updates its listeners. +然后将其传递给 `ListenableBuilder`, +以确保每当 `ChangeNotifier` 更新其监听器时, +`builder` 函数返回的子树都会重新构建。 + ```dart Column( children: [ @@ -338,6 +510,12 @@ so it's compatible with widgets such as `ListenableBuilder` and `ValueListenableBuilder`. To use it, create an instance of `ValueNotifier` with the initial value: +[`ValueNotifier`][] 是 `ChangeNotifier` 的简化版本, +它存储一个单一的值。 +它实现了 `ValueListenable` 和 `Listenable` 接口, +因此与 `ListenableBuilder` 和 `ValueListenableBuilder` 等 widget 兼容。 +要使用它,使用初始值创建一个 `ValueNotifier` 实例: + ```dart ValueNotifier counterNotifier = ValueNotifier(0); ``` @@ -349,6 +527,13 @@ it is also a `Listenable` and can be used with a `ListenableBuilder`. But you can also use `ValueListenableBuilder`, which provides the value in the `builder` callback: +然后使用 `value` 字段来读取或更新值, +并通知任何监听器值已更改。 +因为 `ValueNotifier` 扩展了 `ChangeNotifier`, +它也是一个 `Listenable`,可以与 `ListenableBuilder` 一起使用。 +但你也可以使用 `ValueListenableBuilder`, +它在 `builder` 回调中提供值: + ```dart Column( children: [ @@ -370,37 +555,80 @@ Column( ### Deep dive +### 深入探讨 + To learn more about `Listenable` objects, check out the following resources: +要了解有关 `Listenable` 对象的更多信息,请查看以下资源: + * API Docs: [`Listenable`][] + + API 文档:[`Listenable`][] + * API Docs: [`ValueNotifier`][] + + API 文档:[`ValueNotifier`][] + * API Docs: [`ValueListenable`][] + + API 文档:[`ValueListenable`][] + * API Docs: [`ChangeNotifier`][] + + API 文档:[`ChangeNotifier`][] + * API Docs: [`ListenableBuilder`][] + + API 文档:[`ListenableBuilder`][] + * API Docs: [`ValueListenableBuilder`][] + + API 文档:[`ValueListenableBuilder`][] + * API Docs: [`InheritedNotifier`][] + API 文档:[`InheritedNotifier`][] + ## Using MVVM for your application's architecture +## 为你的应用程序架构使用 MVVM + Now that we understand how to share state and notify other parts of the app when its state changes, we're ready to start thinking about how to organize the stateful objects in our app. +现在我们了解了如何共享状态 +以及在状态发生变化时通知应用程序的其他部分, +我们准备开始思考如何组织 +应用程序中的有状态对象。 + This section describes how to implement a design pattern that works well with reactive frameworks like Flutter, called _Model-View-ViewModel_ or _MVVM_. +本节描述了如何实现一个与 Flutter 等响应式框架配合良好的设计模式, +称为 **Model-View-ViewModel** 或 **MVVM**。 + ### Defining the Model +### 定义 Model + The Model is typically a Dart class that does low-level tasks such as making HTTP requests, caching data, or managing system resources such as a plugin. A model doesn't usually need to import Flutter libraries. +Model 通常是一个执行低级任务的 Dart 类, +例如发出 HTTP 请求、 +缓存数据或管理系统资源(如插件)。 +Model 通常不需要导入 Flutter 库。 + For example, consider a model that loads or updates the counter state using an HTTP client: +例如,考虑一个使用 HTTP 客户端加载或更新计数器状态的 Model: + ```dart import 'package:http/http.dart'; @@ -435,6 +663,11 @@ This allows the model to be implemented with a Mock or Fake in unit tests, and defines clear boundaries between your app's low-level components and the higher-level UI components needed to build the full app. +此 Model 不使用任何 Flutter 基础组件,也不对其运行的平台做任何假设; +它的唯一工作是使用其 HTTP 客户端获取或更新计数。 +这允许在单元测试中使用 Mock 或 Fake 来实现 Model, +并在你的应用程序的低级组件和构建完整应用程序所需的高级 UI 组件之间定义明确的边界。 + The `CounterData` class defines the structure of the data and is the true "model" of our application. The model layer is typically responsible for the core algorithms @@ -444,8 +677,17 @@ such as using immutable value types, check out packages like [freezed][] or [build_collection][] on pub.dev. +`CounterData` 类定义了数据的结构, +是我们应用程序的真正「model」。 +Model 层通常负责应用程序所需的核心算法和数据结构。 +如果你对定义 Model 的其他方法感兴趣, +例如使用不可变值类型, +可以查看 pub.dev 上的 [freezed][] 或 [build_collection][] 等 package。 + ### Defining the ViewModel +### 定义 ViewModel + A `ViewModel` binds the _View_ to the _Model_. It protects the model from being accessed directly by the View, and ensures that data flow starts from a change to the model. @@ -455,6 +697,14 @@ The `ViewModel` is like a waiter in a restaurant that handles the communication between the kitchen (model) and the customers (views). +`ViewModel` 将 **View** 绑定到 **Model**。 +它保护 Model 不被 View 直接访问, +并确保数据流从 Model 的更改开始。 +数据流由 `ViewModel` 处理,它使用 `notifyListeners` +来通知 View 某些内容已更改。 +`ViewModel` 就像餐厅中的服务员, +处理厨房(Model)和顾客(View)之间的通信。 + ```dart import 'package:flutter/foundation.dart'; @@ -497,14 +747,27 @@ which could lead to a crash. Instead, the `errorMessage` field can be used by the view to show a user-friendly error message. +请注意,当 `ViewModel` 从 Model 接收到错误时, +它会存储一个 `errorMessage`。 +这保护了 View 免受未处理的运行时错误的影响, +这些错误可能导致崩溃。 +相反,`errorMessage` 字段 +可以被 View 用来显示用户友好的错误消息。 + ### Defining the View +### 定义 View + Since our `ViewModel` is a `ChangeNotifier`, any widget with a reference to it can use a `ListenableBuilder` to rebuild its widget tree when the `ViewModel` notifies its listeners: +由于我们的 `ViewModel` 是一个 `ChangeNotifier`, +任何引用它的 widget 都可以使用 `ListenableBuilder` +在 `ViewModel` 通知其监听器时重建其 widget 树: + ```dart ListenableBuilder( listenable: viewModel, @@ -536,16 +799,31 @@ This pattern allows the business logic of your application to be separate from the UI logic and low-level operations performed by the Model layer. +此模式允许将应用程序的业务逻辑 +与 UI 逻辑以及 Model 层执行的低级操作分离。 + ## Learn more about state management +## 了解更多关于状态管理的信息 + This page touches the surface of state management as there are many ways to organize and manage the state of your Flutter application. If you would like to learn more, check out the following resources: +本页面只是触及了状态管理的表面, +因为有许多方法可以组织和管理 +你的 Flutter 应用程序的状态。 +如果你想了解更多,请查看以下资源: + * Article: [List of state management approaches][] + + 文章:[List of state management approaches][] + * Repository: [Flutter Architecture Samples][] + 代码库:[Flutter Architecture Samples][] + [A guide to Inherited Widgets]: {{site.youtube-site}}/watch?v=Zbm3hjPjQMk [build_collection]: {{site.pub-pkg}}/built_collection [Flutter Architecture Samples]: https://fluttersamples.com/ @@ -557,7 +835,7 @@ If you would like to learn more, check out the following resources: [State management]: /data-and-backend/state-mgmt/intro [StatefulWidget]: {{site.api}}/flutter/widgets/StatefulWidget-class.html [`StatefulWidget`]: {{site.api}}/flutter/widgets/StatefulWidget-class.html -[`ChangeNotifier`]: {{site.api}}/flutter/foundation/ChangeNotifier-class.html +[`ChangeNotifier`]: {{site.api}}/flutter/widgets/ChangeNotifier-class.html [`InheritedNotifier`]: {{site.api}}/flutter/widgets/InheritedNotifier-class.html [`ListenableBuilder`]: {{site.api}}/flutter/widgets/ListenableBuilder-class.html [`Listenable`]: {{site.api}}/flutter/foundation/Listenable-class.html @@ -574,7 +852,12 @@ If you would like to learn more, check out the following resources: ## 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_6A9KxXR7XmMrNsy?page="state-management" diff --git a/src/content/get-started/fundamentals/user-input.md b/src/content/get-started/fundamentals/user-input.md index 73ec56bb0c..9a7ca16278 100644 --- a/src/content/get-started/fundamentals/user-input.md +++ b/src/content/get-started/fundamentals/user-input.md @@ -1,11 +1,15 @@ --- title: Handling user input +title: 处理用户输入 description: Learn how to handle user input in Flutter. +description: 学习如何在 Flutter 中处理用户输入。 prev: title: State management + title: 状态管理 path: /get-started/fundamentals/state-management next: title: Networking and data + title: 网络和数据 path: /get-started/fundamentals/networking --- @@ -13,8 +17,13 @@ Now that you know how to manage state in your Flutter app, how can you let users interact with your app and change its state? +现在你已经知道如何在 Flutter 应用中管理状态了, +那么如何让用户与你的应用进行交互并改变其状态呢? + ## Introduction to handling user input +## 处理用户输入简介 + As a multi-platform UI framework, there are many different ways for users to interact with a Flutter app. @@ -22,29 +31,56 @@ The resources in this section introduce you to some of the common widgets used for enabling user interaction within your app. +作为一个多平台 UI 框架, +用户可以通过多种不同的方式与 Flutter 应用进行交互。 +本节中的资源将向你介绍一些常用的组件, +这些组件用于在应用中启用用户交互。 + Some user input mechanisms, like [scrolling][], have already been covered in [Layouts][]. +一些用户输入机制,比如 [滚动][scrolling], +已经在 [布局][Layouts] 中介绍过了。 + :::secondary About design system support Flutter ships with prebuilt components for two design systems as part of the SDK, [Material][] and [Cupertino][]. For educational purposes, this page focuses on Material widgets, components that are stylized according to the [Material 3 design language][] specifications. +关于设计系统的支持 +Flutter 在 SDK 中预置了两个设计系统的组件, +[Material][] 和 [Cupertino][]。 +出于教学目的,本页面重点介绍 Material 组件, +这些组件按照 [Material 3 设计语言][Material 3 design language] 规范进行样式设计。 + The Flutter community on [pub.dev][], the package repository for Dart and Flutter, create and support additional design languages such as [Fluent UI][], [macOS UI][], and more. If the existing design system components don't quite fit what you need, Flutter lets you build your own custom widgets, which is covered at the end of this section. No matter which design system you choose, the principals on this page apply. + +在 [pub.dev][] 上的 Flutter 社区(Dart 和 Flutter 的 package 仓库), +创建并支持其他设计语言,如 [Fluent UI][]、[macOS UI][] 等。 +如果现有的设计系统组件不太符合你的需求, +Flutter 允许你构建自己的自定义组件, +这将在本节末尾介绍。 +无论你选择哪个设计系统,本页面的原则都适用。 ::: > **Reference**: > The [widget catalog][] has an inventory of commonly used widgets in the [Material][] and [Cupertino][] libraries. +> **参考**: +> [组件目录][widget catalog] 列出了 [Material][] 和 [Cupertino][] 库中常用的组件清单。 + Next, we'll cover a few of the Material widgets that support common use cases for handling user input in your Flutter app. +接下来,我们将介绍一些 Material 组件, +它们支持 Flutter 应用中处理用户输入的常见用例。 + [scrolling]: /get-started/fundamentals/layout#scrolling-widgets [pub.dev]: {{site.pub}} [Layouts]: /get-started/fundamentals/layout @@ -57,43 +93,82 @@ use cases for handling user input in your Flutter app. ## Buttons +## 按钮 + ![A collection of Material 3 Buttons.](/assets/images/docs/fwe/user-input/material-buttons.png) Buttons allow a user to initiate an action in the UI by clicking or tapping. The Material library provides a variety of button types that are functionally similar, but styled differently for various use cases, including: +按钮允许用户通过点击或轻触在 UI 中启动一个操作。 +Material 库提供了多种按钮类型,它们在功能上相似, +但针对不同的用例采用了不同的样式,包括: + - `ElevatedButton`: A button with some depth. Use elevated buttons to add dimension to otherwise mostly flat layouts. + + `ElevatedButton`:带有一定深度的按钮。使用凸起按钮为原本扁平的布局增加层次感。 + - `FilledButton`: A filled button that should be used for important, final actions that complete a flow, like **Save**, **Join now**, or **Confirm**. + + `FilledButton`:填充按钮,应用于重要的、完成流程的最终操作, + 例如 **保存**、**立即加入** 或 **确认**。 + - `Tonal Button`: A middle ground button between `FilledButton` and `OutlinedButton`. They're useful in contexts where a lower-priority button requires more emphasis than an outline, like **Next**. + + `Tonal Button`:介于 `FilledButton` 和 `OutlinedButton` 之间的按钮。 + 它们在需要比轮廓按钮更突出但优先级较低的上下文中很有用,例如 **下一步**。 + - `OutlinedButton`: A button with text and a visible border. These buttons contain actions that are important, but aren't the primary action in an app. + + `OutlinedButton`:带有文本和可见边框的按钮。 + 这些按钮包含重要的操作, + 但不是应用中的主要操作。 + - `TextButton`: Clickable text, without a border. Since text buttons don't have visible borders, they must rely on their position relative to other content for context. + + `TextButton`:可点击的文本,没有边框。 + 由于文本按钮没有可见的边框, + 它们必须依靠相对于其他内容的位置来提供上下文。 + - `IconButton`: A button with an icon. + + `IconButton`:带有图标的按钮。 + - `FloatingActionButton`: An icon button that hovers over content to promote a primary action. + `FloatingActionButton`:悬浮在内容上方的图标按钮,用于推广主要操作。 + > **Video**: > [FloatingActionButton (Widget of the Week)][] +> **视频**: +> [FloatingActionButton (每周 Widget)][FloatingActionButton (Widget of the Week)] + There are usually 3 main aspects to constructing a button: style, callback, and its child, as seen in the following `ElevatedButton` sample code: +构建按钮通常有 3 个主要方面: +样式、回调函数和子组件, +如下面的 `ElevatedButton` 示例代码所示: + {% comment %} TODO(khanhnwin): -WidgetStateProperty and styling in the design section of + WidgetStateProperty and styling in the design section of FWE. Of course, a button's appearance can be dependent on its state. You can style a button based on its state using `WidgetStateProperty`. {% endcomment %} @@ -104,11 +179,22 @@ You can style a button based on its state using `WidgetStateProperty`. If the callback is `null`, the button is disabled and nothing happens when a user presses the button. + 按钮的回调函数 `onPressed`, + 决定了当按钮被点击时会发生什么, + 因此,这个函数是你更新应用状态的地方。 + 如果回调函数为 `null`,按钮将被禁用, + 用户按下按钮时不会发生任何事情。 + - The button's `child`, which is displayed within the button's content area, is usually text or an icon that indicates the button's purpose. + 按钮的 `child`,显示在按钮的内容区域内, + 通常是指示按钮用途的文本或图标。 + - Finally, a button's `style` controls its appearance: color, border, and so on. + 最后,按钮的 `style` 控制其外观:颜色、边框等。 + {% render "docs/code-and-image.md", image:"fwe/user-input/ElevatedButton.webp", @@ -142,6 +228,9 @@ Widget build(BuildContext context) { > Complete this tutorial that teaches you how to build a > "favorite" button: [Add interactivity to your Flutter app][] +> **检查点**: +> 完成这个教程,学习如何构建一个「收藏」按钮:[为 Flutter 应用添加交互][Add interactivity to your Flutter app] +
    **API Docs**: [`ElevatedButton`][] • [`FilledButton`][] • [`OutlinedButton`][] • [`TextButton`][] • [`IconButton`][] • [`FloatingActionButton`][] @@ -157,14 +246,24 @@ Widget build(BuildContext context) { ## Text +## 文本 + Several widgets support text input. +有几个组件支持文本输入。 + ### `SelectableText` +`SelectableText` 可选择文本 + Flutter's `Text` widget displays text on the screen, but doesn't allow users to highlight or copy the text. `SelectableText` displays a string of _user-selectable_ text. +Flutter 的 `Text` 组件在屏幕上显示文本, +但不允许用户高亮或复制文本。 +`SelectableText` 显示一串 _用户可选择_ 的文本。 + {% render "docs/code-and-image.md", image:"fwe/user-input/SelectableText.webp", caption: "This figure shows a cursor highlighting a portion of a string of text." @@ -186,15 +285,25 @@ From forth the fatal loins of these two foes'''); > **Video**: > [SelectableText (Widget of the Week)][] +> **视频**: +> [SelectableText (每周 Widget)][SelectableText (Widget of the Week)] + [SelectableText (Widget of the Week)]: {{site.youtube-site}}/watch?v=ZSU3ZXOs6hc ### `RichText` +`RichText` 富文本 + `RichText` lets you display strings of rich text in your app. `TextSpan`, similar to `RichText`, allows you to display parts of text with different text styles. It's not for handling user input, but is useful if you're allowing users edit and format text. +`RichText` 让你在应用中显示富文本字符串。 +`TextSpan` 与 `RichText` 类似,允许你用不同的文本样式显示文本的各个部分。 +它不是用来处理用户输入的, +但如果你允许用户编辑和格式化文本,它会很有用。 + {% render "docs/code-and-image.md", image:"fwe/user-input/RichText.png", caption: "This figure shows a string of text formatted with different text styles." @@ -220,37 +329,72 @@ Widget build(BuildContext context) { > **Video**: > [Rich Text (Widget of the Week)][] +> **视频**: +> [Rich Text (每周 Widget)][Rich Text (Widget of the Week)] + > **Code**: > [Rich Text Editor code][] +> **代码**: +> [富文本编辑器代码][Rich Text Editor code] + [Rich Text (Widget of the Week)]: {{site.youtube-site}}/watch?v=rykDVh-QFfw [Rich Text Editor code]: {{site.github}}/flutter/samples/tree/main/simplistic_editor ### `TextField` +`TextField` 文本输入框 + A `TextField` lets users enter text in text box using a hardware or onscreen keyboard. +`TextField` 允许用户使用硬件键盘或屏幕键盘在文本框中输入文本。 + `TextField`s have many different properties and configurations. A few of the highlights: +`TextField` 有许多不同的属性和配置。 +以下是一些重点: + - `InputDecoration` determines the text field's appearance, such as color and border. + + `InputDecoration` 决定文本框的外观,例如颜色和边框。 + - `controller`: A `TextEditingController` controls the text being edited. Why might you need a controller? By default, your app's users can type into the text field, but if you want to programmatically control the `TextField` and clear its value, for example, you'll need a `TextEditingController`. + + `controller`:`TextEditingController` 控制正在编辑的文本。 + 为什么需要控制器? + 默认情况下,你的应用用户可以在文本框中输入, + 但如果你想以编程方式控制 `TextField`, + 例如清除其值,你就需要一个 `TextEditingController`。 + - `onChanged`: This callback function triggers when the user changes the text field's value, such as when inserting or removing text. + + `onChanged`:当用户更改文本框的值时(例如插入或删除文本时), + 会触发此回调函数。 + - `onSubmitted`: This callback is triggered when the user indicates that they are done editing the text in the field; for example, by tapping the "enter" key when the text field is in focus. + `onSubmitted`:当用户表示完成对文本框中文本的编辑时, + 会触发此回调函数; + 例如,在文本框获得焦点时轻触「回车」键。 + The class supports other configurable properties, such as `obscureText` that turns each letter into a `readOnly` circle as its entered and `readOnly` which prevents the user from changing the text. +该类支持其他可配置的属性,例如 +`obscureText`(在输入时将每个字母转换为圆圈)和 +`readOnly`(防止用户更改文本)。 + {% render "docs/code-and-image.md", image:"fwe/user-input/TextField.webp", caption: "This figure shows text being typed into a TextField with a selected border and label." @@ -281,6 +425,14 @@ Widget build(BuildContext context) { > 1. [Handle changes to a text field][] > 1. [Focus and text fields][]. +> **检查点**: +> 完成这个包含 4 个部分的实用教程系列, +> 它将引导你如何创建文本框、获取其值以及更新应用状态: +> 1. [创建和设置文本框样式][Create and style a text field] +> 1. [获取文本框的值][Retrieve the value of a text field] +> 1. [处理文本框的更改][Handle changes to a text field] +> 1. [焦点和文本框][Focus and text fields]。 + [Create and style a text field]: /cookbook/forms/text-input [Retrieve the value of a text field]: /cookbook/forms/retrieve-input [Handle changes to a text field]: /cookbook/forms/text-field-changes @@ -288,21 +440,35 @@ Widget build(BuildContext context) { ### Form +### 表单 + `Form` is an optional container for grouping together multiple form field widgets, such as `TextField`. +`Form` 是一个可选的容器,用于将多个表单字段组件(如 `TextField`)组合在一起。 + Each individual form field should be wrapped in a `FormField` widget with the `Form` widget as a common ancestor. Convenience widgets exist that pre-wrap form field widgets in a `FormField` for you. For example, the `Form` widget version of `TextField` is `TextFormField`. +每个单独的表单字段都应该包装在 `FormField` 组件中, +并以 `Form` 组件作为共同的祖先。 +存在一些便捷组件,可以为你预先将表单字段组件包装在 `FormField` 中。 +例如,`TextField` 的 `Form` 组件版本是 `TextFormField`。 + Using a `Form` provides access to a `FormState`, which lets you save, reset, and validate each `FormField` that descends from this `Form`. You can also provide a `GlobalKey` to identify a specific form, as shown in the following code: +使用 `Form` 可以访问 `FormState`, +它允许你保存、重置和验证从此 `Form` 派生的每个 `FormField`。 +你还可以提供一个 `GlobalKey` 来识别特定的表单, +如下面的代码所示: + ```dart final GlobalKey _formKey = GlobalKey(); @@ -345,12 +511,21 @@ Widget build(BuildContext context) { > **Checkpoint**: > Complete this tutorial to learn how to [build a form with validation][]. +> **检查点**: +> 完成此教程以学习如何 [构建带验证的表单][Build a form with validation]。 + > **Demo**: > [Form app][] +> **演示**: +> [表单应用][Form app] + > **Code**: > [Form app code][] +> **代码**: +> [表单应用代码][Form app code] +
    **API Docs**: [`TextField`][] • [`RichText`][] • [`SelectableText`][] • [`Form`][] @@ -365,36 +540,66 @@ Widget build(BuildContext context) { ## Select a value from a group of options +## 从一组选项中选择一个值 + Provide a way to users to select from several options. +为用户提供从多个选项中进行选择的方法。 + ### SegmentedButton +### 分段按钮 + `SegmentedButton` allows users to select from a minimal group of 2-5 items. +`SegmentedButton` 允许用户从 2 到 5 个项目的最小组中进行选择。 + The data type, ``, can be a built-in type such as `int`, `String`, `bool` or an enum. A `SegmentedButton` has a few relevant properties: +数据类型 `` 可以是内置类型,如 `int`、`String`、`bool` 或枚举。 +`SegmentedButton` 有几个相关的属性: + - `segments`, a list of `ButtonSegment`s, where each represents a "segment" or option that the user can select. Visually, each `ButtonSegment` can have an icon, text label, or both. + `segments`,一个 `ButtonSegment` 列表, + 其中每个代表用户可以选择的「段」或选项。 + 在视觉上,每个 `ButtonSegment` 可以有图标、文本标签或两者都有。 + - `multiSelectionEnabled` indicates whether the user is allowed to select multiple options. This property defaults to false. + `multiSelectionEnabled` 指示是否允许用户选择多个选项。 + 此属性默认为 false。 + - `selected` identifies the currently selected value(s). **Note:** `selected` is of type of `Set`, so if you're only allowing users to select one value, that value must be provided as a`Set` with a single element. + `selected` 标识当前选定的值。 + **注意**:`selected` 的类型为 `Set`, + 因此如果你只允许用户选择一个值, + 则该值必须作为包含单个元素的 `Set` 提供。 + - The `onSelectionChanged` callback triggers when a user selects any segments. It provides a list of the selected segments so you can update your app state. + 当用户选择任何段时,会触发 `onSelectionChanged` 回调。 + 它提供所选段的列表,以便你可以更新应用状态。 + - Additional styling parameters allow you to modify the button's appearance. For example, `style` takes a `ButtonStyle`, providing a way to configure a `selectedIcon`. + 其他样式参数允许你修改按钮的外观。 + 例如,`style` 接受 `ButtonStyle`, + 提供配置 `selectedIcon` 的方法。 + {% render "docs/code-and-image.md", image:"fwe/user-input/segmented-button.webp", caption: "This figure shows a SegmentedButton, each segment with an icon and @@ -447,18 +652,36 @@ Widget build(BuildContext context) { ### Chip +### 纸片 + `Chip` is a compact way of representing an attribute, text, entity, or action for a specific context. Specialized `Chip` widgets exist for specific use cases: +`Chip` 是一种紧凑的方式,用于在特定上下文中表示属性、文本、实体或操作。 +针对特定用例存在专门的 `Chip` 组件: + - [InputChip][] represents a complex piece of information, such as an entity (person, place, or thing), or conversational text, in a compact form. + + [InputChip][] 以紧凑的形式表示复杂的信息, + 例如实体(人、地点或事物)或对话文本。 + - [ChoiceChip][] allows a single selection from a set of options. Choice chips contain related descriptive text or categories. + + [ChoiceChip][] 允许从一组选项中进行单选。 + 选择纸片包含相关的描述性文本或类别。 + - [FilterChip][] uses tags or descriptive words to filter content. + + [FilterChip][] 使用标签或描述性词语来过滤内容。 + - [ActionChip][] represents an action related to primary content. + [ActionChip][] 表示与主要内容相关的操作。 + Every `Chip` widget requires a `label`. It can optionally have an `avatar` (such as an icon or a user's profile picture) and an `onDeleted` callback, which shows a delete icon that @@ -466,10 +689,20 @@ when triggered, deletes the chip. A `Chip` widget's appearance can also be customized by setting a number of optional parameters such as `shape`, `color`, and `iconTheme`. +每个 `Chip` 组件都需要一个 `label`。 +它可以选择性地包含一个 `avatar`(例如图标或用户的个人资料图片) +和一个 `onDeleted` 回调,该回调显示一个删除图标, +触发时会删除纸片。 +`Chip` 组件的外观也可以通过设置一些可选参数(如 `shape`、`color` 和 `iconTheme`)进行自定义。 + You will typically use `Wrap`, a widget that displays its children in multiple horizontal or vertical runs, to make sure your chips wrap and don't get cut off at the edge of your app. +你通常会使用 `Wrap`, +这是一个在多个水平或垂直行中显示其子组件的组件, +以确保你的纸片换行且不会在应用边缘被截断。 + {% render "docs/code-and-image.md", image:"fwe/user-input/chip.png", caption: "This figure shows two rows of Chip widgets, each containing a circular @@ -523,23 +756,48 @@ Widget build(BuildContext context) { ### `DropdownMenu` +`DropdownMenu` 下拉菜单 + A `DropdownMenu` allows users to select a choice from a menu of options and places the selected text into a `TextField`. It also allows users to filter the menu items based on the text input. +`DropdownMenu` 允许用户从选项菜单中选择一个选项, +并将所选文本放入 `TextField` 中。 +它还允许用户根据文本输入过滤菜单项。 + Configuration parameters include the following: +配置参数包括以下内容: + - `dropdownMenuEntries` provides a list of `DropdownMenuEntry`s that describes each menu item. The menu might contain information such as a text label, and a leading or trailing icon. (This is also the only required parameter.) + + `dropdownMenuEntries` 提供一个 `DropdownMenuEntry` 列表, + 描述每个菜单项。 + 菜单可能包含文本标签以及前导或尾随图标等信息。 + (这也是唯一必需的参数。) + - `TextEditingController` allows programmatically controlling the `TextField`. + + `TextEditingController` 允许以编程方式控制 `TextField`。 + - The `onSelected` callback triggers when the user selects an option. + + 当用户选择选项时,会触发 `onSelected` 回调。 + - `initialSelection` allows you to configure the default value. + + `initialSelection` 允许你配置默认值。 + - Additional parameters are also available for customizing the widget's look and behavior. + 还提供了其他参数用于自定义组件的外观和行为。 + {% render "docs/code-and-image.md", image:"fwe/user-input/dropdownmenu.webp", caption: "This figure shows a DropdownMenu widget with 5 value options. Each @@ -598,21 +856,41 @@ Widget build(BuildContext context) { > **Video**: > [DropdownMenu (Widget of the Week)][] +> **视频**: +> [DropdownMenu (每周 Widget)][DropdownMenu (Widget of the Week)] + [DropdownMenu (Widget of the Week)]: {{site.youtube-site}}/watch?v=giV9AbM2gd8?si=E23hjg72cjMTe_mz ### Slider +### 滑块 + The `Slider` widget lets a user adjust a value by moving an indicator, such as a volume bar. +`Slider` 组件允许用户通过移动指示器(例如音量条)来调整值。 + Configuration parameters for the `Slider` widget: +`Slider` 组件的配置参数: + - `value` represents the slider's current value + + `value` 表示滑块的当前值 + - `onChanged` is the callback that gets triggered when the handle is moved + + `onChanged` 是移动滑柄时触发的回调 + - `min` and `max` establish minimum and maximum values allowed by the slider + + `min` 和 `max` 建立滑块允许的最小值和最大值 + - `divisions` establishes a discrete interval with which the user can move the handle along the track. + `divisions` 建立一个离散的间隔,用户可以沿着轨道移动滑柄。 + {% render "docs/code-and-image.md", image:"fwe/user-input/slider.webp", @@ -645,6 +923,9 @@ Widget build(BuildContext context) { > **Video**: > [Slider, RangeSlider, CupertinoSlider (Widget of the Week)][] +> **视频**: +> [Slider、RangeSlider、CupertinoSlider (每周 Widget)][Slider, RangeSlider, CupertinoSlider (Widget of the Week)] +
    **API Docs:** [`SegmentedButton`][] • [`DropdownMenu`][] • [`Slider`][] • [`Chip`][] @@ -657,30 +938,62 @@ Widget build(BuildContext context) { ## Toggle between values +## 在值之间切换 + There are several ways that your UI can allow toggling between values. +有几种方法可以让你的 UI 允许在值之间切换。 + ### Checkbox, Switch, and Radio +### 复选框、开关和单选按钮 + Provide an option to toggle a single value on and off. The functional logic behind these widgets are the same, as all 3 are built on top of `ToggleableStateMixin`, though each provides slight presentation differences.: +提供一个选项来打开和关闭单个值。 +这些组件背后的功能逻辑是相同的, +因为所有 3 个组件都构建在 `ToggleableStateMixin` 之上, +尽管每个组件在呈现上略有不同: + - `Checkbox` is a container that is empty when false or filled with a checkmark when true. + + `Checkbox` 是一个容器,当为 false 时为空, + 当为 true 时填充复选标记。 + - `Switch` has a handle that is on the left when false and slides to the right when true. + + `Switch` 有一个手柄,当为 false 时在左侧, + 当为 true 时滑动到右侧。 + - `Radio` is similar to a `Checkbox` in that it's a container that is empty when false, but filled in when true. + `Radio` 与 `Checkbox` 类似, + 它是一个容器,当为 false 时为空, + 但当为 true 时被填充。 + The configuration for `Checkbox` and `Switch` contain: +`Checkbox` 和 `Switch` 的配置包含: + - a `value` that is `true` or `false` + + 一个 `true` 或 `false` 的 `value` + - and an `onChanged` callback which is triggered when the user toggles the widget + 以及一个 `onChanged` 回调,当用户切换组件时触发 + ### Checkbox +### 复选框 + {% render "docs/code-and-image.md", image:"fwe/user-input/checkbox.webp", caption: "This figure shows a checkbox being checked and unchecked." @@ -707,6 +1020,8 @@ Widget build(BuildContext context) { ### Switch +### 开关 + {% render "docs/code-and-image.md", image:"fwe/user-input/Switch.webp", caption: "This figure shows a Switch widget that is toggled on and off." @@ -736,17 +1051,33 @@ Widget build(BuildContext context) { ### Radio +### 单选按钮 + A `RadioGroup` contains `Radio` buttons that allow the user to select between mutually exclusive values. When the user selects a radio button in a group, the other radio buttons are unselected. +`RadioGroup` 包含 `Radio` 按钮, +允许用户在互斥的值之间进行选择。 +当用户在组中选择一个单选按钮时, +其他单选按钮将被取消选择。 + - A particular `Radio` button's `value` represent that button's value. + + 特定 `Radio` 按钮的 `value` 表示该按钮的值。 + - The selected value for a `RadioGroup` is identified by the `groupValue` parameter. + + `RadioGroup` 的选定值由 `groupValue` 参数标识。 + - `RadioGroup` has an `onChanged` callback that gets triggered when users click it, like `Switch` and `Checkbox`. + `RadioGroup` 有一个 `onChanged` 回调, + 当用户点击它时触发,类似于 `Switch` 和 `Checkbox`。 + {% render "docs/code-and-image.md", image:"fwe/user-input/Radio.webp", caption: "This figure shows a column of ListTiles containing a radio button and @@ -806,9 +1137,14 @@ class _RadioExampleState extends State { #### Bonus: CheckboxListTile & SwitchListTile +#### 额外内容:CheckboxListTile 和 SwitchListTile + These convenience widgets are the same checkbox and switch widgets, but support a label (as a `ListTile`). +这些便捷组件与复选框和开关组件相同, +但支持标签(作为 `ListTile`)。 + {% render "docs/code-and-image.md", image:"fwe/user-input/SpecialListTiles.webp", caption: "This figure shows a column containing a CheckboxListTile and @@ -854,9 +1190,15 @@ Widget build(BuildContext context) { > **Video**: > [CheckboxListTile (Widget of the Week)][] +> **视频**: +> [CheckboxListTile (每周 Widget)][CheckboxListTile (Widget of the Week)] + > **Video**: > [SwitchListTile (Widget of the Week)][] +> **视频**: +> [SwitchListTile (每周 Widget)][SwitchListTile (Widget of the Week)] +
    **API Docs**: @@ -874,24 +1216,45 @@ Widget build(BuildContext context) { ## Select a date or time +## 选择日期或时间 + Widgets are provided so the user can select a date and time. +提供了组件以便用户可以选择日期和时间。 + There is a set of dialogs that enable users to select a date or time, as you'll see in the following sections. With the exception of differing date types - `DateTime` for dates vs `TimeOfDay` for time - these dialogs function similarly, you can configure them by providing: +有一组对话框允许用户选择日期或时间, +正如你将在以下部分中看到的。 +除了日期类型不同(日期使用 `DateTime`,时间使用 `TimeOfDay`)之外, +这些对话框的功能类似,你可以通过提供以下内容进行配置: + - a default `initialDate` or `initialTime` + + 默认的 `initialDate` 或 `initialTime` + - or an `initialEntryMode` that determines the picker UI that's displayed. + 或一个 `initialEntryMode`,用于确定显示的选择器 UI。 + ### DatePickerDialog +### 日期选择器对话框 + This dialog allows the user to select a date or a range of dates. Activate by calling the `showDatePicker` function, which returns a `Future`, so don't forget to await the asynchronous function call! +此对话框允许用户选择日期或日期范围。 +通过调用 `showDatePicker` 函数来激活, +它返回一个 `Future`, +所以不要忘记等待异步函数调用! + {% render "docs/code-and-image.md", image:"fwe/user-input/DatePicker.webp", caption: "This figure shows a DatePicker that is displayed when the @@ -937,12 +1300,20 @@ Widget build(BuildContext context) { ### TimePickerDialog +### 时间选择器对话框 + `TimePickerDialog` is a dialog that presents a time picker. It can be activated by calling the `showTimePicker()` function. Instead of returning a `Future`, `showTimePicker` instead returns a `Future`. Once again, don't forget to await the function call! +`TimePickerDialog` 是一个呈现时间选择器的对话框。 +可以通过调用 `showTimePicker()` 函数来激活它。 +`showTimePicker` 返回 `Future`, +而不是返回 `Future`。 +再次提醒,不要忘记等待函数调用! + {% render "docs/code-and-image.md", image:"fwe/user-input/TimePicker.webp", caption: "This figure shows a TimePicker that is displayed when the @@ -991,6 +1362,11 @@ their respective `Dialog` widgets. To enable state restoration, you can also push `DatePickerDialog()` and `TimePickerDialog()` directly on to the `Navigator` stack. + +调用 `showDatePicker()` 和 `showTimePicker()` +等同于分别使用 `DatePickerDialog()` 和 `TimePickerDialog()` 调用 `showDialog()`。 +在内部,这两个函数都使用 `showDialog()` 函数及其各自的 `Dialog` 组件。 +要启用状态恢复,你还可以直接将 `DatePickerDialog()` 和 `TimePickerDialog()` 推送到 `Navigator` 栈上。 :::
    @@ -1003,17 +1379,36 @@ on to the `Navigator` stack. ## Swipe & slide +## 滑动和滑行 + ### [`Dismissible`][] +可滑动消除 + A `Dismissible` is a widget that enables users to dismiss it by swiping. It has a number of configuration parameters, including: +`Dismissible` 是一个允许用户通过滑动来消除它的组件。 +它有许多配置参数,包括: + - A `child` widget + + 一个 `child` 组件 + - An `onDismissed` callback that is triggered when the user swipes + + 当用户滑动时触发的 `onDismissed` 回调 + - Styling parameters such as `background` + + 样式参数,例如 `background` + - It's important to include a `key` object as well so that they can be uniquely identified from sibling `Dismissible` widgets in the widget tree. + 包含一个 `key` 对象也很重要, + 这样它们就可以在组件树中从同级 `Dismissible` 组件中唯一标识。 + {% render "docs/code-and-image.md", image:"fwe/user-input/Dismissible.webp", caption: "This figure shows a list of Dismissible widgets that each contain a @@ -1055,10 +1450,16 @@ Widget build(BuildContext context) { > **Video**: > [Dismissible (Widget of the Week)][] +> **视频**: +> [Dismissible (每周 Widget)][Dismissible (Widget of the Week)] + > **Checkpoint**: > Complete this tutorial on how to [implement swipe to dismiss][] using the > dismissible widget. +> **检查点**: +> 完成这个关于如何使用可滑动消除组件 [实现滑动消除][Implement swipe to dismiss] 的教程。 +
    **API Docs:** @@ -1070,15 +1471,26 @@ Widget build(BuildContext context) { ## Looking for more widgets? +## 寻找更多组件? + This page features just a few of the common Material widgets that you can use for handling user input in your Flutter app. Check out the [Material Widget library][] and [Material Library API docs][] for a full list of widgets. +本页面仅介绍了一些常见的 Material 组件, +你可以使用它们在 Flutter 应用中处理用户输入。 +查看 [Material 组件库][Material Widget library] 和 +[Material 库 API 文档][Material Library API docs] 以获取完整的组件列表。 + > **Demo**: > See Flutter's [Material 3 Demo][] for a curated sample of user input widgets > available in the Material library. +> **演示**: +> 查看 Flutter 的 [Material 3 演示][Material 3 Demo], +> 了解 Material 库中可用的用户输入组件的精选示例。 + If the Material and Cupertino libraries don't have a widget that does what you need, check out [pub.dev][] to find Flutter & Dart community-owned and maintained packages. @@ -1086,9 +1498,17 @@ For example, the [`flutter_slidable`][] package provides a `Slidable` widget that is more customizable than the `Dismissible` widget described in the previous section. +如果 Material 和 Cupertino 库没有满足你需求的组件, +请查看 [pub.dev][] 以找到 Flutter 和 Dart 社区拥有和维护的 package。 +例如,[`flutter_slidable`][] package 提供了一个 `Slidable` 组件, +它比上一节中描述的 `Dismissible` 组件更可定制。 + > **Video**: > [flutter_slidable (Package of the Week)][] +> **视频**: +> [flutter_slidable (每周 Package)][flutter_slidable (Package of the Week)] + [Material Widget Library]: /ui/widgets/material [Material Library API docs]: {{site.api}}/flutter/material/material-library.html [Material 3 Demo]: https://github.com/flutter/samples/tree/main/material_3_demo @@ -1098,28 +1518,50 @@ the `Dismissible` widget described in the previous section. ## Build interactive widgets with GestureDetector +## 使用 GestureDetector 构建交互式组件 + Have you scoured the widget libraries, pub.dev, asked your coding friends, and still can't find a widget that fits the user interaction that you're looking for? You can build your own custom widget and make it interactive using `GestureDetector`. +你是否搜索了组件库、pub.dev,询问了你的编程朋友, +但仍然找不到符合你所寻找的用户交互的组件? +你可以构建自己的自定义组件, +并使用 `GestureDetector` 使其具有交互性。 + > **Checkpoint**: > Use this recipe as a starting point to create your own _custom_ button widget > that can [handle taps][]. +> **检查点**: +> 使用此教程作为起点,创建你自己的可以 [处理点击][handle taps] 的 _自定义_ 按钮组件。 + > **Video**: > [GestureDetector (Widget of the Week)][] +> **视频**: +> [GestureDetector (每周 Widget)][GestureDetector (Widget of the Week)] + > **Reference**: > Check out [Taps, drags, and other gestures][] which explains how to listen > for, and respond to, gestures in Flutter. +> **参考**: +> 查看 [轻触、拖动和其他手势][Taps, drags, and other gestures], +> 其中解释了如何在 Flutter 中监听和响应手势。 + > **Bonus Video**: > Curious how Flutter's `GestureArena` turns raw user interaction data into > human recognizable concepts like taps, drags, and pinches? > Check out this video: [GestureArena (Decoding Flutter)][] +> **额外视频**: +> 好奇 Flutter 的 `GestureArena` 如何将原始用户交互数据转换为 +> 人类可识别的概念(如轻触、拖动和捏合)? +> 查看此视频:[GestureArena (解码 Flutter)][GestureArena (Decoding Flutter)] + [handle taps]: /cookbook/gestures/handling-taps [GestureDetector (Widget of the Week)]: {{site.youtube-site}}/watch?v=WhVXkCFPmK4 [Taps, drags, and other gestures]: /ui/interactivity/gestures#gestures @@ -1127,14 +1569,23 @@ make it interactive using `GestureDetector`. ### Don't forget about accessibility! +### 不要忘记可访问性! + If you're building a custom widget, annotate its meaning with the `Semantics` widget. It provides descriptions and metadata to screen readers and other semantic analysis-based tools. +如果你正在构建自定义组件, +请使用 `Semantics` 组件注释其含义。 +它为屏幕阅读器和其他基于语义分析的工具提供描述和元数据。 + > **Video**: > [Semantics (Flutter Widget of the Week)][] +> **视频**: +> [Semantics (每周 Flutter Widget)][Semantics (Flutter Widget of the Week)] +
    @@ -1146,28 +1597,44 @@ other semantic analysis-based tools. ## Testing +## 测试 + Once you have finished building user interactions into your app, don't forget to write tests to ensure that everything works as expected! +一旦你完成了在应用中构建用户交互, +不要忘记编写测试以确保一切都按预期工作! + These tutorials walk you through writing tests that simulate user interactions in your app: +这些教程将引导你编写在应用中模拟用户交互的测试: + > **Checkpoint**: > Follow this [tap, drag, and enter text][] cookbook article and learn how to > use `WidgetTester` to simulate and test user interactions in your app. +> **检查点**: +> 遵循这篇 [轻触、拖动和输入文本][Tap, drag, and enter text] 实用教程文章, +> 学习如何使用 `WidgetTester` 在应用中模拟和测试用户交互。 + > **Bonus Tutorial**: > The [handle scrolling][] cookbook recipe shows you how to verify that > lists of widgets contain the expected content by > scrolling through the lists using widget tests. +> **额外教程**: +> [处理滚动][Handle scrolling] 实用教程展示了如何通过使用组件测试滚动列表来验证组件列表是否包含预期的内容。 + [Semantics (Flutter Widget of the Week)]: {{site.youtube-site}}/watch?v=NvtMt_DtFrQ?si=o79BqAg9NAl8EE8_ [Tap, drag, and enter text]: /cookbook/testing/widget/tap-drag [Handle scrolling]: /cookbook/testing/widget/scrolling ## Next: Networking +## 下一步:网络 + This page was an introduction to handling user input. Now that you know how to handle input from app users, you can make your app even more interesting by adding @@ -1176,9 +1643,22 @@ you'll learn how to fetch data for your app over a network, how to convert data to and from JSON, authentication, and other networking features. +本页面是处理用户输入的介绍。 +现在你知道如何处理来自应用用户的输入了, +你可以通过添加外部数据使你的应用更有趣。 +在下一节中, +你将学习如何通过网络为你的应用获取数据、 +如何在 JSON 之间转换数据、身份验证 +以及其他网络功能。 + ## 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_6A9KxXR7XmMrNsy?page="user-input" diff --git a/src/content/get-started/quick.md b/src/content/get-started/quick.md index 4140be2bc2..deb24f60a4 100644 --- a/src/content/get-started/quick.md +++ b/src/content/get-started/quick.md @@ -1,9 +1,12 @@ --- title: Set up and test drive Flutter +title: 设置并体验 Flutter shortTitle: Quick start +shortTitle: 快速开始 description: >- Set up Flutter on your device with a OSS-based editor, such as VS Code, and get started developing your first multi-platform app with Flutter! +description: 使用 VS Code 等基于 OSS 的编辑器在你的设备上设置 Flutter,并开始开发你的第一个多平台应用! showBanner: false sitemap: false --- @@ -12,50 +15,92 @@ Learn how to use any OSS-based editor, such as VS Code, to set up your Flutter development environment and test drive Flutter's developer experience. +了解如何使用 VS Code 等基于 OSS 的编辑器 +来设置 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. +如果你之前使用过 Flutter 进行开发, +或者你更喜欢使用其他编辑器或 IDE, +可以按照[自定义设置说明][custom setup instructions]进行操作。 + :::note What you'll achieve + + +:::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 开发功能,如有状态热重载。 + ::: [custom setup instructions]: /get-started/custom ## Confirm your development platform {: #dev-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} +## 下载必备软件 {: #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

    +

    下载并安装必备软件包

    Using `apt-get` or your preferred installation mechanism, install the latest versions of the following packages: + 使用 `apt-get` 或你偏好的安装方式, + 安装以下软件包的最新版本: + - `curl` - `git` - `unzip` @@ -66,33 +111,51 @@ first install the following tools. If you want to use `apt-get`, install these packages using the following commands: + 如果你想使用 `apt-get`, + 请使用以下命令安装这些软件包: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa ``` 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 ``` @@ -101,42 +164,71 @@ 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

    +

    安装 Git for Windows

    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

    +

    下载并安装必备软件包

    Using your preferred package manager or mechanism, install the latest versions of the following packages: + 使用你偏好的包管理器或安装方式, + 安装以下软件包的最新版本: + - `curl` - `git` - `unzip` @@ -147,21 +239,32 @@ first install the following tools. On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: + 在使用 `apt-get` 的基于 Debian 的发行版(如 Ubuntu)上, + 请使用以下命令安装这些软件包: + ```console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa ``` 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 @@ -172,48 +275,92 @@ first install the following tools. ## Install and set up Flutter {: #install} +## 安装和设置 Flutter {: #install} + 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。 + :::note Download manually + + +:::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. + 如果 VS Code 尚未打开,请使用 Spotlight 搜索它, + 或从安装目录手动打开它。 + 1.

    Add the Flutter extension to VS Code

    +

    将 Flutter 扩展添加到 VS Code

    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. + 要将 Dart 和 Flutter 扩展添加到 VS Code, + 请访问 [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. ``` @@ -222,30 +369,52 @@ 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"} 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 @@ -254,102 +423,196 @@ follow the instructions in [Install Flutter manually][]. ## Test drive Flutter {: #test-drive} +## 体验 Flutter {: #test-drive} + 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(() { // ... @@ -362,48 +625,89 @@ 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. + 一个单独的 **Widget Inspector** 面板应该会在 VS Code 中打开。 + In the widget inspector, you can view your app's widget tree, view the properties and layout of each widget, and more. + 在 widget 检查器中,你可以查看应用的 widget 树、 + 查看每个 widget 的属性和布局等。 + 1. In the widget inspector, try clicking the top-level `MyHomePage` widget. + 在 widget 检查器中,尝试点击顶层的 `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 检查器和 Flutter 侧边栏中的其他功能。 + {:.steps} [increment-button]: /assets/images/docs/get-started/increment-button.png @@ -412,12 +716,20 @@ it's time to create an app and try out Flutter development! ## Continue your Flutter journey {: #next-steps} +## 继续你的 Flutter 之旅 {: #next-steps} + **Congratulations!** Now that you've installed and tried out Flutter, follow the codelab on [Building your first app][], set up development for an [additional target platform][], or explore some of these resources to continue your Flutter learning journey. +**恭喜!** +现在你已经安装并体验了 Flutter, +可以按照 [构建你的第一个应用][Building your first app] 的 codelab 继续学习, +为[其他目标平台][additional target platform]设置开发环境, +或探索这些资源来继续你的 Flutter 学习之旅。 + {% render "docs/get-started/setup-next-steps.html", site: site %} [Building your first app]: /get-started/codelab