diff --git a/.gitignore b/.gitignore index f0a56959a8..41e8fa837d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,13 @@ tmp/ # Temporary as site-shared used to be a submodule. site-shared/ + +# ==== God System (Stealth Mode) ==== +# 神系统文件 - 不提交到 git +.cursorrules +.god/ +god-core/ +context.md +*.chatmode.md +*-sidecar/ +# ==== End God System ==== diff --git a/src/content/ai-toolkit/chat-client-sample.md b/src/content/ai-toolkit/chat-client-sample.md index 911d4bb302..dbbc778eb9 100644 --- a/src/content/ai-toolkit/chat-client-sample.md +++ b/src/content/ai-toolkit/chat-client-sample.md @@ -1,5 +1,8 @@ --- +# title: Chat client sample title: Chat client sample +# description: > +# Learn about the chat client sample included in the AI Toolkit. description: > Learn about the chat client sample included in the AI Toolkit. prev: @@ -15,17 +18,40 @@ 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) + +![桌面应用 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) + + +![移动应用 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 +59,19 @@ 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..df80d1f82f 100644 --- a/src/content/ai-toolkit/custom-llm-providers.md +++ b/src/content/ai-toolkit/custom-llm-providers.md @@ -1,5 +1,8 @@ --- +# title: Custom LLM providers title: Custom LLM providers +# description: > +# How to integrate with other Flutter features. description: > How to integrate with other Flutter features. prev: @@ -13,6 +16,10 @@ next: The protocol connecting an LLM and the `LlmChatView` is expressed in the [`LlmProvider` interface][]: + + +连接 LLM 和 `LlmChatView` 的协议在 [`LlmProvider` 接口][`LlmProvider` interface]中表达: + ```dart abstract class LlmProvider implements Listenable { Stream generateStream(String prompt, {Iterable attachments}); @@ -33,6 +40,16 @@ 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 开箱即提供三个 provider, +它们都实现了将 provider 接入以下内容所需的 `LlmProvider` 接口: + * The [Gemini provider][], which wraps the `google_generative_ai` package * The [Vertex provider][], @@ -40,6 +57,12 @@ that is required to plug the provider into the following: * The [Echo provider][], which is useful as a minimal provider example + + +* [Gemini provider][],封装了 `google_generative_ai` package +* [Vertex provider][],封装了 `firebase_vertexai` package +* [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 @@ -47,19 +70,41 @@ that is required to plug the provider into the following: ## 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 1. Calling the underlying LLM + + +1. 提供完整的配置支持 +1. 处理历史记录 +1. 将消息和附件转换为底层 LLM 的格式 +1. 调用底层 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: + + +1. 配置 + 要在自定义 provider 中支持完整的可配置性, + 你应该允许用户创建底层模型并将其作为参数传入, + 就像 Gemini provider 所做的那样: + ```dart class GeminiProvider extends LlmProvider ... { @immutable @@ -69,16 +114,30 @@ class GeminiProvider extends LlmProvider ... { }) : _model = model, ... - final GenerativeModel _model; +final GenerativeModel _model; ... } ``` + + +这样,无论底层模型将来发生什么变化, +所有配置选项都将对你的自定义 provider 的用户可用。 + In this way, no matter what changes come to the underlying model in the future, the configuration knobs will all be available to the user of your custom provider. + + +2. 历史记录 + 历史记录是任何 provider 的重要组成部分—— + provider 不仅需要允许直接操作历史记录, + 而且必须在历史记录更改时通知监听器。 + 此外,为了支持序列化和更改 provider 参数, + 它还必须支持在构造过程中保存历史记录。 + 2. History History is a big part of any provider—not only does the provider need to allow history to be @@ -87,7 +146,20 @@ to the user of your custom provider. and changing provider parameters, it must also support saving history as part of the construction process. - The Gemini provider handles this as shown: + + +Gemini provider 如下所示处理这个问题: + +The Gemini provider handles this as shown: + + + +你会在这段代码中注意到几件事: +* 使用 `ChangeNotifier` 来实现 `LlmProvider` 接口中 `Listenable` 的方法要求 +* 能够将初始历史记录作为构造函数参数传入 +* 当有新的用户提示词/LLM 响应对时通知监听器 +* 当历史记录被手动更改时通知监听器 +* 当历史记录更改时,使用新的历史记录创建新的聊天 ```dart class GeminiProvider extends LlmProvider with ChangeNotifier { @@ -100,11 +172,19 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { _history = history?.toList() ?? [], ... { ... } - final GenerativeModel _model; +final GenerativeModel _model; final List _history; ... - @override + + +本质上,自定义 provider 管理与底层 LLM 的单个聊天会话的历史记录。 +当历史记录更改时,底层聊天要么需要自动保持最新 +(就像 Dart 的 Gemini AI SDK 在你调用底层聊天特定方法时所做的那样), +要么需要手动重新创建 +(就像 Gemini provider 在手动设置历史记录时所做的那样)。 + +@override Stream sendMessageStream( String prompt, { Iterable attachments = const [], @@ -113,24 +193,53 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { final llmMessage = ChatMessage.llm(); _history.addAll([userMessage, llmMessage]); - final response = _generateStream( + + +3. 消息和附件 + +final response = _generateStream( prompt: prompt, attachments: attachments, contentStreamGenerator: _chat!.sendMessageStream, ); - yield* response.map((chunk) { + + +附件必须从 `LlmProvider` 类型暴露的标准 `ChatMessage` 类 +映射到底层 LLM 处理的任何内容。 +例如,Gemini provider 从 AI Toolkit 的 `ChatMessage` 类 +映射到 Dart 的 Gemini AI SDK 提供的 `Content` 类型, +如以下示例所示: + +yield* response.map((chunk) { llmMessage.append(chunk); return chunk; }); - notifyListeners(); + + +`_contentFrom` 方法在需要将用户提示词发送到底层 LLM 时被调用。 +每个 provider 都需要提供自己的映射。 + +notifyListeners(); } - @override + + +4. 调用 LLM + +@override Iterable get history => _history; - @override + + +你如何调用底层 LLM 来实现 `generateStream` 和 `sendMessageStream` 方法 +取决于它暴露的协议。 +AI Toolkit 中的 Gemini provider 处理配置和历史记录, +但对 `generateStream` 和 `sendMessageStream` 的调用 +最终都会调用 Dart 的 Gemini AI SDK 的 API: + +@override set history(Iterable history) { _history.clear(); _history.addAll(history); @@ -138,10 +247,22 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { notifyListeners(); } - ... + + +## 示例 + +... } ``` + + +[Gemini provider][] 和 [Vertex provider][] 的实现几乎相同, +为你自己的自定义 provider 提供了一个很好的起点。 +如果你想查看一个去除了所有底层 LLM 调用的示例 provider 实现, +请查看 [Echo 示例应用][Echo example app], +它只是将用户的提示词和附件格式化为 Markdown 作为响应发送回用户。 + You'll notice several things in this code: * The use of `ChangeNotifier` to implement the `Listenable` method requirements from the `LlmProvider` interface @@ -180,7 +301,7 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { (final LinkAttachment a) => FilePart(a.url), }; - static Content _contentFrom(ChatMessage message) => Content( +static Content _contentFrom(ChatMessage message) => Content( message.origin.isUser ? 'user' : 'model', [ TextPart(message.text ?? ''), @@ -208,7 +329,7 @@ end up in a call to an API from the Gemini AI SDK for Dart: class GeminiProvider extends LlmProvider with ChangeNotifier { ... - @override +@override Stream generateStream( String prompt, { Iterable attachments = const [], @@ -219,7 +340,7 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { contentStreamGenerator: (c) => _model.generateContentStream([c]), ); - @override +@override Stream sendMessageStream( String prompt, { Iterable attachments = const [], @@ -228,21 +349,21 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { final llmMessage = ChatMessage.llm(); _history.addAll([userMessage, llmMessage]); - final response = _generateStream( +final response = _generateStream( prompt: prompt, attachments: attachments, contentStreamGenerator: _chat!.sendMessageStream, ); - yield* response.map((chunk) { +yield* response.map((chunk) { llmMessage.append(chunk); return chunk; }); - notifyListeners(); +notifyListeners(); } - Stream _generateStream({ +Stream _generateStream({ required String prompt, required Iterable attachments, required Stream Function(Content) @@ -253,17 +374,17 @@ class GeminiProvider extends LlmProvider with ChangeNotifier { ...attachments.map(_partFrom), ]); - final response = contentStreamGenerator(content); +final response = contentStreamGenerator(content); yield* response .map((chunk) => chunk.text) .where((text) => text != null) .cast(); } - @override +@override Iterable get history => _history; - @override +@override set history(Iterable history) { _history.clear(); _history.addAll(history); diff --git a/src/content/ai-toolkit/feature-integration.md b/src/content/ai-toolkit/feature-integration.md index bf7725a18c..e31d137f68 100644 --- a/src/content/ai-toolkit/feature-integration.md +++ b/src/content/ai-toolkit/feature-integration.md @@ -1,5 +1,8 @@ --- +# title: Feature integration title: Feature integration +# description: > +# How to integrate with other Flutter features. description: > How to integrate with other Flutter features. prev: @@ -16,6 +19,12 @@ 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. @@ -37,23 +46,57 @@ additional functionality: * **Rerouting prompts**: Debug, log, or reroute messages meant for the provider to track down issues or route prompts dynamically. + + +* **欢迎消息**:向用户显示初始问候语。 +* **建议提示词**:为用户提供预定义的提示词来引导交互。 +* **系统指令**:为 LLM 提供特定输入以影响其响应。 +* **禁用附件和音频输入**:移除聊天 UI 的可选部分。 +* **管理取消或错误行为**:更改用户取消或 LLM 错误的行为。 +* **管理历史记录**:每个 LLM provider 都允许管理聊天历史, + 这对于清除历史、动态更改历史以及在会话之间存储历史非常有用。 +* **聊天序列化/反序列化**:在应用会话之间存储和检索对话。 +* **自定义响应 widget**:引入专门的 UI 组件来展示 LLM 响应。 +* **自定义样式**:定义独特的视觉样式以匹配聊天外观与整体应用。 +* **无 UI 聊天**:直接与 LLM provider 交互,不影响用户当前的聊天会话。 +* **自定义 LLM provider**:构建你自己的 LLM provider, + 以便将聊天与你自己的模型后端集成。 +* **重路由提示词**:调试、记录或重路由发送给 provider 的消息, + 以追踪问题或动态路由提示词。 + [`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) + + +![欢迎消息示例](/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}); - @override +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( @@ -69,28 +112,54 @@ class ChatPage extends StatelessWidget { } ``` + + +有关设置欢迎消息的完整示例,请查看 [welcome 示例][welcome example]。 + To see a complete example of setting the welcome message, check out the [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: + + +![建议提示词示例](/assets/images/docs/ai-toolkit/example-of-suggested-prompts.png) + ![Example suggested prompts](/assets/images/docs/ai-toolkit/example-of-suggested-prompts.png) + + +建议仅在没有现有聊天历史时显示。 +点击其中一个会将文本复制到用户的提示词编辑区域。 +要设置建议列表,请使用 `suggestions` 参数构造 `LlmChatView`: + The suggestions are only shown when there is no existing 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 示例][suggestions example]。 + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); - @override +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( @@ -110,13 +179,30 @@ class ChatPage extends StatelessWidget { } ``` + + +## LLM 指令 + To see a complete example of setting up suggestions for the user, take a look at the [suggestions example][]. + + +要根据应用的需求优化 LLM 的响应,你需要给它指令。 +例如,[recipes 示例应用][recipes example app]使用 +`GenerativeModel` 类的 `systemInstructions` 参数 +来定制 LLM,使其专注于根据用户的指令提供食谱: + [suggestions example]: {{site.github}}/flutter/ai/blob/main/example/lib/suggestions/suggestions.dart ## LLM instructions + + +设置系统指令对每个 provider 是独特的; +`GeminiProvider` 和 `VertexProvider` +都允许你通过 `systemInstruction` 参数提供它们。 + 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 +210,13 @@ For example, the [recipes example app][] uses the class to tailor the LLM to focus on delivering recipes based on the user's instructions: + + +请注意,在这种情况下,我们将用户偏好作为传递给 +`LlmChatView` 构造函数的 LLM provider 创建过程的一部分。 +每次用户更改其偏好时,我们都会在创建过程中设置指令。 +recipes 应用允许用户使用 scaffold 上的抽屉来更改他们的食物偏好: + ```dart class _HomePageState extends State { ... @@ -149,10 +242,19 @@ You should keep things casual and friendly. You may generate multiple recipes in } ``` + + +![优化提示词的示例](/assets/images/docs/ai-toolkit/setting-food-preferences.png) + Setting system instructions is unique to each provider; both the `GeminiProvider` and the `VertexProvider` allow you to provide them through the `systemInstruction` parameter. + + +每当用户更改他们的食物偏好时, +recipes 应用会创建一个新模型来使用新的偏好: + 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 +262,24 @@ 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: + + +## 禁用附件和音频输入 + ![Example of refining prompt](/assets/images/docs/ai-toolkit/setting-food-preferences.png) + + +如果你想禁用附件(**+** 按钮)或音频输入(麦克风按钮), +可以使用 `LlmChatView` 构造函数的 `enableAttachments` 和 `enableVoiceNotes` 参数: + Whenever the user changes their food preferences, the recipes app creates a new model to use the new preferences: + + +这两个标志默认为 `true`。 + ```dart class _HomePageState extends State { ... @@ -178,19 +293,36 @@ 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: + + +默认情况下,当用户取消 LLM 请求时,LLM 的响应会附加字符串 "CANCEL", +并弹出消息提示用户已取消请求。 +同样,当发生 LLM 错误(如网络连接断开)时, +LLM 的响应会附加字符串 "ERROR",并弹出警告对话框显示错误详情。 + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); - @override +@override Widget build(BuildContext context) { // ... - return Scaffold( + + +你可以使用 `LlmChatView` 的 `cancelMessage`、`errorMessage`、 +`onCancelCallback` 和 `onErrorCallback` 参数来覆盖取消和错误行为。 +例如,以下代码替换了默认的取消处理行为: + +return Scaffold( appBar: AppBar(title: const Text('Restricted Chat')), body: LlmChatView( // ... @@ -202,32 +334,66 @@ class ChatPage extends StatelessWidget { } ``` + + +你可以覆盖这些参数中的任何一个或全部, +`LlmChatView` 将为你未覆盖的内容使用默认值。 + Both of these flags default to `true`. + + +## 管理历史记录 + ## Manage cancel or error behavior + + +[定义所有可以接入聊天视图的 LLM provider 的标准接口][providerIF] +包括获取和设置 provider 历史记录的能力: + 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. + + +当 provider 的历史记录发生变化时, +它会调用 `Listenable` 基类暴露的 `notifyListener` 方法。 +这意味着你可以使用 `add` 和 `remove` 方法手动订阅/取消订阅, +或使用它来构造 `ListenableBuilder` 类的实例。 + 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: + + +`generateStream` 方法调用底层 LLM 而不影响历史记录。 +调用 `sendMessageStream` 方法会在响应完成时 +向 provider 的历史记录添加两条新消息来更改历史记录—— +一条是用户消息,一条是 LLM 响应。 +聊天视图在处理用户的聊天提示词时使用 `sendMessageStream`, +在处理用户的语音输入时使用 `generateStream`。 + ```dart class ChatPage extends StatelessWidget { // ... - void _onCancel(BuildContext context) { +void _onCancel(BuildContext context) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Chat cancelled'))); } - @override + + +要查看或设置历史记录,你可以访问 `history` 属性: + +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( @@ -239,15 +405,35 @@ class ChatPage extends StatelessWidget { } ``` + + +访问 provider 历史记录的能力在重新创建 provider 同时保持历史记录时也很有用: + You can override any or all of these parameters and the `LlmChatView` will use its defaults for anything you don't override. + + +`_createProvider` 方法使用前一个 provider 的历史记录_和_新的用户偏好创建一个新的 provider。 +对用户来说这是无缝的;他们可以继续聊天, +但现在 LLM 会根据他们的新食物偏好给出响应。 +例如: + ## Manage history + + +要查看历史记录的实际应用, +请查看 [recipes 示例应用][recipes example app]和 [history 示例应用][history example app]。 + 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: + + +## 聊天序列化/反序列化 + ```dart abstract class LlmProvider implements Listenable { Stream generateStream( @@ -255,16 +441,28 @@ abstract class LlmProvider implements Listenable { Iterable attachments, }); - Stream sendMessageStream( +Stream sendMessageStream( String prompt, { Iterable attachments, }); - Iterable get history; + + +要在应用会话之间保存和恢复聊天历史, +需要能够序列化和反序列化每个用户提示词(包括附件)和每个 LLM 响应。 +这两种消息(用户提示词和 LLM 响应) +都在 `ChatMessage` 类中公开。 +可以使用每个 `ChatMessage` 实例的 `toJson` 方法来实现序列化。 + +Iterable get history; set history(Iterable history); } ``` + + +同样,要反序列化,请使用 `ChatMessage` 类的静态 `fromJson` 方法: + [providerIF]: {{site.pub-api}}/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html When the history for a provider changes, @@ -273,6 +471,12 @@ 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. + + +为确保序列化时的快速响应,我们建议每个用户消息只写入一次。 +否则,用户必须等待你的应用每次都写入所有消息, +而在处理二进制附件时,这可能需要一段时间。 + 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 +485,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. + + +要查看实际应用,请查看 [history 示例应用][history example app]。 + To see or set the history, you can access the `history` property: + + +## 自定义响应 widget + ```dart void _clearHistory() => _provider.history = []; ``` @@ -290,6 +502,15 @@ 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: + + +默认情况下,聊天视图显示的 LLM 响应是格式化的 Markdown。 +但在某些情况下,你想创建一个自定义 widget 来显示特定于你应用并与之集成的 LLM 响应。 +例如,当用户在 [recipes 示例应用][recipes example app]中请求食谱时, +LLM 响应被用于创建一个特定于显示食谱的 widget, +就像应用的其他部分一样,并提供一个**添加**按钮, +以便用户可以将食谱添加到他们的数据库中: + ```dart class _HomePageState extends State { ... @@ -311,6 +532,9 @@ new food preferences into account. For example: + +![添加食谱按钮](/assets/images/docs/ai-toolkit/add-recipe-button.png) + ```dart class _HomePageState extends State { ... @@ -327,11 +551,20 @@ class _HomePageState extends State { To see history in action, check out the [recipes example app][] and the [history example app][]. + + +这是通过设置 `LlmChatView` 构造函数的 `responseBuilder` 参数来实现的: + [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 + + +在这个特定的示例中,`RecipeResponseView` widget +使用 LLM provider 的响应文本构造,并使用它来实现其 `build` 方法: + 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,18 +574,32 @@ are exposed in the `ChatMessage` class. Serialization can be accomplished by using the `toJson` method of each `ChatMessage` instance. + + +这段代码解析文本以从 LLM 中提取介绍文本和食谱, +将它们与**添加食谱**按钮捆绑在一起显示,以替代 Markdown。 + ```dart Future _saveHistory() async { // get the latest history final history = _provider.history.toList(); - // write the new messages +// write the new messages for (var i = 0; i != history.length; ++i) { // skip if the file already exists final file = await _messageFile(i); if (file.existsSync()) continue; - // write the new message to disk + + +请注意,我们将 LLM 响应解析为 JSON。 +通常会将 provider 设置为 JSON 模式, +并提供 schema 来限制其响应的格式,以确保我们得到可以解析的内容。 +每个 provider 以自己的方式暴露此功能, +但 `GeminiProvider` 和 `VertexProvider` 类都通过 `GenerationConfig` 对象启用此功能, +recipes 示例使用如下: + +// write the new message to disk final map = history[i].toJson(); final json = JsonEncoder.withIndent(' ').convert(map); await file.writeAsString(json); @@ -360,9 +607,21 @@ Future _saveHistory() async { } ``` + + +这段代码通过将 `responseMimeType` 参数设置为 `'application/json'` +并将 `responseSchema` 参数设置为定义你准备解析的 JSON 结构的 `Schema` 类实例 +来初始化 `GenerationConfig` 对象。 +此外,在系统指令中请求 JSON 并提供该 JSON schema 的描述也是一种好做法, +我们在这里就是这样做的。 + Likewise, to deserialize, use the static `fromJson` method of the `ChatMessage` class: + + +要查看实际应用,请查看 [recipes 示例应用][recipes example app]。 + ```dart Future _loadHistory() async { // read the history from disk @@ -371,15 +630,26 @@ Future _loadHistory() async { final file = await _messageFile(i); if (!file.existsSync()) break; - final map = jsonDecode(await file.readAsString()); +final map = jsonDecode(await file.readAsString()); history.add(ChatMessage.fromJson(map)); } - // set the history on the controller + + +## 自定义样式 + +// set the history on the controller _provider.history = history; } ``` + + +聊天视图开箱即用,提供了一组默认样式, +包括背景、文本字段、按钮、图标、建议等。 +你可以通过使用 `LlmChatView` 构造函数的 `style` 参数 +设置你自己的样式来完全自定义这些样式: + To ensure fast turnaround when serializing, we recommend only writing each user message once. Otherwise, the user must wait for your app to @@ -387,12 +657,29 @@ write every message every time and, in the face of binary attachments, that could take a while. + + +例如,[custom styles 示例应用][custom-ex] +使用此功能实现了一个万圣节主题的应用: + To see this in action, check out the [history example app][]. + + +![万圣节主题演示应用](/assets/images/docs/ai-toolkit/demo-app.png) + [history example app]: {{site.github}}/flutter/ai/blob/main/example/lib/history/history.dart ## Custom response widgets + + +有关 `LlmChatViewStyle` 类中可用样式的完整列表, +请查看[参考文档][reference documentation]。 +要查看自定义样式的实际应用, +除了 [custom styles 示例][custom-ex]外, +还可以查看 [dark mode 示例][dark mode example]和[演示应用][demo app]。 + 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 +691,27 @@ 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: + + +## 无 UI 聊天 + ![Add recipe button](/assets/images/docs/ai-toolkit/add-recipe-button.png) + + +你不必使用聊天视图来访问底层 provider 的功能。 +除了能够简单地使用它提供的任何专有接口调用它之外, +你还可以使用 [LlmProvider 接口][LlmProvider interface]。 + This is accomplished by setting the `responseBuilder` parameter of the `LlmChatView` constructor: + + +例如,recipes 示例应用在编辑食谱的页面上提供了一个魔法按钮。 +该按钮的目的是使用你当前的食物偏好更新数据库中的现有食谱。 +按下按钮可以让你预览推荐的更改,并决定是否要应用它们: + ```dart LlmChatView( provider: _provider, @@ -423,31 +726,53 @@ In this particular example, the `RecipeReponseView` widget is constructed with the LLM provider's response text and uses that to implement its `build` method: + + +![用户决定是否更新数据库中的食谱](/assets/images/docs/ai-toolkit/apply-changes-decision.png) + ```dart class RecipeResponseView extends StatelessWidget { const RecipeResponseView(this.response, {super.key}); final String response; - @override +@override Widget build(BuildContext context) { final children = []; String? finalText; - // created with the response from the LLM as the response streams in, so + + +Edit Recipe 页面不是使用应用聊天部分使用的同一个 provider +(这会将多余的用户消息和 LLM 响应插入用户的聊天历史中), +而是创建自己的 provider 并直接使用它: + +// created with the response from the LLM as the response streams in, so // many not be a complete response yet try { final map = jsonDecode(response); final recipesWithText = map['recipes'] as List; finalText = map['text'] as String?; - for (final recipeWithText in recipesWithText) { + + +对 `sendMessageStream` 的调用会在 provider 的历史记录中创建条目, +但由于它没有与聊天视图关联,因此不会显示。 +如果方便的话,你也可以通过调用 `generateStream` 来实现同样的目的, +这允许你重用现有的 provider 而不影响聊天历史。 + +for (final recipeWithText in recipesWithText) { // extract the text before the recipe final text = recipeWithText['text'] as String?; if (text != null && text.isNotEmpty) { children.add(MarkdownBody(data: text)); } - // extract the recipe + + +要查看实际应用, +请查看 recipes 示例的 [Edit Recipe 页面][Edit Recipe page]。 + +// extract the recipe final json = recipeWithText['recipe'] as Map; final recipe = Recipe.fromJson(json); children.add(const Gap(16)); @@ -460,7 +785,11 @@ class RecipeResponseView extends StatelessWidget { ], )); - // add a button to add the recipe to the list + + +## 重路由提示词 + +// add a button to add the recipe to the list children.add(const Gap(16)); children.add(OutlinedButton( onPressed: () => RecipeRepository.addNewRecipe(recipe), @@ -472,9 +801,22 @@ class RecipeResponseView extends StatelessWidget { debugPrint('Error parsing response: $e'); } - ... - return Column( + +如果你想调试、记录或操作聊天视图与底层 provider 之间的连接, +可以通过实现 [`LlmStreamGenerator`][] 函数来完成。 +然后将该函数通过 `messageSender` 参数传递给 `LlmChatView`: + +... + + + +这个示例记录了来回传递的用户提示词和 LLM 响应。 +当提供函数作为 `messageSender` 时,调用底层 provider 是你的责任。 +如果你不调用,它就不会收到消息。 +此功能允许你执行高级操作,如动态路由到 provider 或检索增强生成 (RAG)。 + +return Column( crossAxisAlignment: CrossAxisAlignment.start, children: children, ); @@ -482,6 +824,10 @@ class RecipeResponseView extends StatelessWidget { } ``` + + +要查看实际应用,请查看 [logging 示例应用][logging example app]。 + 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. @@ -499,7 +845,7 @@ recipes example uses as follows: class _HomePageState extends State { ... - // create a new provider with the given history and the current settings +// create a new provider with the given history and the current settings LlmProvider _createProvider([List? history]) => GeminiProvider( ... model: GenerativeModel( @@ -616,11 +962,11 @@ class _EditRecipePageState extends State { var response = await stream.join(); final json = jsonDecode(response); - try { +try { final modifications = json['modifications']; final recipe = Recipe.fromJson(json['recipe']); - if (!context.mounted) return; +if (!context.mounted) return; final accept = await showDialog( context: context, builder: (context) => AlertDialog( @@ -680,7 +1026,7 @@ You then pass that function to the `LlmChatView` in the class ChatPage extends StatelessWidget { final _provider = GeminiProvider(...); - @override +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( @@ -689,7 +1035,7 @@ class ChatPage extends StatelessWidget { ), ); - Stream _logMessage( +Stream _logMessage( String prompt, { required Iterable attachments, }) async* { @@ -698,17 +1044,17 @@ class ChatPage extends StatelessWidget { debugPrint('## Prompt\n$prompt'); debugPrint('## Attachments\n${attachments.map((a) => a.toString())}'); - // forward the message on to the provider +// forward the message on to the provider final response = _provider.sendMessageStream( prompt, attachments: attachments, ); - // log the response +// log the response final text = await response.join(); debugPrint('## Response\n$text'); - // return it +// return it yield text; } } diff --git a/src/content/ai-toolkit/index.md b/src/content/ai-toolkit/index.md index 3a52c2834b..bfffa6143f 100644 --- a/src/content/ai-toolkit/index.md +++ b/src/content/ai-toolkit/index.md @@ -1,5 +1,9 @@ --- +# title: AI Toolkit title: AI Toolkit +# description: > +# Learn how to add the AI Toolkit chatbot +# to your Flutter application. description: > Learn how to add the AI Toolkit chatbot to your Flutter application. @@ -10,6 +14,10 @@ next: Hello and welcome to the Flutter AI Toolkit! + + +欢迎使用 Flutter AI Toolkit! + :::note These pages are now out of date. They will be updated soon but, in the meantime, be aware that the @@ -27,8 +35,21 @@ 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 Toolkit 是一组与 AI 聊天相关的 widget, +可让你轻松地将 AI 聊天窗口添加到 Flutter 应用中。 +AI Toolkit 围绕抽象的 LLM provider API 构建, +可以轻松更换你希望聊天 provider 使用的 LLM provider。 +它开箱即支持两个 LLM provider 集成: +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. @@ -47,19 +68,49 @@ integrations: Google Gemini AI and Firebase Vertex AI. * **Cross-platform support**: Compatible with Android, iOS, web, and macOS platforms. + + +* **多轮对话**:在多次交互中保持上下文。 +* **流式响应**:在生成 AI 响应时实时显示。 +* **富文本显示**:支持聊天消息中的格式化文本。 +* **语音输入**:允许用户使用语音输入提示词。 +* **多媒体附件**:支持发送和接收各种媒体类型。 +* **自定义样式**:提供丰富的自定义选项以匹配你的应用设计。 +* **聊天序列化/反序列化**:在应用会话之间存储和检索对话。 +* **自定义响应 widget**:引入专门的 UI 组件来展示 LLM 响应。 +* **可插拔 LLM 支持**:实现简单接口即可接入你自己的 LLM。 +* **跨平台支持**:兼容 Android、iOS、Web 和 macOS 平台。 + ## Online Demo + + +## 在线演示 + Here's the online demo hosting the AI Toolkit: + + +以下是托管 AI Toolkit 的在线演示: + 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: + + +
      +
    1. 安装 + ```yaml dependencies: flutter_ai_toolkit: ^latest_version @@ -102,6 +162,10 @@ To use Google Gemini AI, Be careful not to check this key into your source code repository to prevent unauthorized access. + + +将以下依赖项添加到你的 `pubspec.yaml` 文件中: + [obtain an API key]: https://aistudio.google.com/app/apikey You'll also need to choose a specific Gemini model name @@ -109,19 +173,35 @@ 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]. -[models]: https://ai.google.dev/gemini-api/docs/models/gemini +
    2. Gemini AI 配置 + +[models]: https://ai.google.dev/gemini-api/docs/models/gemini + ```dart import 'package:google_generative_ai/google_generative_ai.dart'; import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart'; // ... app stuff here + + +该工具包支持 Google Gemini AI 和 Firebase Vertex AI 作为 LLM provider。 +要使用 Google Gemini AI, +请从 Gemini AI Studio [获取 API 密钥][obtain an API key]。 +请注意不要将此密钥提交到源代码仓库中,以防止未经授权的访问。 + class ChatPage extends StatelessWidget { const ChatPage({super.key}); - @override + + +你还需要选择特定的 Gemini 模型名称来创建 Gemini 模型实例。 +以下示例使用 `gemini-2.0-flash`, +但你可以从[不断扩展的模型列表][models]中选择。 + +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( @@ -136,6 +216,13 @@ class ChatPage extends StatelessWidget { } ``` + + +`GenerativeModel` 类来自 `google_generative_ai` package。 +AI Toolkit 在此 package 基础上构建了 `GeminiProvider`, +它将 Gemini AI 接入 `LlmChatView`, +这是一个与用户进行基于 LLM 的聊天对话的顶级 widget。 + The `GenerativeModel` class comes from the `google_generative_ai` package. The AI Toolkit builds on top of this package with @@ -143,8 +230,16 @@ the `GeminiProvider`, which plugs Gemini AI into the `LlmChatView`, the top-level widget that provides an LLM-based chat conversation with your users. + + +有关完整示例,请查看 GitHub 上的 [`gemini.dart`][]。 + For a complete example, check out [`gemini.dart`][] on GitHub. + + +
    3. Vertex AI 配置 + [`gemini.dart`]: {{site.github}}/flutter/ai/blob/main/example/lib/gemini/gemini.dart
    4. @@ -159,18 +254,38 @@ 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, +请按照[使用 Vertex AI in Firebase SDK 开始使用 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'; @@ -178,25 +293,51 @@ import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart'; // ... other imports + + +在 Flutter 应用中正确初始化 Firebase 后, +你现在可以创建 Vertex provider 的实例: + import 'firebase_options.dart'; // from `flutterfire config` + + +`FirebaseVertexAI` 类来自 `firebase_vertexai` package。 +AI Toolkit 构建了 `VertexProvider` 类来向 `LlmChatView` 暴露 Vertex AI。 +请注意,你需要提供模型名称([你有多个选项][options]可供选择), +但不需要提供 API 密钥。 +所有这些都作为 Firebase 项目的一部分处理。 + void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); runApp(const App()); } + + +有关完整示例,请查看 GitHub 上的 [vertex.dart][]。 + // ...app stuff here ``` + + +
    5. 设置设备权限 + With Firebase properly initialized in your Flutter app, you're now ready to create an instance of the Vertex provider: + + +要让用户能够使用语音输入和媒体附件等功能, +请确保你的应用具有必要的权限: + ```dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); - @override +@override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), // create the chat view, passing in the Vertex provider @@ -212,6 +353,11 @@ class ChatPage extends StatelessWidget { ``` + +* **网络访问:** + 要在 macOS 上启用网络访问, + 请将以下内容添加到你的 `*.entitlements` 文件中: + The `FirebaseVertexAI` class comes from the `firebase_vertexai` package. The AI Toolkit builds the `VertexProvider` class to expose @@ -221,8 +367,23 @@ 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. + + +要在 Android 上启用网络访问, + 请确保你的 `AndroidManifest.xml` 文件包含以下内容: + For a complete example, check out [vertex.dart][] on GitHub. + + +* **麦克风访问**:按照 + [record package 的权限设置说明][record]进行配置。 +* **文件选择**:按照 [file_selector 插件的说明][file]操作。 +* **图片选择**:要在设备上拍照_或_从设备中选择图片, + 请参阅 [image_picker 插件的安装说明][image_picker]。 +* **Web 拍照**:要在 Web 上拍照, + 请按照 [camera 插件的设置说明][camera]配置应用。 + [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
    6. @@ -232,11 +393,22 @@ For a complete example, check out [vertex.dart][] on GitHub. 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: - ```xml + + +要执行仓库中的[示例应用][example apps], +你需要替换 `example/lib/gemini_api_key.dart` +和 `example/lib/firebase_options.dart` 文件, +这两个文件都只是占位符。它们是启用 `example/lib` 文件夹中示例项目所必需的。 + +```xml ... @@ -246,10 +418,17 @@ attachments, ensure that your app has the necessary permissions: ``` - To enable network access on Android, +To enable network access on Android, ensure that your `AndroidManifest.xml` file contains the following: - ```xml + + +大多数示例应用依赖于 Gemini API 密钥, +因此要使它们正常工作,你需要在 +`example/lib/gemini_api_key.dart` 文件中填入你的 API 密钥。 +你可以在 [Gemini AI Studio][] 中获取 API 密钥。 + +```xml ... @@ -265,6 +444,15 @@ attachments, ensure that your app has the necessary permissions: * **Web photo**: To take a picture on the web, configure the app according to the [camera plugin's setup instructions][camera]. + + +要使用 [Vertex AI 示例应用][vertex-ex], +请将你的 Firebase 配置详情放入 +`example/lib/firebase_options.dart` 文件中。 +你可以使用 `flutterfire CLI` 工具完成此操作, +如[将 Firebase 添加到你的 Flutter 应用][add-fb]文档中所述, +**需要在 `example` 目录内执行**。 + [camera]: {{site.pub-pkg}}/camera#setup [file]: {{site.pub-pkg}}/file_selector#usage [image_picker]: {{site.pub-pkg}}/image_picker#installation @@ -274,12 +462,24 @@ 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. + + +在使用此 package 的过程中, +请[记录问题和功能请求][file-issues], +并提交你希望[贡献的代码][submit]。 +我们需要你的反馈和贡献, +以确保 AI Toolkit 对你的实际应用尽可能强大和有用。 + **gemini_api_key.dart** Most of the example apps rely on a Gemini API key, diff --git a/src/content/ai-toolkit/user-experience.md b/src/content/ai-toolkit/user-experience.md index e1196aa548..b37fc2bd75 100644 --- a/src/content/ai-toolkit/user-experience.md +++ b/src/content/ai-toolkit/user-experience.md @@ -1,5 +1,8 @@ --- +# title: User experience title: User experience +# description: > +# How the user will experience the AI Toolkit in your app. description: > How the user will experience the AI Toolkit in your app. prev: @@ -16,6 +19,12 @@ 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 Toolkit 提供的交互式聊天体验的入口点。 +托管 `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 @@ -30,161 +39,415 @@ any additional code to use: * **Material and Cupertino**: Adapts to the best practices of both design languages. + + +* **多行文本输入**:允许用户粘贴长文本输入或在输入时插入新行。 +* **语音输入**:允许用户使用语音输入提示词,方便使用。 +* **多媒体输入**:允许用户拍照并发送图片和其他文件类型。 +* **图片缩放**:允许用户放大查看图片缩略图。 +* **复制到剪贴板**:允许用户将消息或 LLM 响应的文本复制到剪贴板。 +* **消息编辑**:允许用户编辑最近的消息以重新提交给 LLM。 +* **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 * **Desktop**: Press **Enter** or tap the **Submit** button + + +* **移动端**:点击**提交**按钮 +* **Web**:按 **Enter** 键或点击**提交**按钮 +* **桌面端**:按 **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 * **Desktop**: Press `Ctrl+Enter` or `Opt/Alt+Enter` + + +* **移动端**:点击虚拟键盘上的 Return 键 +* **Web**:不支持 +* **桌面端**:按 `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) + + +![在桌面端输入文本的截图](/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) + + +![在移动端输入文本的截图](/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) + + +![输入文本的截图](/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) + + +![语音输入的截图](/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) + + +![包含 "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 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) + + +![4 个图标的截图](/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) + + +![自拍图片](/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) + + +![从图库下载图片](/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) + + +![图片缩略图](/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) + + +![放大的图片](/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) + + +![复制到剪贴板](/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) + + +![按下复制按钮](/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) + + +![长按查看复制按钮](/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) + + +![如何编辑提示词](/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) + + +![如何访问编辑菜单](/assets/images/docs/ai-toolkit/accessing-edit-menu.png) + Once the user taps the **Edit** button, they enter Editing mode, which removes both the user's last prompt and the LLM's last response from the chat history, puts the text of the prompt into the text field, and provides an Editing indicator: + + +用户点击**编辑**按钮后,将进入编辑模式, +该模式会从聊天历史中移除用户的上一条提示词和 LLM 的上一条响应, +将提示词的文本放入文本字段中, +并提供一个编辑指示器: + ![How to exit editing mode](/assets/images/docs/ai-toolkit/how-to-exit-editing-mode.png) + + +![如何退出编辑模式](/assets/images/docs/ai-toolkit/how-to-exit-editing-mode.png) + In Editing mode, the user can edit the prompt as they choose and submit it to have the LLM produce a response as normal. Or, if they change their mind, they can tap the **X** near the Editing indicator to cancel their edit and restore their previous LLM response. + + +在编辑模式下,用户可以随意编辑提示词并提交, +让 LLM 像正常一样生成响应。 +或者,如果用户改变主意,可以点击编辑指示器旁边的 **X** +来取消编辑并恢复之前的 LLM 响应。 + ## Material and Cupertino + + +## Material 和 Cupertino + When the `LlmChatView` widget is hosted in a [Material app][], it uses facilities provided by the Material design language, such as Material's [`TextField`][]. Likewise, when hosted in a [Cupertino app][], it uses those facilities, such as [`CupertinoTextField`][]. + + +当 `LlmChatView` widget 托管在 [Material 应用][Material app]中时, +它使用 Material 设计语言提供的功能, +例如 Material 的 [`TextField`][]。 +同样,当托管在 [Cupertino 应用][Cupertino app]中时, +它使用这些功能,例如 [`CupertinoTextField`][]。 + ![Cupertino example app](/assets/images/docs/ai-toolkit/cupertino-chat-app.png) + + +![Cupertino 示例应用](/assets/images/docs/ai-toolkit/cupertino-chat-app.png) + However, while the chat view supports both the Material and 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/contribute/docs/cli.md b/src/content/contribute/docs/cli.md index 0767753dfe..aba820c175 100644 --- a/src/content/contribute/docs/cli.md +++ b/src/content/contribute/docs/cli.md @@ -1,6 +1,11 @@ --- +# title: Docs command-line tool title: Docs command-line tool +# shortTitle: Tool shortTitle: Tool +# description: >- +# Learn about the dash_site CLI tool that is used to +# develop, test, and serve the Dart and Flutter documentation sites. description: >- Learn about the dash_site CLI tool that is used to develop, test, and serve the Dart and Flutter documentation sites. @@ -16,8 +21,17 @@ This document is a work in progress. The site's CLI tool can be accessed by running `dart run dash_site` in the repository's root directory. + + +可以通过在仓库根目录中运行 +`dart run dash_site` 来访问网站的 CLI 工具。 + ## Commands + + +## 命令 + ### `analyze-dart` ### `build` diff --git a/src/content/contribute/docs/code-blocks.md b/src/content/contribute/docs/code-blocks.md index 834f2e5356..c28185bee9 100644 --- a/src/content/contribute/docs/code-blocks.md +++ b/src/content/contribute/docs/code-blocks.md @@ -1,5 +1,9 @@ --- +# title: Code blocks title: Code blocks +# description: >- +# Learn about Markdown code blocks on the Dart and Flutter documentation sites +# and custom functionality and configurability that they support. description: >- Learn about Markdown code blocks on the Dart and Flutter documentation sites and custom functionality and configurability that they support. @@ -14,14 +18,38 @@ This document is a work in progress. ## Title + + +## 标题 + ## Tags + + +## 标签 + ## Line numbers + + +## 行号 + ## Highlight lines + + +## 高亮行 + ## Highlight spans + + +## 高亮区域 + ## Diffing + + +## 差异对比 + ## DartPad diff --git a/src/content/contribute/docs/components.md b/src/content/contribute/docs/components.md index 0c1a201fed..53d6b2ab61 100644 --- a/src/content/contribute/docs/components.md +++ b/src/content/contribute/docs/components.md @@ -1,5 +1,9 @@ --- +# title: Custom components title: Custom components +# description: >- +# Learn about custom components that the +# Dart and Flutter documentation sites support for displaying content. description: >- Learn about custom components that the Dart and Flutter documentation sites support for displaying content. @@ -14,4 +18,12 @@ This document is a work in progress. ## Tabs + + +## 标签页 + ## YouTube embed + + + +## YouTube 嵌入 diff --git a/src/content/contribute/docs/excerpts.md b/src/content/contribute/docs/excerpts.md index ae8258f1c4..540bc61257 100644 --- a/src/content/contribute/docs/excerpts.md +++ b/src/content/contribute/docs/excerpts.md @@ -1,5 +1,9 @@ --- +# title: Code excerpts title: Code excerpts +# description: >- +# Learn about adding and using code excerpts +# in the Dart and Flutter documentation sites. description: >- Learn about adding and using code excerpts in the Dart and Flutter documentation sites. @@ -14,7 +18,16 @@ This document is a work in progress. The source of code excerpts is in the root `/examples` directory. + + +代码摘录的源文件位于根目录 `/examples` 中。 + To learn how to use code excerpts, check out the [excerpter tool README][]. + + +要了解如何使用代码摘录, +请查看 [excerpter 工具 README][excerpter tool README]。 + [excerpter tool README]: https://github.com/dart-lang/site-shared/blob/main/pkgs/excerpter/README.md diff --git a/src/content/contribute/docs/frontmatter.md b/src/content/contribute/docs/frontmatter.md index 1cc96f4914..cd3805e376 100644 --- a/src/content/contribute/docs/frontmatter.md +++ b/src/content/contribute/docs/frontmatter.md @@ -1,5 +1,9 @@ --- +# title: Frontmatter title: Frontmatter +# description: >- +# Learn about the YAML frontmatter each document on +# the Dart and Flutter documentation sites starts with. description: >- Learn about the YAML frontmatter each document on the Dart and Flutter documentation sites starts with. @@ -16,8 +20,18 @@ Each Markdown document on the site starts with [YAML][] frontmatter. You can edit the frontmatter to customize the generated page and its metadata. + + +网站上的每个 Markdown 文档都以 [YAML][] frontmatter 开头。 +你可以编辑 frontmatter 来自定义 +生成的页面及其元数据。 + As a minimum, a `title` and `description` are required for each page. + + +每个页面至少需要 `title` 和 `description`。 + ```yaml --- title: Build a Flutter app @@ -30,11 +44,24 @@ description: >- ## Access frontmatter data in templates + + +## 在模板中访问 frontmatter 数据 + Layouts, templates, and source files can access values from the frontmatter as top-level data with templating. + + +布局、模板和源文件可以通过模板语法 +将 frontmatter 中的值作为顶级数据访问。 + For example, the following frontmatter sets a `showData` variable to `value`: + + +例如,以下 frontmatter 将 `showData` 变量设置为 `value`: + ```yaml --- # ... @@ -44,6 +71,10 @@ showDate: false The configured value of `showDate` can be accessed in templates: + + +`showDate` 的配置值可以在模板中访问: + ```md Should show date: {{showDate}} {% if showDate %} @@ -54,12 +85,27 @@ Should show date: {{showDate}} If you add a new value to the frontmatter, prefer using `lowerCamelCase` for the name. + + +如果你向 frontmatter 添加新值, +请优先使用 `lowerCamelCase` 作为名称。 + ## Frontmatter fields + + +## Frontmatter 字段 + Besides `title` and `description`, the sites support a variety of other optional fields to customize page generation. + + +除了 `title` 和 `description`, +网站还支持各种其他可选字段 +来自定义页面生成。 + ### `title` ### `description` diff --git a/src/content/contribute/docs/index.md b/src/content/contribute/docs/index.md index 0a3a41ca46..829c0065ce 100644 --- a/src/content/contribute/docs/index.md +++ b/src/content/contribute/docs/index.md @@ -1,6 +1,10 @@ --- +# title: Contribute to the docs title: Contribute to the docs +# shortTitle: Docs shortTitle: Docs +# description: >- +# Learn about contributing to the Dart and Flutter documentation sites. description: >- Learn about contributing to the Dart and Flutter documentation sites. sitemap: false @@ -14,6 +18,10 @@ This document is a work in progress. ## Contribution guides + + +## 贡献指南 + - [Writing](/contribute/docs/writing) - [Markdown](/contribute/docs/markdown) - [Frontmatter](/contribute/docs/frontmatter) @@ -24,70 +32,181 @@ This document is a work in progress. - [Releases](/contribute/docs/releases) - [Command-line tool](/contribute/docs/cli) + + +- [写作](/contribute/docs/writing) +- [Markdown](/contribute/docs/markdown) +- [Frontmatter](/contribute/docs/frontmatter) +- [代码块](/contribute/docs/code-blocks) +- [代码摘录](/contribute/docs/excerpts) +- [组件](/contribute/docs/components) +- [侧边导航](/contribute/docs/sidenav) +- [版本发布](/contribute/docs/releases) +- [命令行工具](/contribute/docs/cli) + ## Repository layout + + +## 仓库布局 + - `.github/` - Configuration for GitHub [actions][gh-actions], + + +GitHub [actions][gh-actions]、 + issue 和 PR [模板][gh-templates]以及 [dependabot][] 的配置。 +- `cloud_build/` + +Configuration for GitHub [actions][gh-actions], issue and PR [templates][gh-templates], and [dependabot][]. - `cloud_build/` - Configuration for Google [Cloud Build][] that is used for staging + + +用于暂存和部署网站的 Google [Cloud Build][] 配置。 +- `diagrams/` + +Configuration for Google [Cloud Build][] that is used for staging and deploying the site. - `diagrams/` - Source files for diagrams used on the site. + + +网站上使用的图表的源文件。 +- `examples/` + +Source files for diagrams used on the site. - `examples/` - The source files for [code excerpts][] used in doc code blocks. + + +文档代码块中使用的[代码摘录][code excerpts]的源文件。 +- `src/` + - `_11ty/` + +The source files for [code excerpts][] used in doc code blocks. - `src/` - `_11ty/` - Custom extensions for [11ty][], [Liquid][], and Markdown. + + +[11ty][]、[Liquid][] 和 Markdown 的自定义扩展。 + - `plugins/` + - `syntax/` + +Custom extensions for [11ty][], [Liquid][], and Markdown. - `plugins/` - `syntax/` - [Shiki][] themes for syntax highlighting. + + +用于语法高亮的 [Shiki][] 主题。 + - `filters.ts` + - `shortcodes.ts` + - `_data/` + +[Shiki][] themes for syntax highlighting. - `filters.ts` - `shortcodes.ts` - `_data/` - YAML and JSON files used to add data used across site templates. + + +用于在网站模板中添加数据的 YAML 和 JSON 文件。 - `_includes/` - Partial files used by liquid [render and include][] statements. +YAML and JSON files used to add data used across site templates. + - `_includes/` + + + +liquid [render 和 include][] 语句使用的部分文件。 + - `_layouts/` + +Partial files used by liquid [render and include][] statements. - `_layouts/` - Layout templates used by the pages on the site. + + +网站页面使用的布局模板。 - `_sass/` - Styles for the generated documentation, written with [sass][]. +Layout templates used by the pages on the site. + - `_sass/` + + + +使用 [sass][] 编写的生成文档的样式。 + - `content/` + +Styles for the generated documentation, written with [sass][]. - `content/` - The root directory for the content of the site. + + +网站内容的根目录。 + - `assets/` + +The root directory for the content of the site. - `assets/` - The directory for assets, including images, used by the site. + + +网站使用的资源(包括图片)目录。 - `...` - The other directories hosting the site content. +The directory for assets, including images, used by the site. + - `...` + + + +托管网站内容的其他目录。 - `tool/` - `dash_site/` - The implementation directories for the `dash_site` tooling. +The other directories hosting the site content. +- `tool/` + - `dash_site/` + + + +`dash_site` 工具的实现目录。 +- `dash_site` + +The implementation directories for the `dash_site` tooling. - `dash_site` - The entrypoint script for the site's CLI tool. + + +网站 CLI 工具的入口脚本。 - `eleventy.config.ts` - The entrypoint for the site's [11ty][] static-site generation setup. +The entrypoint script for the site's CLI tool. +- `eleventy.config.ts` + + + +网站 [11ty][] 静态网站生成设置的入口。 +- `firebase.json` + +The entrypoint for the site's [11ty][] static-site generation setup. - `firebase.json` - Configuration for [Firebase Hosting][] that is used for + + +用于暂存和部署网站的 [Firebase Hosting][] 配置。 +- `package.json` + +Configuration for [Firebase Hosting][] that is used for the staged and deployed sites. - `package.json` - Configuration of used [npm][] dependencies. + + +使用的 [npm][] 依赖项配置。 + +Configuration of used [npm][] dependencies. [gh-actions]: https://docs.github.com/actions [gh-templates]: https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests diff --git a/src/content/contribute/docs/markdown.md b/src/content/contribute/docs/markdown.md index fa62002c26..a7c037671c 100644 --- a/src/content/contribute/docs/markdown.md +++ b/src/content/contribute/docs/markdown.md @@ -1,6 +1,11 @@ --- +# title: Authoring Markdown title: Authoring Markdown +# shortTitle: Markdown shortTitle: Markdown +# description: >- +# Learn about the Markdown syntaxes the Dart and Flutter documentation sites +# support and their guidelines for using them. description: >- Learn about the Markdown syntaxes the Dart and Flutter documentation sites support and their guidelines for using them. @@ -17,24 +22,55 @@ Our sites support writing content in [Markdown][], with some additions from [GitHub Flavored Markdown][] as well as other custom syntaxes. + + +我们的网站支持使用 [Markdown][] 编写内容, +以及来自 [GitHub Flavored Markdown][] 的一些扩展 +和其他自定义语法。 + This page outlines the Markdown syntax we support as well as our style guidelines for authoring Markdown. + + +本页面概述了我们支持的 Markdown 语法 +以及编写 Markdown 的样式指南。 + [Markdown]: https://commonmark.org/ [GitHub Flavored Markdown]: https://github.github.com/gfm/ ## General guidelines + + +## 通用指南 + Prefer using Markdown syntax over custom HTML and components. Raw Markdown is easier to maintain, easier for tools to understand, and easier to migrate in the future if necessary. + + +优先使用 Markdown 语法而不是自定义 HTML 和组件。 +原始 Markdown 更易于维护、更易于工具理解, +并且在必要时更容易迁移。 + ## Code blocks + + +## 代码块 + Don't use Markdown's indented code blocks, only use fenced code blocks using backticks and always specify a language. For example: + + +不要使用 Markdown 的缩进代码块, +只使用带反引号的围栏代码块, +并始终指定语言。例如: + ````markdown ```dart void main() { @@ -46,4 +82,9 @@ void main() { To learn more about customizing code blocks, check out the dedicated documentation on [Code blocks][]. + + +要了解更多关于自定义代码块的信息, +请查看[代码块][Code blocks]的专门文档。 + [Code blocks]: /contribute/docs/code-blocks diff --git a/src/content/contribute/docs/releases.md b/src/content/contribute/docs/releases.md index 3a56d2b91f..c8ce463eb2 100644 --- a/src/content/contribute/docs/releases.md +++ b/src/content/contribute/docs/releases.md @@ -1,6 +1,11 @@ --- +# title: Handling releases title: Handling releases +# shortTitle: Releases shortTitle: Releases +# description: >- +# Learn how to prepare for and handle new releases of +# Dart and Flutter on the documentation sites. description: >- Learn how to prepare for and handle new releases of Dart and Flutter on the documentation sites. diff --git a/src/content/contribute/docs/sidenav.md b/src/content/contribute/docs/sidenav.md index 88990b7795..8db3ab4fee 100644 --- a/src/content/contribute/docs/sidenav.md +++ b/src/content/contribute/docs/sidenav.md @@ -1,5 +1,9 @@ --- +# title: Sidenav title: Sidenav +# description: >- +# Learn about adding to and configuring the sidenav of the +# Dart and Flutter documentation site. description: >- Learn about adding to and configuring the sidenav of the Dart and Flutter documentation site. @@ -15,15 +19,41 @@ This document is a work in progress. The sidenav presents the overall information architecture for the site and provides developers access to documentation by topic. + + +侧边导航呈现网站的整体信息架构, +并为开发者提供按主题访问文档的功能。 + The contents of the sidenav are configured in the `/src/_data/sidenav.yml` file in [YAML][] format. + + +侧边导航的内容在 +`/src/_data/sidenav.yml` 文件中以 [YAML][] 格式配置。 + [YAML]: https://yaml.org/ ## Add a page + + +## 添加页面 + ## Remove a page + + +## 移除页面 + ## Hide pages unless open + + +## 除非展开否则隐藏页面 + ## Infrastructure + + + +## 基础设施 diff --git a/src/content/contribute/docs/writing.md b/src/content/contribute/docs/writing.md index 5690a275ca..128587c7ee 100644 --- a/src/content/contribute/docs/writing.md +++ b/src/content/contribute/docs/writing.md @@ -1,6 +1,10 @@ --- +# title: Writing for the docs sites title: Writing for the docs sites short-tile: Writing +# description: >- +# Learn about the writing style guide and processes followed when writing +# for the Dart and Flutter documentation sites. description: >- Learn about the writing style guide and processes followed when writing for the Dart and Flutter documentation sites. @@ -15,15 +19,29 @@ This document is a work in progress. ## Writing guidelines + + +## 写作指南 + When writing for the documentation sites, follow the [Google developer documentation style guide][], except in the cases where the [Dash docs guidelines][] conflict with it. + + +为文档网站写作时, +请遵循 [Google 开发者文档风格指南][Google developer documentation style guide], +除非 [Dash 文档指南][Dash docs guidelines]与之冲突。 + [Google developer documentation style guide]: https://developers.google.com/style [Dash docs guidelines]: #dash-docs-styles ### Dash docs styles + + +### Dash 文档样式 + :::warning This section is a work in progress. It will be added to over time. @@ -31,11 +49,22 @@ It will be added to over time. ## Semantic breaks + + +## 语义换行 + To make PR review, diff resolution, and history tracking easier, use [semantic breaks][] when writing Markdown. Reference the [full specification][sembr-spec] for helps, but roughly follow these guidelines: + + +为了使 PR 审查、差异解决和历史跟踪更容易, +在编写 Markdown 时请使用[语义换行][semantic breaks]。 +参考[完整规范][sembr-spec]获取帮助, +大致遵循以下指南: + - Keep each line 80 characters or fewer. - Break lines at sentences and, unless the sentence is very short, on phrases within sentences. @@ -45,51 +74,122 @@ but roughly follow these guidelines: That way future editors and reviewers are more likely to notice that the edit might affect another line. + + +- 每行保持 80 个字符或更少。 +- 在句子处换行,除非句子很短, + 也可以在句子中的短语处换行。 +- 当需要将句子拆分到多行时, + 尽量选择一个能清楚表明 + 该行在下一行继续的换行点。 + 这样未来的编辑者和审查者更可能 + 注意到编辑可能会影响另一行。 + Incorporating semantic breaks in your writing might feel tedious at first, but quickly proves helpful and becomes natural. Don't worry about getting the breaks perfect or completely consistent, any effort towards their semantic nature is extremely helpful. + + +在写作中加入语义换行一开始可能感觉很繁琐, +但很快就会证明是有帮助的并变得自然。 +不用担心换行是否完美或完全一致, +任何向语义化方向的努力都非常有帮助。 + For some more discussion about the origin of this technique, also check out Brandon Rhode's [Semantic Linefeeds][] post. + + +有关此技术起源的更多讨论, +另请查看 Brandon Rhode 的[语义换行][Semantic Linefeeds]文章。 + [semantic breaks]: https://sembr.org/ [sembr-spec]: https://sembr.org/#:~:text=seen%20by%20readers.-,Semantic%20Line%20Breaks%20Specification,-(SemBr) [Semantic Linefeeds]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ ## Links + + +## 链接 + ### Write link text + + +### 编写链接文本 + Use descriptive link text that follows the Google guidelines on [Cross-references and linking][]. + + +使用遵循 Google 关于[交叉引用和链接][Cross-references and linking]指南的描述性链接文本。 + [Cross-references and linking]: https://developers.google.com/style/cross-references ### Configure link destinations + + +### 配置链接目标 + For easier editing, shorter lines, and reduced duplication, prefer using Markdown link references instead of inline links. + + +为了更容易编辑、更短的行和减少重复, +请优先使用 Markdown 链接引用而不是内联链接。 + Place the link definitions at the end of the current section where they're used, before the next header. + + +将链接定义放在使用它们的当前部分的末尾, +在下一个标题之前。 + If a link definition is used multiple times across a page, you can place it at the bottom of the document. + + +如果链接定义在页面中多次使用, +你可以将其放在文档底部。 + ### Open the link in a new tab + + +### 在新标签页中打开链接 + If you want a link to open in a new tab by default, add the `target="_blank"` and `rel="noopener"` attributes. + + +如果你希望链接默认在新标签页中打开, +请添加 `target="_blank"` 和 `rel="noopener"` 属性。 + For Markdown links: + + +对于 Markdown 链接: + ```md [Link text][link-ref]{: target="_blank" rel="noopener"} ``` For HTML links: + + +对于 HTML 链接: + ```html
      Link text ``` diff --git a/src/content/contribute/index.md b/src/content/contribute/index.md index 9429cec46d..f3e8705185 100644 --- a/src/content/contribute/index.md +++ b/src/content/contribute/index.md @@ -1,6 +1,10 @@ --- +# title: Contribute to Flutter title: Contribute to Flutter +# shortTitle: Contribute shortTitle: Contribute +# description: >- +# Learn about contributing to the Flutter project and its surrounding ecosystem. description: >- Learn about contributing to the Flutter project and its surrounding ecosystem. showBreadcrumbs: false @@ -8,19 +12,41 @@ showBreadcrumbs: false ![Dash and her friends excited for your contribution](/assets/images/dash/dash-contribute.png){:height="160px" style="float: right;"} + + +![Dash 和她的朋友们为你的贡献感到兴奋](/assets/images/dash/dash-contribute.png){:height="160px" style="float: right;"} + If you would like to contribute to the Flutter project and its surrounding ecosystem, we're happy to have your help! + + +如果你想为 Flutter 项目及其周边生态系统做出贡献, +我们很高兴得到你的帮助! + Flutter is an open-source project that thrives on community contributions. No matter whether you're fixing a bug, proposing a new feature, improving documentation, or helping others in the community, your efforts are valuable and appreciated. + + +Flutter 是一个依靠社区贡献而蓬勃发展的开源项目。 +无论你是修复 bug、提出新功能、改进文档, +还是在社区中帮助他人, +你的努力都是有价值的,我们非常感谢。 + This page provides a non-exhaustive overview of how you can get involved. If you need help contributing or would like more suggestions on getting started, consider reaching out on the [Flutter contributors Discord][]. + + +本页面提供了如何参与的概述(但不是全部)。 +如果你在贡献时需要帮助或想要更多入门建议, +请考虑在 [Flutter 贡献者 Discord][Flutter contributors Discord] 上联系我们。 + :::important Before beginning your journey of contributing to Flutter, please make sure to read and follow Flutter's [Code of conduct][]. @@ -28,6 +54,11 @@ please make sure to read and follow Flutter's [Code of conduct][]. Also, learn more about Flutter's [culture of inclusivity][] and [core values][]. ::: + + +此外,了解更多关于 Flutter 的[包容性文化][culture of inclusivity]和[核心价值观][core values]。 +::: + [Flutter contributors Discord]: {{site.main-url}}/chat [Code of conduct]: {{site.repo.flutter}}/blob/main/CODE_OF_CONDUCT.md [culture of inclusivity]: https://flutter.dev/culture @@ -86,57 +117,184 @@ Also, learn more about Flutter's [culture of inclusivity][] and [core values][]. ## Develop with Flutter + + + + Even just using Flutter and providing feedback is a valuable contribution! + + +## 使用 Flutter 进行开发 + ### Provide feedback + + +即使只是使用 Flutter 并提供反馈也是一种有价值的贡献! + Sharing your feedback and experiences helps the Flutter team understand and prioritize developer needs and pain points. + + +### 提供反馈 + You can provide valuable feedback through many avenues, including: + + +分享你的反馈和经验可以帮助 Flutter 团队 +理解并优先考虑开发者的需求和痛点。 + - Upvoting existing issues - If you're experiencing an issue that has already been reported, + + +你可以通过多种途径提供有价值的反馈,包括: + +If you're experiencing an issue that has already been reported, consider upvoting it to help the Flutter team understand its importance. - Avoid otherwise empty thumbs up, +1, or similar comments. + + +- 为现有 issue 投票 + +Avoid otherwise empty thumbs up, +1, or similar comments. However, if you have additional information, such as reproduction steps or additional version information, do consider providing those details in a new comment. - Reporting new bugs - If you experience a bug with Flutter that hasn't yet been reported, + + +如果你遇到的问题已经被报告, + 请考虑为其投票,帮助 Flutter 团队了解其重要性。 + +If you experience a bug with Flutter that hasn't yet been reported, do [open a new issue][] with reproduction information. - Requesting features - If there's a feature you think Flutter should add or implement + + +避免发表空洞的点赞、+1 或类似评论。 + 但是,如果你有额外的信息, + 例如复现步骤或额外的版本信息, + 请考虑在新评论中提供这些详细信息。 +- 报告新 bug + +If there's a feature you think Flutter should add or implement but hasn't yet been suggested, do [open a new issue][] with all relevant information as well as your use case. - Partaking in surveys - Occasionally, the Flutter team will run developer surveys and studies. + + +如果你遇到了尚未被报告的 Flutter bug, + 请[提交新 issue][open a new issue] 并附上复现信息。 +- 请求功能 + +Occasionally, the Flutter team will run developer surveys and studies. To help understand pain points and improve the Flutter developer experience, consider responding with as much feedback and details as possible. - To sign up for future UX research studies, + + +如果你认为 Flutter 应该添加或实现某个功能, + 但尚未有人建议,请[提交新 issue][open a new issue], + 附上所有相关信息以及你的用例。 +- 参与调查 + +To sign up for future UX research studies, visit [flutter.dev/research-signup][uxr-signup]. - Discussing proposals - Major changes to Flutter are often discussed through [design documents][]. + + +Flutter 团队会不定期进行开发者调查和研究。 + 为了帮助了解痛点并改善 Flutter 开发者体验, + 请考虑尽可能详细地提供反馈和细节。 + +Major changes to Flutter are often discussed through [design documents][]. Consider reading and providing feedback on proposals that are relevant to you or your apps. - To find current design docs and proposals, + + +要注册参加未来的 UX 研究, + 请访问 [flutter.dev/research-signup][uxr-signup]。 +- 讨论提案 + +To find current design docs and proposals, check out [issues with the `design doc` label][design-doc-issues] on the GitHub issue database. - Reviewing pull requests - If you're familiar with a particular area of Flutter + + +对 Flutter 的重大更改通常通过[设计文档][design documents]进行讨论。 + 请考虑阅读并对与你或你的应用相关的提案提供反馈。 + +If you're familiar with a particular area of Flutter or a solution to a particular issue is important to you, consider reviewing open pull requests, trying them with your app, and providing any relevant feedback. + + +要查找当前的设计文档和提案, + 请查看 GitHub issue 数据库中[带有 `design doc` 标签的 issue][design-doc-issues]。 +- 审查 pull request + [open a new issue]: {{site.repo.flutter}}/issues/new [uxr-signup]: {{site.main-url}}/research-signup [design documents]: {{site.repo.flutter}}/blob/main/docs/contributing/Design-Documents.md @@ -144,19 +302,45 @@ You can provide valuable feedback through many avenues, including: ### Try out the beta channel + + +如果你熟悉 Flutter 的某个特定领域, + 或者某个特定问题的解决方案对你很重要, + 请考虑审查开放的 pull request,用你的应用测试它们, + 并提供任何相关反馈。 + To help ensure Flutter's stability and improve upcoming features, help test upcoming releases before they reach the stable channel. + + +### 尝试 beta 渠道 + Consider testing releases on the `beta` channel, both for general development and for testing compatibility with your apps. + + +为了帮助确保 Flutter 的稳定性并改进即将推出的功能, +请在新版本到达 stable 渠道之前帮助测试。 + Any feedback you have or regressions you encounter, make sure to [report them][report-bugs] to the Flutter team. + + +请考虑在 `beta` 渠道上测试版本, +无论是用于一般开发还是测试与你的应用的兼容性。 + To get started, [switch][switch-channels] to the [`beta` channel][beta-channel] today and account for any [necessary migrations][]. + + +如果你有任何反馈或遇到回归问题, +请务必[报告给][report-bugs] Flutter 团队。 + [switch-channels]: /install/upgrade#change-channels [beta-channel]: /install/upgrade#the-beta-channel [report-bugs]: {{site.github}}/flutter/flutter/issues/new/choose @@ -164,73 +348,185 @@ and account for any [necessary migrations][]. ## Contribute code + + +要开始使用, +请立即[切换][switch-channels]到 [`beta` 渠道][beta-channel], +并处理任何[必要的迁移][necessary migrations]。 + Directly improve Flutter's codebase and related tools. + + +## 贡献代码 + ### Flutter framework + + +直接改进 Flutter 的代码库和相关工具。 + Found a bug in a built-in widget, have an idea for a new one, love adding tests, or just interested in the internals of Flutter? Consider contributing to the Flutter framework itself and improving the core of Flutter for everyone. + + +### Flutter 框架 + To learn about contributing to the Flutter framework, check out the Flutter [contribution guide][framework-contribute]. + + +在内置 widget 中发现了 bug,有新 widget 的想法, +喜欢添加测试,或者只是对 Flutter 的内部实现感兴趣? +请考虑为 Flutter 框架本身做贡献, +为所有人改进 Flutter 的核心。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md ### Flutter engine + + +要了解如何为 Flutter 框架做贡献, +请查看 Flutter [贡献指南][framework-contribute]。 + Interested in implementing the primitives and platform integrations that underlay Flutter or have a knack for graphics programming? Consider contributing to the Flutter engine and making Flutter even more portable, performant, and powerful. + + +### Flutter 引擎 + To learn about contributing to the Flutter engine, check out the Flutter [contribution guide][framework-contribute] and how to [Set up the engine development environment][engine-setup]. + + +对实现构成 Flutter 基础的原语和平台集成感兴趣, +或者擅长图形编程? +请考虑为 Flutter 引擎做贡献, +让 Flutter 更加便携、高性能和强大。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [engine-setup]: {{site.repo.flutter}}/blob/main/engine/src/flutter/docs/contributing/Setting-up-the-Engine-development-environment.md ### Flutter packages + + +要了解如何为 Flutter 引擎做贡献, +请查看 Flutter [贡献指南][framework-contribute] +以及如何[设置引擎开发环境][engine-setup]。 + Contribute to first-party packages that are maintained by the Flutter team. The first-party packages provide essential functionality for apps as well as encapsulate various platform-specific functionality. + + +为 Flutter 团队维护的第一方 package 做贡献。 +第一方 package 为应用提供基本功能, +并封装各种平台特定的功能。 + To learn about contributing to the first-party packages, check out the Flutter [contribution guide][framework-contribute] as well as the packages-specific [contribution guide][packages-contribute]. + + +要了解如何为第一方 package 做贡献, +请查看 Flutter [贡献指南][framework-contribute] +以及 package 特定的[贡献指南][packages-contribute]。 + [framework-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [packages-contribute]: {{site.repo.packages}}/blob/main/CONTRIBUTING.md ### DevTools + + +为 [Dart 和 Flutter DevTools][Dart and Flutter DevTools] 做贡献 +是一个很好的起点,因为其设置成本相对较低。 +增强和修复可以极大地影响 Flutter 开发者的开发体验, +也许还能帮助你开发自己的应用。 + Contributing to the [Dart and Flutter DevTools][] is a great place to begin contributing due to its relatively low-cost setup. Enhance and fixes can greatly impact the developer experience for Flutter developers and perhaps help you develop your own apps. + + +要开始使用,请查看 +[DevTools `CONTRIBUTING.md` 指南][devtools-contribute]。 + To get started, check out the [DevTools `CONTRIBUTING.md` guide][devtools-contribute]. + + +### 网站基础设施 + [Dart and Flutter DevTools]: /tools/devtools [devtools-contribute]: {{site.repo.organization}}/devtools/blob/master/CONTRIBUTING.md ### Site infrastructure + + +修复 bug、改善无障碍访问或为 Dart 和 Flutter 网站添加功能。 + Fix bugs, improve accessibility, or add features to the Dart and Flutter websites. + + +如果你熟悉 Web 开发或网站生成, +为 Dart 和 Flutter 网站做贡献是改善 +Flutter 开发者学习体验的好途径。 + If you're familiar with web development or site generation, contributing to the Dart and Flutter websites can be a great avenue to improve the learning experience of Flutter developers. + + +根据你的兴趣,你可能想要为以下内容做贡献: + Depending on your interests, you might want to contribute to: + + +- pub.dev 网站 + - **在线网站:** [`pub.dev`]({{site.pub}}) + - **仓库:** [`dart-lang/pub-dev`]({{site.github}}/dart-lang/pub-dev) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/pub-dev/blob/master/CONTRIBUTING.md) +- Flutter 文档网站 + - **在线网站:** [`docs.flutter.dev`]({{site.url}}) + - **仓库:** [`flutter/website`]({{site.repo.this}}) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/flutter/website/blob/main/CONTRIBUTING.md) +- Dart 文档网站 + - **在线网站:** [`dart.dev`]({{site.dart-site}}) + - **仓库:** [`dart-lang/site-www`]({{site.github}}/dart-lang/site-www) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/site-www/blob/main/CONTRIBUTING.md) +- DartPad + - **在线网站:** [`dartpad.dev`]({{site.dartpad}}) + - **仓库:** [`dart-lang/dart-pad`]({{site.github}}/dart-lang/dart-pad) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dart-pad/blob/main/CONTRIBUTING.md) +- `dartdoc` 工具 + - **在线网站:** [`api.flutter.dev`]({{site.api}}) + - **仓库:** [`dart-lang/dartdoc`]({{site.github}}/dart-lang/dartdoc) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dartdoc/blob/main/CONTRIBUTING.md) + - The pub.dev site - **Live site:** [`pub.dev`]({{site.pub}}) - **Repository:** [`dart-lang/pub-dev`]({{site.github}}/dart-lang/pub-dev) @@ -252,27 +548,72 @@ you might want to contribute to: - **Repository:** [`dart-lang/dartdoc`]({{site.github}}/dart-lang/dartdoc) - **Contribution guide:** [`CONTRIBUTING.md`]({{site.github}}/dart-lang/dartdoc/blob/main/CONTRIBUTING.md) + + +为 Dart 语言及其周边工具做贡献, +改进这门为客户端优化的语言, +它是 Flutter 出色开发者体验的基础。 + ### Dart SDK + + +Dart 的贡献工作流程略有不同, +所以如果你有兴趣,请务必查看其 +[贡献][dart-contribute]和[构建][dart-build]指南。 + Contribute to the Dart language and surrounding tooling, improving the client-optimized language that forms the foundation of Flutter's excellent developer experience. + + +### 代码示例 + Dart's contribution workflow is slightly different, so if you're interested, make sure to check out its [contribution][dart-contribute] and [building][dart-build] guides. + + +改进或添加演示 Flutter 功能的示例, +帮助喜欢通过示例学习的开发者。 + [dart-contribute]: {{site.github}}/dart-lang/sdk/blob/main/CONTRIBUTING.md [dart-build]: {{site.github}}/dart-lang/sdk/blob/main/docs/Building.md ### Code samples + + +你可以随时分享你自己的示例或模板, +或者你可以为 Flutter 维护的示例做贡献: + Improve or add samples demonstrating Flutter features, helping developers that prefer to learn by example. + + +- 完整项目示例 + - **位置:** [`flutter/samples`]({{site.repo.samples}}) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.samples}}/blob/main/CONTRIBUTING.md) +- API 文档示例 + - **位置:** [`flutter/flutter/packages/flutter`]({{site.repo.flutter}}/tree/main/packages/flutter) + - **贡献指南:** [`README.md`]({{site.repo.flutter}}/tree/main/dev/snippets) +- 网站代码片段 + - **位置:** [`flutter/website/examples`]({{site.repo.this}}/tree/main/examples) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.this}}/blob/main/CONTRIBUTING.md) +- Flutter 仓库示例 + - **位置:** [`flutter/flutter/examples`]({{site.repo.flutter}}/tree/main/examples) + - **贡献指南:** [`CONTRIBUTING.md`]({{site.repo.flutter}}/blob/main/CONTRIBUTING.md) + You can always share your own samples or templates, or you can contribute to Flutter-maintained samples: + + +## 编写文档 + - Full project samples - **Location:** [`flutter/samples`]({{site.repo.samples}}) - **Contribution guide:** [`CONTRIBUTING.md`]({{site.repo.samples}}/blob/main/CONTRIBUTING.md) @@ -286,53 +627,130 @@ or you can contribute to Flutter-maintained samples: - **Location:** [`flutter/flutter/examples`]({{site.repo.flutter}}/tree/main/examples) - **Contribution guide:** [`CONTRIBUTING.md`]({{site.repo.flutter}}/blob/main/CONTRIBUTING.md) + + +为 Flutter 文档做贡献,无论以何种形式, +都是帮助 Flutter 最有影响力的方式之一。 + ## Write documentation + + +### Flutter API 文档 + Contributing to Flutter documentation, no matter the form, is one of the most impactful ways you can help Flutter. + + +API 文档被许多 Flutter 开发者大量依赖, +无论是在线还是在他们的代码编辑器中。 + ### Flutter API docs + + +无论你是对编写新文档、更新现有文档、 +添加相关代码片段,还是创建新的视觉效果(如图表)感兴趣, +你对 API 文档的贡献都会受到每位 Flutter 开发者的感谢。 + The API docs are heavily relied on by many Flutter developers, both online and in their code editors. + + +要开始使用,请查看 +[Flutter SDK 贡献指南][flutter-contribute], +特别是其中关于 [API 文档][flutter-api-contribute]的部分。 + Whether you're interested in writing new docs, updating existing ones, adding relevant code snippets, or even creating new visuals like diagrams, your contribution to the API docs will be appreciated by every Flutter developer. + + +### 文档网站 + To get started, check out the [Flutter SDK contribution guide][flutter-contribute], particularly its section on [API documentation][flutter-api-contribute] + + +考虑为这个网站做贡献, +在开发者学习和探索 Flutter 时为他们提供指导。 + [flutter-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md [flutter-api-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md#api-documentation ### Documentation website + + +要了解如何为 Flutter 文档网站做贡献, +请查看网站的[贡献文档][website-contribute]。 + Consider contributing to this very site, guiding developers as they learn and explore Flutter. + + +你也可以为 [Dart 网站][Dart website]做贡献, +增强这门为客户端优化的语言的文档, +它是 Flutter 的基础。 +要了解如何贡献, +请查看 [`dart-lang/site-www` 贡献文档][dart-dev-contribute]。 + To learn about contributing to the Flutter documentation website, check out the website's [contribution documentation][website-contribute]. + + +## 分类 issue + You can also contribute to the [Dart website][], enhancing the documentation for the client-optimized language that forms the foundation of Flutter. To learn how to contribute, check out the [`dart-lang/site-www` contribution docs][dart-dev-contribute]. + + +通过分类传入的 bug 报告和功能请求来帮助 Flutter 团队。 + [website-contribute]: {{site.repo.this}}/blob/main/CONTRIBUTING.md [Dart website]: {{site.dart-site}} [dart-dev-contribute]: {{site.github}}/dart-lang/site-www/tree/main?tab=readme-ov-file#getting-started ## Triage issues + + +在 [Flutter 的 issue 数据库][flutter-issues]中有很多帮助的方式, +包括但不限于: + Help the Flutter team by triaging incoming bug reports and feature requests. + + +- 确定 issue 有效性 +- 确保可操作性 +- 记录受影响的版本 +- 添加复现步骤 +- 识别重复或已解决的 issue +- 解决或重定向支持查询 + There are plenty of ways to help in [Flutter's issue database][flutter-issues], including but not limited to: + + +要开始帮助处理 issue, +请阅读[在 issue 数据库中提供帮助][issue-contribute], +并了解 Flutter 的 +[issue 分类][issue triage]和 [issue 管理][issue hygiene]方法。 + - Determining issue validity - Ensuring actionability - Documenting affected versions @@ -340,11 +758,20 @@ including but not limited to: - Identifying duplicate or resolved issues - Solving or redirecting support queries + + +## 加强 package 生态系统 + To get started helping with issues, read about [helping out in the issue database][issue-contribute] and learn about Flutter's approach to [issue triage][] and [issue hygiene][issue hygiene]. + + +帮助增长和支持 [pub.dev](https://pub.dev/) 上可用的 +Dart 和 Flutter package 集合。 + [flutter-issues]: {{site.repo.flutter}}/issues [issue-contribute]: {{site.repo.flutter}}/blob/main/CONTRIBUTING.md#helping-out-in-the-issue-database [issue triage]: {{site.repo.flutter}}/blob/main/docs/triage/README.md @@ -352,36 +779,82 @@ learn about Flutter's approach to ## Strengthen the package ecosystem + + +### 为你使用的 package 做贡献 + Help grow and support the collection of available Dart and Flutter packages on [pub.dev](https://pub.dev/). + + +为了回馈你依赖的 package 并可能帮助你自己的应用, +找到你依赖的 package 并为它们做贡献。 + ### Contribute to packages you use + + +要为 package 做贡献, +请导航到 [pub.dev 网站][pub.dev site]上的页面, +并找到页面侧边导航中链接的仓库。 + To give back to packages you depend on and potentially even help your own apps, find packages you rely on and contribute back to them. + + +在贡献之前,请务必 +遵循每个 package 的贡献指南, +与维护者讨论你的贡献, +并牢记 Flutter 的[行为准则][Code of conduct]。 + To contribute to a package, navigate to its page on the [pub.dev site][] and find the repository linked in the page's sidenav. + + +### 从你的应用开源可复用功能 + Before contributing, do make sure to follow each package's contribution guide, discuss your contribution with the maintainers, and keep in mind Flutter's [Code of conduct][]. + + +如果你在应用中构建了一个很酷的通用 widget 或工具, +请考虑将其提取为 package 并发布到 pub.dev。 + [pub.dev site]: {{site.pub}} [Code of conduct]: {{site.repo.flutter}}/blob/main/CODE_OF_CONDUCT.md ### Open source reusable functionality from your app + + +要开始,请了解 +[创建 Dart package][Creating Dart packages] 和[开发 Flutter package][Developing Flutter packages]。 +然后,当你准备好将 package 发布到 [pub.dev 网站][pub.dev site]时, +请遵循[发布 package][Publishing packages] 的指南和最佳实践。 + If you've built a cool, generic widget or utility in your app, consider extracting it into a package and publishing it to pub.dev. + + +### 为流行的 SDK 添加 Dart 或 Flutter 支持 + To get started, learn about [Creating Dart packages][] and [Developing Flutter packages][]. Then, when you're ready to publish your package to the [pub.dev site][], follow the guide and best practices on [Publishing packages][]. + + +创建或为封装原生 SDK 或 Web API 的 package 做贡献。 + [Creating Dart packages]: {{site.dart-site}}/tools/pub/create-packages [Developing Flutter packages]: /packages-and-plugins/developing-packages [pub.dev site]: {{site.pub}} @@ -389,22 +862,51 @@ follow the guide and best practices on [Publishing packages][]. ### Add Dart or Flutter support to popular SDKs + + +在创建新 package 之前, +首先尝试在 [pub.dev 网站][pub.dev site]上 +找到你可以使用或贡献的现有封装。 + Create or contribute to packages that wrap native SDKs or web APIs. + + +根据 SDK 和平台, +你可能需要[编写平台特定代码][Write platform-specific code]、 +使用 [JS 互操作][JS interop]、使用 [`package:http`][] 封装 REST API, +或在 Dart 中重新实现所需的功能。 + Before creating a new package, first try to find any existing wrappers that you could use or contribute to on the [pub.dev site][]. + + +如果你计划创建新 package,请了解 +[创建 Dart package][Creating Dart packages] 和[开发 Flutter package][Developing Flutter packages]。 +然后,当你准备好将 package 发布到 [pub.dev 网站][pub.dev site]时, +请遵循[发布 package][Publishing packages] 的指南和最佳实践。 + Depending on the SDK and platform, you might need to [Write platform-specific code][], use [JS interop][], wrap a REST API using [`package:http`][], or reimplement the required functionality in Dart. + + +## 支持社区 + If you're planning to create a new package, learn about [Creating Dart packages][] and [Developing Flutter packages][]. Then, when you're ready to publish your package to the [pub.dev site][], follow the guide and best practices on [Publishing packages][]. + + +帮助其他开发者学习 Flutter, +并在他们构建自己的应用时取得成功。 + [pub.dev site]: {{site.pub}} [Write platform-specific code]: /platform-integration/platform-channels [JS interop]: {{site.dart-site}}/interop/js-interop @@ -412,19 +914,50 @@ follow the guide and best practices on [Publishing packages][]. ## Support the community + + +### 帮助其他开发者 + Help other developers learn Flutter and succeed as they build their own apps. + + +分享你的 Flutter 知识和专业技能, +帮助你的 Flutter 开发者同伴取得成功。 + ### Help other developers + + +这可以采取多种形式,从在你的公司开设 Flutter 帮助频道, +到在公共论坛上回答问题。 + Share your Flutter knowledge and expertise to help your fellow Flutter developers succeed. + + +Flutter 开发者通常寻求帮助的一些常见地点包括: + This can take many forms from starting a Flutter help channel in your company to answering questions on public forums. + + +- [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) +- [Flutter Dev Discord](https://discord.com/invite/rflutterdev) +- [Dart Community Discord](https://discord.com/invite/Qt6DgfAWWx) +- [Reddit 上的 r/FlutterDev](https://www.reddit.com/r/FlutterDev) +- [GitHub issues]({{site.repo.flutter}}/issues) +- [Flutter 论坛](https://forum.itsallwidgets.com/) + Some common locations Flutter developers look for help include: + + +### 举办活动 + - [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) - [Flutter Dev Discord](https://discord.com/invite/rflutterdev) - [Dart Community Discord](https://discord.com/invite/Qt6DgfAWWx) @@ -432,30 +965,72 @@ Some common locations Flutter developers look for help include: - [GitHub issues]({{site.repo.flutter}}/issues) - [Flutter Forum](https://forum.itsallwidgets.com/) + + +与其他 Flutter 爱好者建立联系, +组织本地、全国甚至虚拟活动。 +活动可以是任何形式,从学习小组和简单的聚会, +到工作坊和黑客马拉松。 + ### Host events + + +为了获得灵感和支持, +请查看现有的 [Flutter 活动][Flutter events], +了解更多关于 [Flutter 社区][Flutter community]的信息, +并探索 [Flutter Meetup 网络][Flutter Meetup Network]。 + Connect with other Flutter enthusiasts and organize local, national, and even virtual events. Events can be anything, from study groups and simple meetups, to workshops and hackathons. + + +### 发布关于 Flutter 的内容 + For inspiration and support, check out existing [Flutter events][], learn more about the [Flutter community][], and explore the [Flutter Meetup Network][]. + + +与更广泛的 Flutter 社区分享你的见解和项目。 + [Flutter events]: {{site.main-url}}/events [Flutter community]: {{site.main-url}}/community [Flutter Meetup Network]: https://www.meetup.com/pro/flutter/ ### Post about Flutter + + +关于分享 Flutter 和与开发者社区建立联系有无数的选择。 +一些常见的渠道包括: + Share your insights and projects with the wider Flutter community. + + +- 博客文章 +- 视频教程 +- 短文章 +- 论坛帖子 +- GitHub 讨论 +- 链接聚合板块 + There are endless options for sharing about Flutter and connecting with the developer community. Some common outlets include: + + +发布或分享你热衷的任何内容, +但如果你不确定要发布什么, +请考虑发布开发者经常询问的主题。 + - Blog posts - Video tutorials - Short-form posts @@ -463,6 +1038,12 @@ Some common outlets include: - GitHub discussions - Link aggregation boards + + +如果你发布的平台支持标记帖子, +请考虑添加 `#Flutter` 和 `#FlutterDev` 标签, +以帮助其他开发者找到你的内容。 + Post or share about whatever you're passionate about, but if you're not sure what to post, consider posting about topics that developers often ask about. diff --git a/src/content/get-started/custom.md b/src/content/get-started/custom.md index 6aa3dfdc7f..5999e1d5e4 100644 --- a/src/content/get-started/custom.md +++ b/src/content/get-started/custom.md @@ -1,6 +1,11 @@ --- +# title: Set up Flutter for your needs title: Set up Flutter for your needs +# shortTitle: Custom setup shortTitle: Custom setup +# description: >- +# Install and set up Flutter for your preferred +# development environment and target platforms. description: >- Install and set up Flutter for your preferred development environment and target platforms. @@ -12,17 +17,37 @@ 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。 +选择以下安装方式之一: +
      @@ -46,11 +71,40 @@ Choose one of the following installation methods: ## Set up an IDE or editor {: #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. + + +## 配置 IDE 或编辑器 {: #editor} +
      @@ -88,14 +142,66 @@ Firebase Studio, and other Code OSS-based editors. ## Set up a target platform {: #target-platform} + + +为了获得最佳的 Flutter 开发体验, +请安装支持 Dart 和 Flutter 的 IDE 或编辑器。 +一些常用的选项包括 VS Code、Android Studio、 +Firebase Studio 以及其他基于 Code OSS 的编辑器。 + Once you've successfully installed Flutter, set up development for at least one target platform to continue your journey with 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. + + +## 配置目标平台 {: #target-platform} +
      @@ -157,15 +263,92 @@ You can always set up development for additional target platforms later. ## Continue your Flutter journey {: #next-steps} + + +成功安装 Flutter 后, +请至少为一个目标平台配置开发环境, +以继续你的 Flutter 之旅。 + **Congratulations!** Now that you've installed Flutter, set up an IDE or editor, and set up development for a target platform, you can continue your Flutter learning journey. + + +我们建议首先[为 Web 平台开发][web-setup]{: target="_blank"}, +因为它除了需要一个合适的浏览器外,不需要其他额外配置。 +你随时可以在之后为其他目标平台配置开发环境。 + Follow the codelab on [Building your first app][], set up development for an [additional target platform][], or explore some of these other learning resources. + + + + {% 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..3a023992a9 100644 --- a/src/content/get-started/fundamentals/index.md +++ b/src/content/get-started/fundamentals/index.md @@ -1,36 +1,52 @@ --- +# title: Learn the fundamentals title: Learn the fundamentals +# shortTitle: Fundamentals shortTitle: Fundamentals +# description: > +# You've gotten a taste of using the Flutter framework; +# now go beyond to learn the basics of Flutter. description: > You've gotten a taste of using the Flutter framework; now go beyond to learn the basics of Flutter. 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 是如何工作的。 +
      - :::note These fundamentals docs are still a work in progress and we welcome your feedback! @@ -42,7 +58,12 @@ new subject pages in this section. We suggest that you work through the following subjects in the listed order. - 1. [Intro to Dart][] _(Optional)_ + + +这些基础文档仍在持续完善中,我们欢迎你的反馈! +请考虑填写本页底部和本章节各主题页面上列出的调查问卷。 + +1. [Intro to Dart][] _(Optional)_ As you might know, Flutter uses the [Dart language][]. If you have experience with other object-oriented languages, like Java, C++, or Swift, @@ -83,6 +104,10 @@ following subjects in the listed order. 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 +121,23 @@ following subjects in the listed order. ## Feedback + + +[Dart 入门][Intro to Dart] _(可选)_ + 你可能已经知道,Flutter 使用 [Dart 语言][Dart language]。 + 如果你有其他面向对象语言的经验, + 如 Java、C++ 或 Swift, + Dart 对你来说应该很熟悉。 + 截至目前, + [Dart 是增长最快的编程语言之一][dart-lang], + 这在一定程度上要归功于 Flutter。 + As this section of the website is evolving, we [welcome your feedback][]! + + +[Widget 基础][Widget fundamentals] + 了解 Flutter 应用程序的主要构建块之一——Widget。 + [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..96ab4d623a 100644 --- a/src/content/get-started/fundamentals/layout.md +++ b/src/content/get-started/fundamentals/layout.md @@ -1,5 +1,7 @@ --- +# title: Layouts title: Layouts +# description: Learn how to create layouts in Flutter. description: Learn how to create layouts in Flutter. prev: title: Widgets @@ -21,8 +23,22 @@ 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 +48,26 @@ 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 +75,26 @@ 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,10 +104,22 @@ 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. + + +在最简单的例子中,布局对话看起来是这样的: + +1. A widget receives its constraints from its parent. 2. A constraint is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height. @@ -76,18 +133,45 @@ the layout conversation looks like this: using a variety of widgets like `Center`, and the alignment properties on `Row` and `Column`. + + +1. Widget 从其父级接收约束。 + 2. 约束只是 4 个 double 值的集合: + 最小和最大宽度,以及最小和最大高度。 + 3. Widget 在这些约束范围内确定自己应该是什么大小, + 并将其宽度和高度传回给父级。 + 4. 父级查看它想要的大小以及应该如何对齐, + 并相应地设置 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 + + +### Box 类型 + 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 @@ -99,6 +183,16 @@ children. For example, the boxes used by For example, the boxes used by [`Image`][] and [`Text`][]. + + +通常,有三种类型的 box: +* 尝试尽可能大的 box。 + 例如,[`Center`][] 和 [`ListView`][] 使用的 box。 +* 尝试与其子级大小相同的 box。 + 例如,[`Transform`][] 和 [`Opacity`][] 使用的 box。 +* 尝试成为特定大小的 box。 + 例如,[`Image`][] 和 [`Text`][] 使用的 box。 + Some widgets, for example [`Container`][], vary from type to type based on their constructor arguments. @@ -107,19 +201,44 @@ 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][]. + + +其他 Widget,例如 [`Row`][] 和 [`Column`][](弹性 box), +根据给定的约束而变化。 +在[理解约束文章][Understanding Constraints article]中 +了解更多关于弹性 box 和约束的内容。 + ## 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 The examples on the page use a widget called `BorderedImage`. This is a custom widget, @@ -139,6 +258,12 @@ The following figure shows a widget that isn't aligned on the left, and a widget that has been centered on the right. + + +本页面的示例使用了一个名为 `BorderedImage` 的 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: @@ -149,8 +274,20 @@ or `Padding`. of widgets—for example, `Row`, `Column`, `ListView`, or `Stack`. + + +下图显示了左侧未对齐的 Widget 和右侧已居中的 Widget。 + ### Container + + +所有布局 Widget 都具有以下两种属性之一: +* 如果它们接受单个子级,则有 `child` 属性—— + 例如 `Center`、`Container` 或 `Padding`。 +* 如果它们接受 Widget 列表,则有 `children` 属性—— + 例如 `Row`、`Column`、`ListView` 或 `Stack`。 + `Container` is a convenience widget that's made up of several widgets responsible for layout, painting, positioning, and sizing. @@ -161,6 +298,14 @@ 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 +319,21 @@ 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 +347,10 @@ 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 +358,19 @@ 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." @@ -227,6 +396,11 @@ 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", caption: "This figure shows a row widget with three children, each of which is a column." @@ -260,9 +434,12 @@ 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 +447,11 @@ 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 +462,24 @@ 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 +508,50 @@ 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 +560,12 @@ 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 +601,14 @@ 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 +636,10 @@ 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 +649,13 @@ function usefully when given an unbounded constraint and, in debug mode, throws an exception. + + +在某些情况下,box 的约束是无界的或无限的。 +这意味着最大宽度或最大高度被设置为 [`double.infinity`][]。 +当给定无界约束时,尝试尽可能大的 box 将无法正常工作, +并且在调试模式下会抛出异常。 + The most common case where a render box ends up with an unbounded constraint is within a flex box ([`Row`][] or [`Column`][]), @@ -425,16 +671,40 @@ the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction. + + +渲染 box 最终遇到无界约束的最常见情况是在弹性 box +([`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 @@ -447,8 +717,19 @@ useful when building and debugging layouts (and working with widgets in general) [Learn more about the Flutter inspector][]. ::: + + +Flutter 拥有一套强大的 DevTools, +可以帮助你处理 Flutter 开发的各个方面。 +「Widget 检查器」工具在构建和调试布局时特别有用 +(以及在一般的 Widget 工作中)。 + ## Scrolling widgets + + +[了解更多关于 Flutter 检查器的信息][Learn more about the Flutter inspector]。 + Flutter has many built-in widgets that automatically scroll and also offers a variety of widgets that you can customize to @@ -457,8 +738,19 @@ 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. + + +## 滚动 Widget + ### ListView + + +Flutter 有许多内置的 Widget 可以自动滚动, +还提供了各种 Widget 供你自定义以创建特定的滚动行为。 +在本页面中,你将看到如何使用最常见的 Widget 使任何页面可滚动, +以及用于创建可滚动列表的 Widget。 + `ListView` is a column-like widget that automatically provides scrolling when its content is longer than its render box. @@ -469,6 +761,14 @@ 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,12 +794,23 @@ 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", caption: "This figure shows the ListView.builder constructor to display an unknown number of children." @@ -529,8 +840,19 @@ Widget build(BuildContext context) { ``` " %} + + +## 自适应布局 + ## Adaptive layouts + + +因为 Flutter 用于创建移动端、平板、桌面端_和_ Web 应用, +你很可能需要根据屏幕大小或输入设备等因素 +调整你的应用程序以表现不同的行为。 +这被称为使应用_自适应_和_响应式_。 + Because Flutter is used to create mobile, tablet, desktop, _and_ web apps, it's likely you'll need to adjust your @@ -539,24 +861,57 @@ things like screen size or input device. This is referred to as making an app _adaptive_ and _responsive_. + + +在创建自适应布局时最有用的 Widget 之一是 [`LayoutBuilder`][] Widget。 +`LayoutBuilder` 是 Flutter 中使用「builder」模式的众多 Widget 之一。 + 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. + + +### Builder 模式 + ### The builder pattern + + +在 Flutter 中,你会发现有几个 Widget 在其名称或构造函数中使用了「builder」一词。 +以下列表并不详尽: + 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: + + +这些不同的「builder」对于解决不同的问题很有用。 +例如,`ListView.builder` 构造函数主要用于懒加载渲染列表中的项目, +而 `Builder` Widget 则用于在深层 Widget 代码中访问 `BuildContext`。 + * [`ListView.builder`][] * [`GridView.builder`][] * [`Builder`][] * [`LayoutBuilder`][] * [`FutureBuilder`][] + + +尽管它们的用例不同, +但这些 builder 的工作方式是统一的。 +Builder Widget 和 builder 构造函数都有名为「builder」的参数 +(或类似的名称,如 `ListView.builder` 中的 `itemBuilder`), +builder 参数总是接受一个回调函数。 +这个回调函数是一个 __builder 函数__。 +Builder 函数是将数据传递给父 Widget 的回调函数, +父 Widget 使用这些参数来构建和返回子 Widget。 +Builder 函数总是至少传入一个参数——build context—— +通常至少还有一个其他参数。 + These different "builders" are useful for solving different problems. For example, the `ListView.builder` constructor is primarily used @@ -564,6 +919,13 @@ to lazily render items in a list, while the `Builder` widget is useful for gaining access to the `BuildContext` in deeply widget code. + + +例如,`LayoutBuilder` Widget 用于根据视口大小创建响应式布局。 +builder 回调函数体会接收从其父级传来的 [`BoxConstraints`][], +以及 Widget 的「BuildContext」。 +有了这些约束,你可以根据可用空间返回不同的 Widget。 + Despite their different use cases, these builders are unified by how they work. Builder widgets and builder constructors all have @@ -581,6 +943,12 @@ Builder functions always pass in at least one argument–the build context– and generally at least one other argument. + + +在以下示例中,`LayoutBuilder` 返回的 Widget +会根据视口是小于或等于 600 像素, +还是大于 600 像素而变化。 + For example, the `LayoutBuilder` widget is used to create responsive layouts based on the size of the viewport. The builder callback @@ -589,6 +957,15 @@ from its parent, along with the widgets 'BuildContext'. With these constraints, you can return a different widget based on the available space. + + +同时,`ListView.builder` 构造函数上的 `itemBuilder` 回调 +会接收 build context 和一个 `int`。 +这个回调函数会为列表中的每个项目调用一次, +int 参数表示列表项的索引。 +当 Flutter 构建 UI 时第一次调用 itemBuilder 回调时, +传递给函数的 int 是 0,第二次是 1,依此类推。 + In the following example, @@ -598,6 +975,10 @@ less than or equal 600 pixels, or greater than 600 pixels. + +这允许你根据索引提供特定的配置。 +回想一下上面使用 `ListView.builder` 构造函数的示例: + {% render "docs/code-and-image.md", image:"fwe/layout/layout_builder.png", caption: "This figure shows a narrow layout, which lays out its children vertically, and a wider layout, which lays out its children in a grid." @@ -629,10 +1010,20 @@ when Flutter is building the UI, the int passed to the function is 0, the second time it's 1, and so on. + + +此示例代码使用传入 builder 的索引 +从项目列表中获取正确的待办事项, +然后在从 builder 返回的 Widget 中显示该待办事项的数据。 + This allows you to provide specific configuration based on the index. Recall the example above using the`ListView.builder` constructor: + + +为了说明这一点,以下示例更改每隔一个列表项的背景颜色。 + ```dart final List items = Repository.fetchTodos(); @@ -656,16 +1047,35 @@ Widget build(BuildContext context) { } ``` + + +## 更多资源 + This example code uses the index that's passed into the builder to grab the correct todo from the list of items, and then displays that todo's data in the widget that is returned from the builder. + + +* 常见布局 Widget 和概念 + * 视频:[OverlayPortal—Flutter Widget of the Week][] + * 视频:[Stack—Flutter Widget of the Week][] + * 教程:[Flutter 中的布局][Layouts in Flutter] + * 文档:[Stack 文档][Stack documentation] + To exemplify this, the following example changes the background color of every other list item. + + +* 调整 Widget 大小和定位 + * 视频:[Expanded—Flutter Widget of the Week][] + * 视频:[Flexible—Flutter Widget of the Week][] + * 视频:[Intrinsic widgets—Decoding Flutter][] + {% 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`." @@ -695,8 +1105,24 @@ Widget build(BuildContext context) { ``` " %} + + +* 可滚动 Widget + * 示例代码:[处理长列表][Work with long lists] + * 示例代码:[创建水平列表][Create a horizontal list] + * 示例代码:[创建网格列表][Create a grid list] + * 视频:[ListView—Flutter Widget of the Week][] + ## Additional resources + + +* 自适应应用 + * 教程:[自适应应用 codelab][Adaptive Apps codelab] + * 视频:[MediaQuery—Flutter Widget of the Week][] + * 视频:[构建平台自适应应用][Building platform adaptive apps] + * 视频:[Builder—Flutter Widget of the Week][] + * Common layout widgets and concepts * Video: [OverlayPortal—Flutter Widget of the Week][] * Video: [Stack—Flutter Widget of the Week][] @@ -717,10 +1143,22 @@ Widget build(BuildContext context) { * Video: [Building platform adaptive apps][] * Video: [Builder—Flutter Widget of the Week][] + + +### API 参考 + ### API reference + + +以下资源解释了各个 API。 + The following resources explain individual APIs. + + +## 反馈 + * [`Builder`][] * [`Row`][] * [`Column`][] @@ -732,6 +1170,11 @@ The following resources explain individual APIs. * [`MediaQuery`][] * [`LayoutBuilder`][] + + +由于本网站的此部分正在不断发展, +我们[欢迎你的反馈][welcome your feedback]! + [Layouts in Flutter]: /ui/layout [Understanding constraints article]: /ui/layout/constraints [`RenderBox`]: {{site.api}}/flutter/rendering/RenderBox-class.html diff --git a/src/content/get-started/fundamentals/local-caching.md b/src/content/get-started/fundamentals/local-caching.md index 39df4b8279..3fcdc49cad 100644 --- a/src/content/get-started/fundamentals/local-caching.md +++ b/src/content/get-started/fundamentals/local-caching.md @@ -1,5 +1,7 @@ --- +# title: Local caching title: Local caching +# description: Learn how to persist data locally. description: Learn how to persist data locally. prev: title: Networking and data @@ -19,11 +21,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; @@ -38,31 +60,72 @@ Future get data async { } ``` + + +有许多有趣的方式可以改变此策略, +包括缓存的位置、预先向缓存写入值(或「预热」缓存)的程度等。 + 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 +133,51 @@ 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." 😄 + + +尽管存在风险,世界上几乎每个应用都大量使用数据缓存。 +本页的其余部分探讨了在 Flutter 应用中缓存数据的多种方法, +但要知道,所有这些方法都可以根据你的情况进行调整或组合。 + 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. + + +## 在本地内存中缓存数据 + ## 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,24 +185,46 @@ 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!) + + +由于其简单性,内存缓存与上面看到的伪代码非常相似。 +也就是说,最好使用经过验证的设计原则, +如[仓库模式][repository pattern],来组织你的代码, +并防止上述缓存检查出现在代码库的各处。 + 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. + + +想象一个 `UserRepository` 类,它还负责在内存中缓存用户以避免重复的网络请求。 +其实现可能如下所示: + 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); - final Api api; +final Api api; final Map _userCache = {}; - Future loadUser(int id) async { + + +* [依赖注入][dependency injection],有助于测试 +* [松耦合][loose coupling],保护周围代码免受其实现细节的影响,以及 +* [关注点分离][separation of concerns],防止其实现处理过多的关注点 + +Future loadUser(int id) async { if (!_userCache.containsKey(id)) { final response = await api.get(id); if (response.statusCode == 200) { @@ -125,23 +238,48 @@ class UserRepository { } ``` + + +最重要的是,无论用户在单个会话中访问加载给定用户的 +Flutter 应用页面多少次, +`UserRepository` 类只通过网络加载该数据*一次*。 + This `UserRepository` follows multiple proven design principles including: + + +然而,你的用户最终可能会厌倦每次重新启动应用时等待数据加载。 +为此,你应该从下面的持久缓存策略中选择一种。 + * [dependency injection][], which helps with testing * [loose coupling][], which protects surrounding code from its implementation details, and * [separation of concerns][], which prevents its implementation from juggling too many 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*. + + +在内存中缓存数据永远不会让你宝贵的缓存存活超过单个用户会话。 +要在应用的新启动中享受缓存命中的性能优势, +你需要将数据缓存在设备的硬盘上的某个位置。 + 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. + + +### 使用 `shared_preferences` 缓存数据 + [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 +287,31 @@ choose from one of the persistent caching strategies found below. ## Persistent caches + + +[`shared_preferences`][] 是一个 Flutter 插件, +它在 Flutter 的所有六个目标平台上封装了特定于平台的[键值存储][key-value storage]。 +虽然这些底层平台键值存储是为小数据量设计的, +但它们仍然适用于大多数应用的缓存策略。 +有关完整指南,请参阅我们关于使用键值存储的其他资源。 + 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. + + +* Cookbook:[在磁盘上存储键值数据][Store key-value data on disk] +* 视频:[每周包:`shared_preferences`][Package of the Week: `shared_preferences`] + ### Caching data with `shared_preferences` + + +### 使用文件系统缓存数据 + [`shared_preferences`][] is a Flutter plugin that wraps platform-specific [key-value storage][] on all six of Flutter's target platforms. @@ -165,9 +320,19 @@ 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. + + +如果你的 Flutter 应用超出了适合 `shared_preferences` 的低吞吐量场景, +你可能已准备好探索使用设备的文件系统来缓存数据。 +有关更详尽的指南,请参阅我们关于文件系统缓存的其他资源。 + * Cookbook: [Store key-value data on disk][] * Video: [Package of the Week: `shared_preferences`][] + + +* Cookbook:[读写文件][Read and write files] + [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 [`shared_preferences`]: {{site.pub-pkg}}/shared_preferences @@ -175,18 +340,42 @@ 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. + + +本地数据缓存的终极方案是使用适当的数据库来读写数据的任何策略。 +存在多种类型,包括关系型和非关系型数据库。 +所有方法都比简单文件提供了显著改进的性能——尤其是对于大型数据集。 +有关更详尽的指南,请参阅以下资源: + * Cookbook: [Read and write files][] + + +* Cookbook:[使用 SQLite 持久化数据][Persist data with SQLite] +* SQLite 替代方案:[`sqlite3` 包][`sqlite3` package] +* Drift,关系型数据库:[`drift` 包][`drift` package] +* Hive CE,非关系型数据库:[`hive_ce` 包][`hive_ce` package] +* Isar Community,快速非关系型数据库:[`isar_community` 包][`isar_community` package] +* Remote Caching,用于 API 响应的轻量级缓存系统:[`remote_caching` 包][`remote_caching` package] + [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,6 +384,13 @@ All approaches offer dramatically improved performance over simple files - especially for large datasets. For a more thorough guide, see the following resources: + + +缓存图片与缓存常规数据是类似的问题空间, +但有一个一刀切的解决方案。 +要指导你的 Flutter 应用使用文件系统来存储图片, +请使用 [`cached_network_image` 包][`cached_network_image` package]。 + * Cookbook: [Persist data with SQLite][] * SQLite alternate: [`sqlite3` package][] * Drift, a relational database: [`drift` package][] @@ -202,6 +398,10 @@ For a more thorough guide, see the following resources: * Isar Community, a fast non-relational database: [`isar_community` package][] * Remote Caching, a lightweight caching system for API responses: [`remote_caching` package][] + + +* 视频:[每周包:`cached_network_image`][Package of the Week: `cached_network_image`] + [`drift` package]: {{site.pub-pkg}}/drift [`hive_ce` package]: {{site.pub-pkg}}/hive_ce [`isar_community` package]: {{site.pub-pkg}}/isar_community @@ -212,13 +412,29 @@ 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 中。 + * Video: [Package of the Week: `cached_network_image`][] + + +状态恢复的工作原理是指示 Flutter 框架将其 Element 树中的数据 +与 Flutter 引擎同步,然后引擎将其缓存在特定于平台的存储中以供将来的会话使用。 +要在 Android 和 iOS 上的 Flutter 中启用状态恢复,请参阅以下文档: + {% comment %} TODO: My understanding is that we now recommend `Image.network` instead of cache_network_image. {% endcomment %} @@ -228,17 +444,31 @@ TODO: My understanding is that we now recommend `Image.network` instead of cache ## State restoration + + +* Android 文档:[Android 状态恢复][Android state restoration] +* iOS 文档:[iOS 状态恢复][iOS 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. + + +## 反馈 + 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: + + +由于本网站的此部分正在不断发展, +我们[欢迎你的反馈][welcome your feedback]! + * Android documentation: [Android state restoration][] * iOS documentation: [iOS state restoration][] diff --git a/src/content/get-started/fundamentals/networking.md b/src/content/get-started/fundamentals/networking.md index 717f4db488..bb43fd5776 100644 --- a/src/content/get-started/fundamentals/networking.md +++ b/src/content/get-started/fundamentals/networking.md @@ -1,5 +1,7 @@ --- +# title: Networking and data title: Networking and data +# description: Learn how to network your Flutter app. description: Learn how to network your Flutter app. prev: title: Handling user input @@ -17,13 +19,30 @@ 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`][] 包来适应基于 Dart VM 的平台 +和基于 Web 浏览器的环境之间网络访问的差异, +发起 HTTP `GET` 请求可以像下面这样简单: + ```dart import 'package:http/http.dart' as http; @@ -35,6 +54,17 @@ void main() async { } ``` + + +以下两个教程向你展示了将 [`http`][] 包添加到应用中的所有细节, +无论你是在 Android、iOS、Web 浏览器中运行, +还是在 Windows、macOS 或 Linux 上原生运行。 +第一个教程向你展示如何向网站发起未经身份验证的 `GET` 请求, +将检索到的数据解析为 `JSON`,然后显示结果数据。 +第二个教程在第一个的基础上添加身份验证头, +从而能够访问需要授权的 Web 服务器。 +Mozilla 开发者网络 (MDN) 的文章提供了更多关于 Web 授权工作原理的背景知识。 + The following two tutorials show you all of the details involved in adding the [`http`][] package to your app, whether you are running on Android, @@ -49,12 +79,31 @@ 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. + + +* 教程:[从网络获取数据][Fetch data from the internet] +* 教程:[发起经过身份验证的请求][Make authenticated requests] +* 文章:[MDN 关于网站授权的文章][MDN's article on Authorization for websites] + * 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 + + +一旦你从网络检索到数据,你需要一种方法将网络数据转换为 +你可以在 Dart 中轻松使用的内容。 +上一节中的教程使用手动编写的 Dart 代码将网络数据转换为内存表示。 +在本节中,你将看到处理此转换的其他选项。 +第一个链接到一个 YouTube 视频,展示了 [`freezed` 包][`freezed` package]的概述。 +第二个链接到一个 codelab,该 codelab 通过解析 JSON 的案例研究来介绍模式和记录。 + 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 +116,32 @@ of the [`freezed` package][]. The second links to a codelab that covers patterns and records using a case study of parsing JSON. + + +* YouTube 视频:[Freezed(每周包)][Freezed (Package of the Week)] +* Codelab:[深入了解 Dart 的模式和记录][Dive into Dart's patterns and records] + * YouTube video: [Freezed (Package of the Week)][] * Codelab: [Dive into Dart's patterns and records][] + + +## 双向传输,再次发送数据 + ## Going both ways, getting data out again + + +既然你已经掌握了检索数据的技巧,现在是时候看看如何发送数据了。 +这些信息从向网络发送数据开始,然后深入异步性。 +事实是,一旦你在网络上进行对话, +你就需要处理一个事实:物理距离较远的 Web 服务器可能需要一段时间才能响应, +而你不能在等待数据包往返时停止向屏幕渲染。 +Dart 对异步性有很好的支持,Flutter 也是如此。 +你将在一个教程中学习所有关于 Dart 支持的内容, +然后在「每周 Widget」视频中看到 Flutter 的功能介绍。 +完成后,你将学习如何使用 DevTools 的网络视图来调试网络流量。 + 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,19 +159,45 @@ Widget of the Week video. Once you complete that, you'll learn how to debug network traffic using DevTool's Network View. + + +* 教程:[向网络发送数据][Send data to the internet] +* 教程:[异步编程:futures、async、await][Asynchronous programming: futures, async, await] +* YouTube 视频:[FutureBuilder(每周 Widget)][FutureBuilder (Widget of the Week)] +* 文章:[使用网络视图][Using the Network View] + * Tutorial: [Send data to the internet][] * Tutorial: [Asynchronous programming: futures, async, await][] * YouTube video: [FutureBuilder (Widget of the Week)][] * Article: [Using the Network View][] + + +## 扩展材料 + ## Extension material + + +既然你已经掌握了使用 Flutter 的网络 API, +那么在上下文中查看 Flutter 的网络使用会很有帮助。 +第一个 codelab(表面上是关于在 Flutter 中创建自适应应用的), +使用 Dart 编写的 Web 服务器来解决 Web 浏览器的 +[跨源资源共享 (CORS) 限制][Cross-Origin Resource Sharing (CORS) restrictions]。 + 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 DevRel 校友 Fitz +讨论了数据位置对 Flutter 应用的重要性。 +最后是 Flutter GDE Anna (Domashych) Leushchenko +撰写的一系列非常有用的文章,涵盖了 Flutter 中的高级网络。 + :::note If you've already worked through this codelab on the [layout][] page, feel free to skip this step. @@ -115,11 +211,20 @@ 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. + + +* Codelab:[Flutter 中的自适应应用][Adaptive apps in Flutter] +* 视频:[保持本地:管理 Flutter 应用的数据][Keeping it local: Managing a Flutter app's data] +* 文章系列:[Dart 和 Flutter 中的基础和高级网络][Basic and advanced networking in Dart and 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][] + +## 反馈 + [Adaptive apps in Flutter]: {{site.codelabs}}/codelabs/flutter-adaptive-app [Asynchronous programming: futures, async, await]: {{site.dart-site}}/codelabs/async-await [Basic and advanced networking in Dart and Flutter]: {{site.medium}}/tide-engineering-team/basic-and-advanced-networking-in-dart-and-flutter-the-tide-way-part-0-introduction-33ac040a4a1c @@ -139,6 +244,11 @@ Anna (Domashych) Leushchenko covering advanced networking in Flutter. ## Feedback + + +由于本网站的此部分正在不断发展, +我们[欢迎你的反馈][welcome your feedback]! + As this section of the website is evolving, we [welcome your feedback][]! diff --git a/src/content/get-started/fundamentals/state-management.md b/src/content/get-started/fundamentals/state-management.md index 5150948e08..a8a195d260 100644 --- a/src/content/get-started/fundamentals/state-management.md +++ b/src/content/get-started/fundamentals/state-management.md @@ -1,5 +1,7 @@ --- +# title: State management title: State management +# description: Learn how to manage state in Flutter. description: Learn how to manage state in Flutter. prev: title: Layout @@ -15,8 +17,18 @@ 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`][] * Sharing state between widgets using constructors, [`InheritedWidget`][]s, and callbacks @@ -25,11 +37,27 @@ This page explores many aspects of state management, including: * Using Model-View-ViewModel (MVVM) for your application's architecture + + +* 使用 [`StatefulWidget`][] +* 使用构造函数、[`InheritedWidget`][] 和回调在 Widget 之间共享状态 +* 使用 [`Listenable`][] 在某些内容发生变化时通知其他 Widget +* 使用 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. + + +* 视频:[在 Flutter 中管理状态][managing-state-video]。 + 该视频展示了如何使用 [riverpod][] 包。 + Tutorial: [State management][]. This shows how to use `ChangeNotifer` with the [provider][] package. @@ -38,24 +66,48 @@ This guide doesn't use third-party packages like provider or Riverpod. Instead, it only uses primitives available in the Flutter framework. + + + 教程: +[状态管理][State management]。 +这展示了如何将 `ChangeNotifier` 与 [provider][] 包一起使用。 + ## Using a StatefulWidget + + +本指南不使用 provider 或 Riverpod 等第三方包。 +相反,它只使用 Flutter 框架中可用的基础组件。 + The simplest way to manage state is to use a `StatefulWidget`, which stores state within itself. For example, consider the following widget: + + +## 使用 StatefulWidget + ```dart class MyCounter extends StatefulWidget { const MyCounter({super.key}); - @override +@override State createState() => _MyCounterState(); } + + +管理状态最简单的方法是使用 `StatefulWidget`,它将状态存储在自身内部。 +例如,考虑以下 Widget: + class _MyCounterState extends State { int count = 0; - @override + + +此代码说明了在考虑状态管理时的两个重要概念: + +@override Widget build(BuildContext context) { return Column( children: [ @@ -74,9 +126,24 @@ class _MyCounterState extends State { } ``` + + +* **封装** +:使用 `MyCounter` 的 Widget 无法看到底层的 `count` 变量, + 也无法访问或更改它。 +* **对象生命周期** +:`_MyCounterState` 对象及其 `count` 变量 + 在 `MyCounter` 首次构建时创建, + 并一直存在直到它从屏幕上移除。 + 这是_临时状态_的一个例子。 + 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 @@ -87,24 +154,59 @@ when thinking about state management: and exist until it's removed from the screen. This is an example of _ephemeral state_. + + +* 文章:[临时状态和应用状态][ephemeral-state] +* API 文档:[StatefulWidget][] + You might find the following resources to be useful: + + +## 在 Widget 之间共享状态 + * Article: [Ephemeral state and app state][ephemeral-state] * API docs: [StatefulWidget][] + + +应用需要存储状态的一些场景包括以下几种: + ## Sharing state between widgets + + +* **更新**共享状态并通知应用的其他部分 +* **监听**共享状态的变化并在变化时重建 UI + Some scenarios where an app needs to store state include the following: + + +本节探讨如何在应用中的不同 Widget 之间有效地共享状态。 +最常见的模式有: + * 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 + + +* **使用 Widget 构造函数** + (在其他框架中有时称为「prop drilling」) +* **使用 `InheritedWidget`**(或类似的 API, + 如 [provider][] 包)。 +* **使用回调**通知父 Widget 某些内容已更改 + 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) * **Using `InheritedWidget`** (or a similar API, @@ -112,29 +214,58 @@ The most common patterns are: * **Using callbacks** to notify a parent widget that something has changed + + +由于 Dart 对象是按引用传递的, +Widget 在其构造函数中定义它们需要使用的对象是很常见的。 +你传递给 Widget 构造函数的任何状态都可以用于构建其 UI: + ### 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: + + +通过 Widget 构造函数传递应用的共享数据, +可以让任何阅读代码的人清楚地了解存在共享依赖关系。 +这是一种常见的设计模式,称为_依赖注入_, +许多框架都利用它或提供工具使其更容易实现。 + ```dart class MyCounter extends StatelessWidget { final int count; const MyCounter({super.key, required this.count}); - @override +@override Widget build(BuildContext context) { return Text('$count'); } } ``` + + +### 使用 InheritedWidget + This makes it obvious for other users of your widget to know what they need to provide in order to use it: + + +手动将数据向下传递到 Widget 树可能会很繁琐, +并导致不必要的样板代码, +因此 Flutter 提供了 _`InheritedWidget`_, +它提供了一种在父 Widget 中高效托管数据的方式, +以便子 Widget 可以访问它们而无需将它们存储为字段。 + ```dart Column( children: [ @@ -161,14 +292,32 @@ 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. + + +要使用 `InheritedWidget`,请扩展 `InheritedWidget` 类 +并使用 `dependOnInheritedWidgetOfExactType` 实现静态方法 `of()`。 +在 build 方法中调用 `of()` 的 Widget +会创建一个由 Flutter 框架管理的依赖关系, +因此当此 Widget 使用新数据重建 +且 `updateShouldNotify` 返回 true 时, +依赖于此 `InheritedWidget` 的任何 Widget 都会重建。 + ### Using InheritedWidget + + +接下来,从需要访问共享状态的 Widget 的 `build()` 方法中调用 `of()` 方法: + 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. + + +### 使用回调 + To use `InheritedWidget`, extend the `InheritedWidget` class and implement the static method `of()` using `dependOnInheritedWidgetOfExactType`. @@ -178,6 +327,12 @@ so that any widgets that depend on this `InheritedWidget` rebuild when this widget re-builds with new data and `updateShouldNotify` returns true. + + +你可以通过暴露回调来在值更改时通知其他 Widget。 +Flutter 提供了 `ValueChanged` 类型, +它声明了一个带有单个参数的函数回调: + ```dart class MyState extends InheritedWidget { const MyState({ @@ -186,18 +341,37 @@ class MyState extends InheritedWidget { required super.child, }); - final String data; +final String data; - static MyState of(BuildContext context) { + + +通过在 Widget 的构造函数中暴露 `onChanged`, +你为使用此 Widget 的任何 Widget 提供了一种方式, +以便在你的 Widget 调用 `onChanged` 时做出响应。 + +static MyState of(BuildContext context) { // This method looks for the nearest `MyState` widget ancestor. final result = context.dependOnInheritedWidgetOfExactType(); - assert(result != null, 'No MyState found in context'); - return result!; + +例如,这个 Widget 可能会处理 `onPressed` 回调, +并使用 `count` 变量的最新内部状态调用 `onChanged`: + +assert(result != null, 'No MyState found in context'); + + + +### 深入了解 + +return result!; } - @override + + +有关在 Widget 之间共享状态的更多信息,请查看以下资源: + +@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. @@ -205,15 +379,29 @@ class MyState extends InheritedWidget { } ``` + + +* 文章:[Flutter 架构概述—状态管理][architecture-state] +* 视频:[实用状态管理][Pragmatic state management] +* 视频:[InheritedWidgets][inherited-widget-video] +* 视频:[InheritedWidget 指南][A guide to Inherited Widgets] +* 示例:[Provider shopper][] +* 示例:[Provider counter][] +* API 文档:[`InheritedWidget`][] + Next, call the `of()` method from the `build()`method of the widget that needs access to the shared state: + + +## 使用 Listenable + ```dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); - @override +@override Widget build(BuildContext context) { var data = MyState.of(context).data; return Scaffold( @@ -226,12 +414,28 @@ class HomeScreen extends StatelessWidget { ``` + +既然你已经选择了如何在应用中共享状态, +那么当状态发生变化时如何更新 UI? +如何以一种能通知应用其他部分的方式来更改共享状态? + ### Using callbacks + + +Flutter 提供了一个名为 `Listenable` 的抽象类, +它可以更新一个或多个监听器。 +使用 Listenable 的一些有用方式包括: + 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: + + +* 使用 `ChangeNotifier` 并通过 `ListenableBuilder` 订阅它 +* 使用 `ValueNotifier` 配合 `ValueListenableBuilder` + ```dart typedef ValueChanged = void Function(T value); ``` @@ -240,20 +444,48 @@ 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`. + + +要使用 `ChangeNotifier`,请创建一个继承它的类, +并在该类需要通知其监听器时调用 `notifyListeners`。 + ```dart class MyCounter extends StatefulWidget { const MyCounter({super.key, required this.onChanged}); - final ValueChanged onChanged; +final ValueChanged onChanged; - @override + + +然后将其传递给 `ListenableBuilder`, +以确保每当 `ChangeNotifier` 更新其监听器时, +由 `builder` 函数返回的子树都会重建。 + +@override State createState() => _MyCounterState(); } ``` + + +[`ValueNotifier`][] 是 `ChangeNotifier` 的一个更简单的版本, +它存储单个值。 +它实现了 `ValueListenable` 和 `Listenable` 接口, +因此它与 `ListenableBuilder` 和 `ValueListenableBuilder` 等 Widget 兼容。 +要使用它,请使用初始值创建一个 `ValueNotifier` 实例: + For example, this widget might handle the `onPressed` callback, and call `onChanged` with its latest internal state for the `count` variable: + + +然后使用 `value` 字段来读取或更新值, +并通知所有监听器值已更改。 +因为 `ValueNotifier` 继承自 `ChangeNotifier`, +所以它也是一个 `Listenable`,可以与 `ListenableBuilder` 一起使用。 +但你也可以使用 `ValueListenableBuilder`, +它在 `builder` 回调中提供值: + ```dart TextButton( onPressed: () { @@ -264,9 +496,17 @@ TextButton( ### Dive deeper + + +### 深入探索 + For more information on sharing state between widgets, check out the following resources: + + +要了解更多关于 `Listenable` 对象的信息,请查看以下资源: + * Article: [Flutter Architectural Overview—State management][architecture-state] * Video: [Pragmatic state management][] * Video: [InheritedWidgets][inherited-widget-video] @@ -275,41 +515,95 @@ check out the following resources: * Sample: [Provider counter][] * API Docs: [`InheritedWidget`][] + + +* API 文档:[`Listenable`][] +* API 文档:[`ValueNotifier`][] +* API 文档:[`ValueListenable`][] +* API 文档:[`ChangeNotifier`][] +* API 文档:[`ListenableBuilder`][] +* API 文档:[`ValueListenableBuilder`][] +* API 文档:[`InheritedNotifier`][] + ## Using listenables + + +## 为应用程序架构使用 MVVM + 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? + + +既然我们已经了解了如何共享状态 +以及在状态变化时通知应用的其他部分, +我们就可以开始考虑如何组织应用中的有状态对象了。 + Flutter provides an abstract class called `Listenable` that can update one or more listeners. Some useful ways to use listenables are: + + +本节介绍如何实现一种与 Flutter 等响应式框架配合良好的设计模式, +称为 _Model-View-ViewModel_ 或 _MVVM_。 + * Use a `ChangeNotifier` and subscribe to it using a `ListenableBuilder` * Use a `ValueNotifier` with a `ValueListenableBuilder` + + +### 定义 Model + ### ChangeNotifier + + +Model 通常是一个执行底层任务的 Dart 类, +例如发起 HTTP 请求、缓存数据或管理插件等系统资源。 +Model 通常不需要导入 Flutter 库。 + To use `ChangeNotifier`, create a class that extends it, and call `notifyListeners` whenever the class needs to notify its listeners. + + +例如,考虑一个使用 HTTP 客户端加载或更新计数器状态的 Model: + ```dart class CounterNotifier extends ChangeNotifier { int _count = 0; int get count => _count; - void increment() { +void increment() { _count++; notifyListeners(); } } ``` + + +这个 Model 不使用任何 Flutter 基础组件,也不对其运行的平台做任何假设; +它的唯一工作是使用 HTTP 客户端获取或更新计数。 +这使得 Model 可以在单元测试中使用 Mock 或 Fake 来实现, +并在应用的底层组件和构建完整应用所需的高级 UI 组件之间定义清晰的边界。 + Then pass it to `ListenableBuilder` to ensure that the subtree returned by the `builder` function is re-built whenever the `ChangeNotifier` updates its listeners. + + +`CounterData` 类定义了数据的结构,是我们应用程序真正的「模型」。 +Model 层通常负责应用所需的核心算法和数据结构。 +如果你对定义 Model 的其他方式感兴趣, +例如使用不可变值类型, +可以查看 pub.dev 上的 [freezed][] 或 [build_collection][] 等包。 + ```dart Column( children: [ @@ -331,6 +625,10 @@ Column( ### ValueNotifier + + +### 定义 ViewModel + A [`ValueNotifier`][] is a simpler version of a `ChangeNotifier`, that stores a single value. It implements the `ValueListenable` and `Listenable` interfaces, @@ -338,6 +636,16 @@ so it's compatible with widgets such as `ListenableBuilder` and `ValueListenableBuilder`. To use it, create an instance of `ValueNotifier` with the initial value: + + +`ViewModel` 将 _View_ 绑定到 _Model_。 +它保护 Model 不被 View 直接访问, +并确保数据流从 Model 的变化开始。 +数据流由 `ViewModel` 处理,它使用 `notifyListeners` +来通知 View 有内容发生了变化。 +`ViewModel` 就像餐厅里的服务员, +处理厨房(Model)和顾客(View)之间的沟通。 + ```dart ValueNotifier counterNotifier = ValueNotifier(0); ``` @@ -349,6 +657,14 @@ 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: + + +请注意,当 `ViewModel` 从 Model 收到错误时, +它会存储一个 `errorMessage`。 +这保护了 View 免受未处理的运行时错误的影响, +这些错误可能导致崩溃。 +相反,`errorMessage` 字段可以被 View 用来显示用户友好的错误消息。 + ```dart Column( children: [ @@ -370,8 +686,18 @@ Column( ### Deep dive + + +### 定义 View + To learn more about `Listenable` objects, check out the following resources: + + +由于我们的 `ViewModel` 是一个 `ChangeNotifier`, +任何引用它的 Widget 都可以使用 `ListenableBuilder` +在 `ViewModel` 通知其监听器时重建其 Widget 树: + * API Docs: [`Listenable`][] * API Docs: [`ValueNotifier`][] * API Docs: [`ValueListenable`][] @@ -380,24 +706,53 @@ To learn more about `Listenable` objects, check out the following resources: * API Docs: [`ValueListenableBuilder`][] * API Docs: [`InheritedNotifier`][] + + +这种模式允许应用程序的业务逻辑与 UI 逻辑 +以及 Model 层执行的底层操作分离。 + ## Using MVVM for your application's architecture + + +## 了解更多关于状态管理的内容 + 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. + + +本页面只是触及了状态管理的表面, +因为有很多方式可以组织和管理 Flutter 应用程序的状态。 +如果你想了解更多,请查看以下资源: + This section describes how to implement a design pattern that works well with reactive frameworks like Flutter, called _Model-View-ViewModel_ or _MVVM_. + + +* 文章:[状态管理方法列表][List of state management approaches] +* 代码仓库:[Flutter 架构示例][Flutter Architecture Samples] + ### Defining the 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. + + +由于本网站的此部分正在不断发展, +我们[欢迎你的反馈][welcome your feedback]! + For example, consider a model that loads or updates the counter state using an HTTP client: @@ -407,7 +762,7 @@ import 'package:http/http.dart'; class CounterData { CounterData(this.count); - final int count; +final int count; } class CounterModel { @@ -415,14 +770,14 @@ class CounterModel { final uri = Uri.parse('https://myfluttercounterapp.net/count'); final response = await get(uri); - if (response.statusCode != 200) { +if (response.statusCode != 200) { throw ('Failed to update resource'); } - return CounterData(int.parse(response.body)); +return CounterData(int.parse(response.body)); } - Future updateCountOnServer(int newCount) async { +Future updateCountOnServer(int newCount) async { // ... } } @@ -464,7 +819,7 @@ class CounterViewModel extends ChangeNotifier { String? errorMessage; CounterViewModel(this.model); - Future init() async { +Future init() async { try { count = (await model.loadCountFromServer()).count; } catch (e) { @@ -473,7 +828,7 @@ class CounterViewModel extends ChangeNotifier { notifyListeners(); } - Future increment() async { +Future increment() async { final currentCount = count; if (currentCount == null) { throw('Not initialized'); @@ -497,7 +852,6 @@ which could lead to a crash. Instead, the `errorMessage` field can be used by the view to show a user-friendly error message. - ### Defining the View Since our `ViewModel` is a `ChangeNotifier`, diff --git a/src/content/get-started/fundamentals/user-input.md b/src/content/get-started/fundamentals/user-input.md index 73ec56bb0c..a11675741d 100644 --- a/src/content/get-started/fundamentals/user-input.md +++ b/src/content/get-started/fundamentals/user-input.md @@ -1,5 +1,7 @@ --- +# title: Handling user input title: Handling user input +# description: Learn how to handle user input in Flutter. description: Learn how to handle user input in Flutter. prev: title: State management @@ -13,8 +15,17 @@ 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,9 +33,21 @@ The resources in this section introduce you to some of the common widgets used for enabling user interaction within your app. + + +作为一个多平台 UI 框架, +用户与 Flutter 应用交互的方式有很多种。 +本节中的资源将向你介绍一些常用的 Widget, +用于在应用中启用用户交互。 + 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][]. @@ -39,12 +62,34 @@ which is covered at the end of this section. No matter which design system you choose, the principals on this page apply. ::: + + +Flutter SDK 内置了两个设计系统的预构建组件: +[Material][] 和 [Cupertino][]。 +出于教学目的,本页重点介绍 Material Widget, +这些组件是根据 [Material 3 设计语言][Material 3 design language]规范进行样式设计的。 + > **Reference**: > The [widget catalog][] has an inventory of commonly used widgets in the [Material][] and [Cupertino][] libraries. + + +[pub.dev][](Dart 和 Flutter 的包仓库)上的 Flutter 社区 +创建并支持其他设计语言,如 [Fluent UI][]、[macOS UI][] 等。 +如果现有的设计系统组件不太符合你的需求, +Flutter 允许你构建自己的自定义 Widget, +这将在本节末尾介绍。 +无论你选择哪种设计系统,本页上的原则都适用。 +::: + Next, we'll cover a few of the Material widgets that support common use cases for handling user input in your Flutter app. + + +> **参考**: +> [Widget 目录][widget catalog]包含 [Material][] 和 [Cupertino][] 库中常用 Widget 的清单。 + [scrolling]: /get-started/fundamentals/layout#scrolling-widgets [pub.dev]: {{site.pub}} [Layouts]: /get-started/fundamentals/layout @@ -57,12 +102,27 @@ use cases for handling user input in your Flutter app. ## Buttons + + +接下来,我们将介绍一些 Material Widget, +它们支持 Flutter 应用中处理用户输入的常见用例。 + ![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. - `FilledButton`: A filled button that should be used for @@ -83,14 +143,37 @@ but styled differently for various use cases, including: - `FloatingActionButton`: An icon button that hovers over content to promote a primary action. + + +- `ElevatedButton`:具有一定深度的按钮。使用凸起按钮为原本大部分平坦的布局增加层次感。 +- `FilledButton`:填充按钮,应用于完成流程的重要、最终操作, + 如**保存**、**立即加入**或**确认**。 +- `Tonal Button`:介于 `FilledButton` 和 `OutlinedButton` 之间的按钮。 + 在低优先级按钮需要比轮廓更多强调的情况下很有用,如**下一步**。 +- `OutlinedButton`:带有文本和可见边框的按钮。 + 这些按钮包含重要的操作,但不是应用中的主要操作。 +- `TextButton`:没有边框的可点击文本。 + 由于文本按钮没有可见边框,它们必须依靠相对于其他内容的位置来提供上下文。 +- `IconButton`:带有图标的按钮。 +- `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 @@ -104,12 +187,25 @@ 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", caption: "This figure shows an ElevatedButton with the text \"Enabled\" being clicked." @@ -136,12 +232,21 @@ Widget build(BuildContext context) { " %} + +> **练习**: +> 完成这个教程,学习如何构建一个「收藏」按钮: +> [为你的 Flutter 应用添加交互性][Add interactivity to your Flutter app] +
      > **Checkpoint**: > Complete this tutorial that teaches you how to build a > "favorite" button: [Add interactivity to your Flutter app][] + + + **API 文档**:[`ElevatedButton`][] • [`FilledButton`][] • [`OutlinedButton`][] • [`TextButton`][] • [`IconButton`][] • [`FloatingActionButton`][] +
      **API Docs**: [`ElevatedButton`][] • [`FilledButton`][] • [`OutlinedButton`][] • [`TextButton`][] • [`IconButton`][] • [`FloatingActionButton`][] @@ -157,14 +262,33 @@ Widget build(BuildContext context) { ## Text + + +## 文本 + Several widgets support text input. + + +有几个 Widget 支持文本输入。 + ### `SelectableText` + + +Flutter 的 `Text` Widget 在屏幕上显示文本, +但不允许用户高亮或复制文本。 +`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. + + +> **视频**: +> [SelectableText(每周 Widget)][SelectableText (Widget of the Week)] + {% 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 +310,31 @@ From forth the fatal loins of these two foes'''); > **Video**: > [SelectableText (Widget of the Week)][] + + +`RichText` 让你可以在应用中显示富文本字符串。 +`TextSpan` 与 `RichText` 类似,允许你使用不同的文本样式显示文本的各个部分。 +它不是用于处理用户输入的,但如果你允许用户编辑和格式化文本,它会很有用。 + [SelectableText (Widget of the Week)]: {{site.youtube-site}}/watch?v=ZSU3ZXOs6hc ### `RichText` + + +> **视频**: +> [Rich Text(每周 Widget)][Rich Text (Widget of the Week)] + `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. + + +> **代码**: +> [Rich Text Editor 代码][Rich Text Editor code] + {% 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,20 +360,53 @@ Widget build(BuildContext context) { > **Video**: > [Rich Text (Widget of the Week)][] + + +`TextField` 让用户可以使用硬件或屏幕键盘在文本框中输入文本。 + > **Code**: > [Rich Text Editor code][] + + +`TextField` 有许多不同的属性和配置。以下是一些重点: + [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` + + +- `InputDecoration` 决定文本字段的外观,如颜色和边框。 +- `controller`:`TextEditingController` 控制正在编辑的文本。 + 为什么可能需要控制器?默认情况下,你的应用用户可以在文本字段中输入, + 但如果你想以编程方式控制 `TextField` 并清除其值,例如,你将需要一个 `TextEditingController`。 +- `onChanged`:当用户更改文本字段的值时(例如插入或删除文本时)触发此回调函数。 +- `onSubmitted`:当用户表示他们已完成编辑字段中的文本时触发此回调; + 例如,当文本字段获得焦点时按下「回车」键。 + A `TextField` lets users enter text in text box using a hardware or onscreen keyboard. + + +该类支持其他可配置属性,例如 `obscureText`(将输入的每个字母转换为圆点) +和 `readOnly`(阻止用户更改文本)。 + `TextField`s have many different properties and configurations. A few of the highlights: + + +> **练习**: +> 完成这个 4 部分的 cookbook 系列,它将引导你创建文本字段、 +> 获取其值并更新你的应用状态: +> 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] + - `InputDecoration` determines the text field's appearance, such as color and border. - `controller`: A `TextEditingController` controls the text being edited. @@ -247,10 +420,18 @@ A few of the highlights: they are done editing the text in the field; for example, by tapping the "enter" key when the text field is in focus. + + +### 表单 + 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. + + +`Form` 是一个可选的容器,用于将多个表单字段 Widget(如 `TextField`)组合在一起。 + {% 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." @@ -272,6 +453,13 @@ Widget build(BuildContext context) { ``` " %} + + +每个单独的表单字段都应该包装在 `FormField` Widget 中, +并以 `Form` Widget 作为共同的祖先。 +有一些便捷 Widget 会为你预先将表单字段 Widget 包装在 `FormField` 中。 +例如,`TextField` 的 `Form` Widget 版本是 `TextFormField`。 + > **Checkpoint**: > Complete this 4-part cookbook series that walks > you through how to create a text field, @@ -281,6 +469,12 @@ Widget build(BuildContext context) { > 1. [Handle changes to a text field][] > 1. [Focus and text fields][]. + + +使用 `Form` 可以访问 `FormState`, +它允许你保存、重置和验证从该 `Form` 继承的每个 `FormField`。 +你还可以提供一个 `GlobalKey` 来标识特定的表单,如以下代码所示: + [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 +482,40 @@ Widget build(BuildContext context) { ### Form + + +> **练习**: +> 完成本教程,学习如何[构建带有验证的表单][build a form with validation]。 + `Form` is an optional container for grouping together multiple form field widgets, such as `TextField`. + + +> **演示**: +> [表单应用][Form app] + 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`. + + +> **代码**: +> [表单应用代码][Form app code] + 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: + + + **API 文档**:[`TextField`][] • [`RichText`][] • [`SelectableText`][] • [`Form`][] + ```dart final GlobalKey _formKey = GlobalKey(); @@ -342,15 +555,32 @@ Widget build(BuildContext context) { } ``` + + +## 从一组选项中选择一个值 + > **Checkpoint**: > Complete this tutorial to learn how to [build a form with validation][]. + + +为用户提供从多个选项中进行选择的方式。 + > **Demo**: > [Form app][] + + +`SegmentedButton` 允许用户从 2-5 个项目的小组中进行选择。 + > **Code**: > [Form app code][] + + +数据类型 `` 可以是内置类型,如 `int`、`String`、`bool` 或枚举。 +`SegmentedButton` 有几个相关属性: +
      **API Docs**: [`TextField`][] • [`RichText`][] • [`SelectableText`][] • [`Form`][] @@ -365,36 +595,90 @@ Widget build(BuildContext context) { ## Select a value from a group of options + + +- `segments`,一个 `ButtonSegment` 列表,每个代表用户可以选择的一个「分段」或选项。 + 在视觉上,每个 `ButtonSegment` 可以有图标、文本标签或两者兼有。 + Provide a way to users to select from several options. + + +- `multiSelectionEnabled` 指示是否允许用户选择多个选项。此属性默认为 false。 + ### SegmentedButton + + +- `selected` 标识当前选中的值。 + **注意:** `selected` 的类型是 `Set`,所以如果你只允许用户选择一个值, + 该值必须作为只有单个元素的 `Set` 提供。 + `SegmentedButton` allows users to select from a minimal group of 2-5 items. + + +- `onSelectionChanged` 回调在用户选择任何分段时触发。 + 它提供已选分段的列表,以便你可以更新应用状态。 + The data type, ``, can be a built-in type such as `int`, `String`, `bool` or an enum. A `SegmentedButton` has a few relevant properties: + + +- 其他样式参数允许你修改按钮的外观。 + 例如,`style` 接受一个 `ButtonStyle`,提供了配置 `selectedIcon` 的方式。 + - `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. + + +### Chip(标签) + - `multiSelectionEnabled` indicates whether the user is allowed to select multiple options. This property defaults to false. + + +`Chip` 是一种紧凑的方式,用于在特定上下文中表示属性、文本、实体或操作。 +针对特定用例存在专门的 `Chip` Widget: + - `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. + + +- [InputChip][] 以紧凑的形式表示复杂的信息,如实体(人、地点或事物)或会话文本。 +- [ChoiceChip][] 允许从一组选项中进行单选。选择标签包含相关的描述性文本或类别。 +- [FilterChip][] 使用标签或描述性词语来过滤内容。 +- [ActionChip][] 表示与主要内容相关的操作。 + - 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. + + +每个 `Chip` Widget 都需要一个 `label`。 +它可以选择性地拥有一个 `avatar`(如图标或用户的头像) +和一个 `onDeleted` 回调,该回调显示一个删除图标,触发时会删除该标签。 +`Chip` Widget 的外观也可以通过设置一些可选参数来自定义, +如 `shape`、`color` 和 `iconTheme`。 + - Additional styling parameters allow you to modify the button's appearance. For example, `style` takes a `ButtonStyle`, providing a way to configure a `selectedIcon`. + + +你通常会使用 `Wrap`(一个在多个水平或垂直行中显示其子组件的 Widget), +以确保你的标签会换行而不会在应用边缘被截断。 + {% 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 @@ -410,6 +694,10 @@ enum Calendar { day, week, month, year } // StatefulWidget... Calendar calendarView = Calendar.day; + + +### `DropdownMenu`(下拉菜单) + @override Widget build(BuildContext context) { return SegmentedButton( @@ -445,12 +733,29 @@ Widget build(BuildContext context) { " %} + +`DropdownMenu` 允许用户从选项菜单中选择一个选项,并将所选文本放入 `TextField` 中。 +它还允许用户根据文本输入来过滤菜单项。 + ### 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: + + +- `dropdownMenuEntries` 提供一个 `DropdownMenuEntry` 列表,描述每个菜单项。 + 菜单可能包含文本标签、前导图标或尾随图标等信息。(这也是唯一必需的参数。) +- `TextEditingController` 允许以编程方式控制 `TextField`。 +- `onSelected` 回调在用户选择选项时触发。 +- `initialSelection` 允许你配置默认值。 +- 还有其他参数可用于自定义 Widget 的外观和行为。 + - [InputChip][] represents a complex piece of information, such as an entity (person, place, or thing), or conversational text, in a compact form. @@ -459,6 +764,11 @@ Specialized `Chip` widgets exist for specific use cases: - [FilterChip][] uses tags or descriptive words to filter content. - [ActionChip][] represents an action related to primary content. + + +> **视频**: +> [DropdownMenu(每周 Widget)][DropdownMenu (Widget of the Week)] + 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 +776,18 @@ 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`. + + +### Slider(滑块) + 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. + + +`Slider` Widget 允许用户通过移动指示器来调整值,例如音量条。 + {% 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 @@ -520,15 +838,30 @@ Widget build(BuildContext context) { [FilterChip]: {{site.api}}/flutter/material/FilterChip-class.html [ActionChip]: {{site.api}}/flutter/material/ActionChip-class.html - ### `DropdownMenu` + + +`Slider` Widget 的配置参数: + 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. + + +- `value` 表示滑块的当前值 +- `onChanged` 是移动滑块时触发的回调 +- `min` 和 `max` 设置滑块允许的最小和最大值 +- `divisions` 设置用户可以沿轨道移动滑块的离散间隔 + Configuration parameters include the following: + + +> **视频**: +> [Slider、RangeSlider、CupertinoSlider(每周 Widget)][Slider, RangeSlider, CupertinoSlider (Widget of the Week)] + - `dropdownMenuEntries` provides a list of `DropdownMenuEntry`s that describes each menu item. The menu might contain information such as a text label, and @@ -540,6 +873,10 @@ Configuration parameters include the following: - Additional parameters are also available for customizing the widget's look and behavior. + + + **API 文档:** [`SegmentedButton`][] • [`DropdownMenu`][] • [`Slider`][] • [`Chip`][] + {% render "docs/code-and-image.md", image:"fwe/user-input/dropdownmenu.webp", caption: "This figure shows a DropdownMenu widget with 5 value options. Each @@ -556,11 +893,15 @@ enum ColorLabel { orange('Orange', Colors.orange), grey('Grey', Colors.grey); - const ColorLabel(this.label, this.color); +const ColorLabel(this.label, this.color); final String label; final Color color; } + + +## 在值之间切换 + // StatefulWidget... @override Widget build(BuildContext context) { @@ -595,18 +936,43 @@ Widget build(BuildContext context) { ``` " %} + + +你的 UI 可以通过多种方式允许在值之间切换。 + > **Video**: > [DropdownMenu (Widget of the Week)][] + + +### Checkbox、Switch 和 Radio + [DropdownMenu (Widget of the Week)]: {{site.youtube-site}}/watch?v=giV9AbM2gd8?si=E23hjg72cjMTe_mz ### Slider + + +提供切换单个值开关的选项。 +这些 Widget 背后的功能逻辑是相同的, +因为所有 3 个都建立在 `ToggleableStateMixin` 之上, +尽管每个都提供略微不同的呈现方式: + The `Slider` widget lets a user adjust a value by moving an indicator, such as a volume bar. + + +- `Checkbox` 是一个容器,为 false 时为空,为 true 时填充复选标记。 +- `Switch` 有一个滑块,为 false 时在左侧,为 true 时滑动到右侧。 +- `Radio` 与 `Checkbox` 类似,是一个容器,为 false 时为空,为 true 时被填充。 + Configuration parameters for the `Slider` widget: + + +`Checkbox` 和 `Switch` 的配置包含: + - `value` represents the slider's current value - `onChanged` is the callback that gets triggered when the handle is moved - `min` and `max` establish minimum and maximum values allowed by the slider @@ -614,6 +980,10 @@ Configuration parameters for the `Slider` widget: handle along the track. + +- 一个 `true` 或 `false` 的 `value` +- 以及一个 `onChanged` 回调,当用户切换 Widget 时触发 + {% render "docs/code-and-image.md", image:"fwe/user-input/slider.webp", caption: "This figure shows a slider widget with a value ranging from 0.0 to 5.0 @@ -642,9 +1012,18 @@ Widget build(BuildContext context) { ``` " %} + + +### Radio(单选按钮) + > **Video**: > [Slider, RangeSlider, CupertinoSlider (Widget of the Week)][] + + +`RadioGroup` 包含 `Radio` 按钮,允许用户在互斥的值之间进行选择。 +当用户在组中选择一个单选按钮时,其他单选按钮将被取消选择。 +
      **API Docs:** [`SegmentedButton`][] • [`DropdownMenu`][] • [`Slider`][] • [`Chip`][] @@ -657,15 +1036,34 @@ Widget build(BuildContext context) { ## Toggle between values + + +- 特定 `Radio` 按钮的 `value` 表示该按钮的值。 +- `RadioGroup` 的选中值由 `groupValue` 参数标识。 +- `RadioGroup` 有一个 `onChanged` 回调,像 `Switch` 和 `Checkbox` 一样,当用户点击时触发。 + There are several ways that your UI can allow toggling between values. + + +#### 附加:CheckboxListTile 和 SwitchListTile + ### Checkbox, Switch, and Radio + + +这些便捷 Widget 与复选框和开关 Widget 相同,但支持标签(作为 `ListTile`)。 + 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.: + + +> **视频**: +> [CheckboxListTile(每周 Widget)][CheckboxListTile (Widget of the Week)] + - `Checkbox` is a container that is empty when false or filled with a checkmark when true. - `Switch` has a handle that is on the left when false and @@ -673,14 +1071,33 @@ each provides slight presentation differences.: - `Radio` is similar to a `Checkbox` in that it's a container that is empty when false, but filled in when true. + + +> **视频**: +> [SwitchListTile(每周 Widget)][SwitchListTile (Widget of the Week)] + The configuration for `Checkbox` and `Switch` contain: + + + **API 文档**: +[`Checkbox`][] • [`CheckboxListTile`][] • [`Switch`][] • [`SwitchListTile`][] • +[`Radio`][] + - a `value` that is `true` or `false` - and an `onChanged` callback which is triggered when the user toggles the widget + + +## 选择日期或时间 + ### Checkbox + + +提供了 Widget 以便用户可以选择日期和时间。 + {% render "docs/code-and-image.md", image:"fwe/user-input/checkbox.webp", caption: "This figure shows a checkbox being checked and unchecked." @@ -705,8 +1122,19 @@ Widget build(BuildContext context) { ``` " %} + + +有一组对话框使用户能够选择日期或时间,如以下各节所示。 +除了不同的日期类型(日期使用 `DateTime`,时间使用 `TimeOfDay`)外, +这些对话框的功能类似,你可以通过提供以下内容来配置它们: + ### Switch + + +- 默认的 `initialDate` 或 `initialTime` +- 或决定显示哪种选择器 UI 的 `initialEntryMode` + {% 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." @@ -734,19 +1162,41 @@ Widget build(BuildContext context) { ``` " %} + + +### DatePickerDialog(日期选择器对话框) + ### Radio + + +此对话框允许用户选择一个日期或日期范围。 +通过调用 `showDatePicker` 函数激活,该函数返回一个 `Future`, +所以不要忘记等待异步函数调用! + 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. + + +### TimePickerDialog(时间选择器对话框) + - A particular `Radio` button's `value` represent that button's value. - The selected value for a `RadioGroup` is identified by the `groupValue` parameter. - `RadioGroup` has an `onChanged` callback that gets triggered when users click it, like `Switch` and `Checkbox`. + + +`TimePickerDialog` 是一个呈现时间选择器的对话框。 +可以通过调用 `showTimePicker()` 函数来激活它。 +与返回 `Future` 不同, +`showTimePicker` 返回的是 `Future`。 +同样,不要忘记等待函数调用! + {% 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 @@ -760,20 +1210,41 @@ enum Character { musician, chef, firefighter, artist } class RadioExample extends StatefulWidget { const RadioExample({super.key}); - @override + + + **API 文档:** +[`showDatePicker`][] • [`showTimePicker`][] + +@override State createState() => _RadioExampleState(); } + + +## 滑动与拖动 + class _RadioExampleState extends State { Character? _character = Character.musician; - void setCharacter(Character? value) { + + +`Dismissible` 是一个允许用户通过滑动来关闭的 Widget。 +它有许多配置参数,包括: + +void setCharacter(Character? value) { setState(() { _character = value; }); } - @override + + +- 一个 `child` Widget +- 一个当用户滑动时触发的 `onDismissed` 回调 +- 样式参数,如 `background` +- 还需要包含一个 `key` 对象,以便它们可以与 Widget 树中的同级 `Dismissible` Widget 唯一区分开来 + +@override Widget build(BuildContext context) { return RadioGroup( groupValue: _character, @@ -804,11 +1275,26 @@ class _RadioExampleState extends State { ``` " %} + + +> **视频**: +> [Dismissible(每周 Widget)][Dismissible (Widget of the Week)] + #### Bonus: CheckboxListTile & SwitchListTile + + +> **练习**: +> 完成本教程,学习如何使用 Dismissible Widget [实现滑动删除][implement swipe to dismiss]。 + These convenience widgets are the same checkbox and switch widgets, but support a label (as a `ListTile`). + + + **API 文档:** +[`Dismissible`][] + {% render "docs/code-and-image.md", image:"fwe/user-input/SpecialListTiles.webp", caption: "This figure shows a column containing a CheckboxListTile and @@ -851,12 +1337,27 @@ Widget build(BuildContext context) { ``` " %} + + +## 寻找更多 Widget? + > **Video**: > [CheckboxListTile (Widget of the Week)][] + + +本页仅介绍了一些你可以在 Flutter 应用中用于处理用户输入的常见 Material Widget。 +查看 [Material Widget 库][Material Widget library]和 +[Material 库 API 文档][Material Library API docs]以获取完整的 Widget 列表。 + > **Video**: > [SwitchListTile (Widget of the Week)][] + + +> **演示**: +> 查看 Flutter 的 [Material 3 Demo][],了解 Material 库中可用的用户输入 Widget 精选示例。 +
      **API Docs**: @@ -874,24 +1375,57 @@ Widget build(BuildContext context) { ## Select a date or time + + +如果 Material 和 Cupertino 库没有满足你需求的 Widget, +请查看 [pub.dev][] 以找到 Flutter 和 Dart 社区拥有和维护的包。 +例如,[`flutter_slidable`][] 包提供了一个比上一节描述的 +`Dismissible` Widget 更可定制的 `Slidable` Widget。 + Widgets are provided so the user can select a date and time. + + +> **视频**: +> [flutter_slidable(每周包)][flutter_slidable (Package of the Week)] + 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: + + +## 使用 GestureDetector 构建交互式 Widget + - a default `initialDate` or `initialTime` - or an `initialEntryMode` that determines the picker UI that's displayed. + + +你是否已经搜遍了 Widget 库、pub.dev,询问了你的编程朋友, +仍然找不到符合你所寻找的用户交互的 Widget? +你可以构建自己的自定义 Widget,并使用 `GestureDetector` 使其具有交互性。 + ### DatePickerDialog + + +> **练习**: +> 使用这个教程作为起点,创建你自己的_自定义_按钮 Widget, +> 使其能够[处理点击][handle taps]。 + 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! + + +> **视频**: +> [GestureDetector(每周 Widget)][GestureDetector (Widget of the Week)] + {% render "docs/code-and-image.md", image:"fwe/user-input/DatePicker.webp", caption: "This figure shows a DatePicker that is displayed when the @@ -907,7 +1441,13 @@ DateTime? selectedDate; Widget build(BuildContext context) { var date = selectedDate; - return Column(children: [ + + +> **参考**: +> 查看[点击、拖动和其他手势][Taps, drags, and other gestures], +> 其中解释了如何在 Flutter 中监听和响应手势。 + +return Column(children: [ Text( date == null ? "You haven't picked a date yet." @@ -924,7 +1464,14 @@ Widget build(BuildContext context) { lastDate: DateTime(2050), ); - setState(() { + + +> **附加视频**: +> 想知道 Flutter 的 `GestureArena` 如何将原始用户交互数据转换为 +> 人类可识别的概念(如点击、拖动和捏合)吗? +> 观看这个视频:[GestureArena(解码 Flutter)][GestureArena (Decoding Flutter)] + +setState(() { selectedDate = pickedDate; }); }, @@ -935,14 +1482,29 @@ Widget build(BuildContext context) { ``` " %} + + +### 不要忘记无障碍功能! + ### TimePickerDialog + + +如果你正在构建自定义 Widget, +请使用 `Semantics` Widget 标注其含义。 +它为屏幕阅读器和其他基于语义分析的工具提供描述和元数据。 + `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! + + +> **视频**: +> [Semantics(每周 Flutter Widget)][Semantics (Flutter Widget of the Week)] + {% render "docs/code-and-image.md", image:"fwe/user-input/TimePicker.webp", caption: "This figure shows a TimePicker that is displayed when the @@ -958,7 +1520,12 @@ TimeOfDay? selectedTime; Widget build(BuildContext context) { var time = selectedTime; - return Column(children: [ + + + **API 文档**: +[`GestureDetector`][] • [`Semantics`][] + +return Column(children: [ Text( time == null ? "You haven't picked a time yet." : time.format(context), ), @@ -971,7 +1538,11 @@ Widget build(BuildContext context) { initialTime: TimeOfDay.now(), ); - setState(() { + + +## 测试 + +setState(() { selectedTime = pickedTime; }); }, @@ -982,6 +1553,11 @@ Widget build(BuildContext context) { ``` " %} + + +在你完成应用中的用户交互构建后, +不要忘记编写测试以确保一切按预期工作! + :::tip Calling `showDatePicker()` and `showTimePicker()` is equivalent to calling `showDialog()` with `DatePickerDialog()` and @@ -1003,17 +1579,37 @@ on to the `Navigator` stack. ## Swipe & slide + + +这些教程将引导你编写模拟应用中用户交互的测试: + ### [`Dismissible`][] + + +> **练习**: +> 按照[点击、拖动和输入文本][tap, drag, and enter text] cookbook 文章, +> 学习如何使用 `WidgetTester` 在应用中模拟和测试用户交互。 + A `Dismissible` is a widget that enables users to dismiss it by swiping. It has a number of configuration parameters, including: + + +> **附加教程**: +> [处理滚动][handle scrolling] cookbook 教程向你展示如何通过使用 Widget 测试 +> 滚动列表来验证 Widget 列表包含预期内容。 + - A `child` widget - An `onDismissed` callback that is triggered when the user swipes - Styling parameters such as `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. + + +## 下一步:网络 + {% 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 @@ -1052,13 +1648,30 @@ Widget build(BuildContext context) { ``` " %} + + +本页是处理用户输入的介绍。 +既然你已经知道如何处理应用用户的输入, +你可以通过添加外部数据使你的应用更加有趣。 +在下一节中,你将学习如何通过网络为你的应用获取数据、 +如何在 JSON 之间转换数据、身份验证以及其他网络功能。 + > **Video**: > [Dismissible (Widget of the Week)][] + + +## 反馈 + > **Checkpoint**: > Complete this tutorial on how to [implement swipe to dismiss][] using the > dismissible widget. + + +由于本网站的此部分正在不断发展, +我们[欢迎你的反馈][welcome your feedback]! +
      **API Docs:** @@ -1135,7 +1748,6 @@ other semantic analysis-based tools. > **Video**: > [Semantics (Flutter Widget of the Week)][] -
      **API Docs**: diff --git a/src/content/get-started/quick.md b/src/content/get-started/quick.md index 4140be2bc2..964e6b93c1 100644 --- a/src/content/get-started/quick.md +++ b/src/content/get-started/quick.md @@ -1,6 +1,11 @@ --- +# title: Set up and test drive Flutter title: Set up and test drive Flutter +# shortTitle: Quick start shortTitle: Quick start +# 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: >- 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! @@ -12,10 +17,22 @@ 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. + + +了解如何使用任何基于 OSS 的编辑器(如 VS Code) +来配置你的 Flutter 开发环境, +并体验 Flutter 的开发流程。 + If you've developed with Flutter before, or you prefer to use a different editor or IDE, you can follow the [custom setup instructions][] instead. + + +如果你之前使用过 Flutter, +或者你更喜欢使用其他编辑器或 IDE, +可以改为按照[自定义配置说明][custom setup instructions]进行操作。 + :::note What you'll achieve - Install the software prerequisites for Flutter. @@ -23,147 +40,339 @@ you can follow the [custom setup instructions][] instead. - Create a new Flutter app from a sample template. - Try out Flutter development features like stateful hot reload. + + +- 安装 Flutter 所需的软件。 +- 使用 VS Code 下载并安装 Flutter。 +- 从示例模板创建一个新的 Flutter 应用。 +- 体验 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. - 1.

      Set up Linux support

      - If you haven't set up Linux support on your Chromebook before, + +为了顺利配置 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]. - If you've already turned on Linux support, + + +如果你之前没有在 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. - 1.

      Download and install prerequisite packages

      - Using `apt-get` or your preferred installation mechanism, + +如果你已经开启了 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: - - `curl` + + +使用 `apt-get` 或你喜欢的安装方式, + 安装以下软件包的最新版本: + +- `curl` - `git` - `unzip` - `xz-utils` - `zip` - `libglu1-mesa` - If you want to use `apt-get`, + + +如果你想使用 `apt-get`, + 请使用以下命令安装这些软件包: + +If you want to use `apt-get`, install these packages using the following commands: - ```console + + +

      下载并安装 Visual Studio Code

      + +```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

      +1.

      Download and install Visual Studio Code

      + + - To quickly install Flutter, then edit and debug your apps, +为了快速安装 Flutter 并编辑调试你的应用, + 请[安装并配置 Visual Studio Code][vscode-install]。 + +To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. - You can instead install and use any other Code OSS-based editor + + +你也可以安装和使用其他支持 VS Code 扩展的基于 Code OSS 的编辑器。 + 如果你选择这样做,在本文的其余部分, + 请将 VS Code 理解为你选择的编辑器。 + +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. + + +

      安装 git

      + {: .steps .chromeos-only} - 1.

      Install git

      - **If you already have git installed, skip to the next + +**如果你已经安装了 git,请跳到下一步:下载并安装 Visual Studio Code。** + +1.

      Install git

      + + + +在 Mac 上安装 git 有几种方式, + 但我们推荐使用 XCode。 + 当你的构建目标是 iOS 或 macOS 时,这一点很重要。 + +**If you already have git installed, skip to the next step: Download and install Visual Studio Code.** - There are a few ways to install git on your Mac, + + +如果你之前没有安装过这些工具, + 会弹出一个对话框确认你是否要安装。 + 点击 **Install**,安装完成后点击 **Done**。 + +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. - ```console + + +

      下载并安装 Visual Studio Code

      + +```console $ xcode-select --install ``` - If you haven't installed the tools already, +If you haven't installed the tools already, a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**. - 1.

      Download and install Visual Studio Code

      - To quickly install Flutter, then edit and debug your apps, + +为了快速安装 Flutter 并编辑调试你的应用, + 请[安装并配置 Visual Studio Code][vscode-install]。 + +1.

      Download and install Visual Studio Code

      + + + +你也可以安装和使用其他支持 VS Code 扩展的基于 Code OSS 的编辑器。 + 如果你选择这样做,在本文的其余部分, + 请将 VS Code 理解为你选择的编辑器。 + +To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. - You can instead install and use any other Code OSS-based editor + + +

      安装 Git for Windows

      + +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. + + +下载并安装最新版本的 [Git for Windows][]。 + {: .steps .macos-only} - 1.

      Install Git for Windows

      - Download and install the latest version of [Git for Windows][]. - For help installing or troubleshooting Git, +如需安装帮助或排查 Git 问题, + 请参阅 [Git 文档][git-install]。 + +1.

      Install Git for Windows

      + + + +

      下载并安装 Visual Studio Code

      + +Download and install the latest version of [Git for Windows][]. + + + +为了快速安装 Flutter 并编辑调试你的应用, + 请[安装并配置 Visual Studio Code][vscode-install]。 + +For help installing or troubleshooting Git, reference the [Git documentation][git-install]. - 1.

      Download and install Visual Studio Code

      - To quickly install Flutter, then edit and debug your apps, + +你也可以安装和使用其他支持 VS Code 扩展的基于 Code OSS 的编辑器。 + 如果你选择这样做,在本文的其余部分, + 请将 VS Code 理解为你选择的编辑器。 + +1.

      Download and install Visual Studio Code

      + + + +

      下载并安装必备软件包

      + +To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. - You can instead install and use any other Code OSS-based editor + + +使用你喜欢的包管理器或安装方式, + 安装以下软件包的最新版本: + +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. + + +在使用 `apt-get` 的基于 Debian 的发行版(如 Ubuntu)上, + 请使用以下命令安装这些软件包: + {: .steps .windows-only} - 1.

      Download and install prerequisite packages

      - Using your preferred package manager or mechanism, + +

      下载并安装 Visual Studio Code

      + +1.

      Download and install prerequisite packages

      + + + +为了快速安装 Flutter 并编辑调试你的应用, + 请[安装并配置 Visual Studio Code][vscode-install]。 + +Using your preferred package manager or mechanism, install the latest versions of the following packages: - - `curl` + + +你也可以安装和使用其他支持 VS Code 扩展的基于 Code OSS 的编辑器。 + 如果你选择这样做,在本文的其余部分, + 请将 VS Code 理解为你选择的编辑器。 + +- `curl` - `git` - `unzip` - `xz-utils` - `zip` - `libglu1-mesa` - On Debian-based distros with `apt-get`, such as Ubuntu, + + +## 安装并配置 Flutter {: #install} + +On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands: - ```console + + +既然你已经安装了 Git 和 VS Code, +请按照以下步骤使用 VS Code 安装并配置 Flutter。 + +```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

      +1.

      Download and install Visual Studio Code

      + + - To quickly install Flutter, then edit and debug your apps, +如果你更喜欢手动安装 Flutter, +请按照[手动安装 Flutter][Install Flutter manually]中的说明操作。 + +To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code][vscode-install]. - You can instead install and use any other Code OSS-based editor + + +

      启动 VS Code

      + +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. + + +如果尚未打开,可以使用 Spotlight 搜索 VS Code, + 或从安装目录手动打开。 + {: .steps .linux-only} + + +

      为 VS Code 添加 Flutter 扩展

      + [chromeos-linux]: https://support.google.com/chromebook/answer/9145439 [chromeos-linux-update]: https://support.google.com/chromebook/answer/9145439?hl=en#:~:text=Fix%20problems%20with%20Linux [Git for Windows]: https://git-scm.com/downloads/win @@ -172,185 +381,471 @@ first install the following tools. ## Install and set up Flutter {: #install} + + +要为 VS Code 添加 Dart 和 Flutter 扩展, + 请访问 [Flutter 扩展的商店页面][flutter-vscode], + 然后点击 **Install**。 + 如果浏览器提示,请允许其打开 VS Code。 + Now that you've installed Git and VS Code, follow these steps to use VS Code to install and set up Flutter. + + +

      使用 VS Code 安装 Flutter

      + :::note Download manually If you prefer to manually install Flutter, follow the instructions in [Install Flutter manually][]. ::: - 1.

      Launch VS Code

      +1.

      Launch VS Code

      - If not already open, open VS Code by searching for it with Spotlight + + +在 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. - 1.

      Add the Flutter extension to VS Code

      - To add the Dart and Flutter extensions to VS Code, + +转到 **View** > **Command Palette** + 或按 Cmd/Ctrl + + Shift + P。 + +1.

      Add the Flutter extension to VS Code

      + + + +在命令面板中输入 `flutter`。 + +To add the Dart and Flutter extensions to VS Code, visit the [Flutter extension's marketplace page][flutter-vscode], then click **Install**. If prompted by your browser, allow it to open VS Code. - 1.

      Install Flutter with VS Code

      - 1. Open the command palette in VS Code. - Go to **View** > **Command Palette** +选择 **Flutter: New Project**。 + +1.

      Install Flutter with VS Code

      + + + +VS Code 会提示你在计算机上定位 Flutter SDK。 + 选择 **Download SDK**。 + +1. Open the command palette in VS Code. + + + +当 **Select Folder for Flutter SDK** 对话框出现时, + 选择你想要安装 Flutter 的位置。 + +Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. - 1. In the command palette, type `flutter`. - 1. Select **Flutter: New Project**. - 1. VS Code prompts you to locate the Flutter SDK on your computer. +点击 **Clone Flutter**。 + +1. In the command palette, type `flutter`. + + + +下载 Flutter 时,VS Code 会显示以下弹出通知: + +1. Select **Flutter: New Project**. + + + +下载需要几分钟时间。 + 如果你怀疑下载已卡住,请点击 **Cancel**,然后重新开始安装。 + +1. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**. - 1. When the **Select Folder for Flutter SDK** dialog displays, + + +点击 **Add SDK to PATH**。 + +1. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter. - 1. Click **Clone Flutter**. - While downloading Flutter, VS Code displays this pop-up notification: - ```console +成功后会显示以下通知: + +1. Click **Clone Flutter**. + + + +VS Code 可能会显示 Google Analytics 通知。 + +While downloading Flutter, VS Code displays this pop-up notification: + + + +如果你同意,请点击 **OK**。 + +```console Downloading the Flutter SDK. This may take a few minutes. ``` - This download takes a few minutes. +This download takes a few minutes. If you suspect that the download has hung, click **Cancel** then start the installation again. - 1. Click **Add SDK to PATH**. - When successful, a notification displays: - ```console +为确保 Flutter 在所有终端中可用: + +1. Click **Add SDK to PATH**. + + + +关闭并重新打开所有终端窗口。 + +When successful, a notification displays: + + + +重启 VS Code。 + +```console The Flutter SDK was added to your PATH ``` - 1. VS Code might display a Google Analytics notice. +1. VS Code might display a Google Analytics notice. + + + +

      排查安装问题

      + +If you agree, click **OK**. + + + +如果你在安装过程中遇到任何问题, + 请查看 [Flutter 安装故障排查][troubleshoot]。 - If you agree, click **OK**. +1. To ensure that Flutter is available in all terminals: - 1. To ensure that Flutter is available in all terminals: - 1. Close, then reopen all terminal windows. + +## 体验 Flutter {: #test-drive} + +1. Close, then reopen all terminal windows. 1. Restart VS Code. - {:type="a"} - 1.

      Troubleshoot installation issues

      - If you encounter any issues during installation, +既然你已经配置好了 VS Code 和 Flutter, +现在是时候创建一个应用并体验 Flutter 开发了! + +{:type="a"} + + + +

      创建一个新的 Flutter 应用

      + +1.

      Troubleshoot installation issues

      + + + +在 VS Code 中打开命令面板。 + +If you encounter any issues during installation, check out [Flutter installation troubleshooting][troubleshoot]. + + +转到 **View** > **Command Palette** + 或按 Cmd/Ctrl + + Shift + P。 + {:.steps} + + +在命令面板中,开始输入 `flutter:`。 + [Install Flutter manually]: /install/manual [flutter-vscode]: https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [troubleshoot]: /install/troubleshoot ## Test drive Flutter {: #test-drive} + + +VS Code 应该会显示来自 Flutter 插件的命令。 + Now that you've set up VS Code and Flutter, it's time to create an app and try out Flutter development! - 1.

      Create a new Flutter app

      - 1. Open the command palette in VS Code. - Go to **View** > **Command Palette** +选择 **Flutter: New Project** 命令。 + +1.

      Create a new Flutter app

      + + + +你的操作系统或 VS Code 可能会请求访问你的文档, + 同意后继续下一步。 + +1. Open the command palette in VS Code. + + + +选择 **Application** 模板。 + +Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. - 1. In the command palette, start typing `flutter:`. - VS Code should surface commands from the Flutter plugin. - 1. Select the **Flutter: New Project** command. +VS Code 会提示你 **Which Flutter template?**。 + 选择 **Application** 来创建一个简单的计数器应用。 + +1. In the command palette, start typing `flutter:`. + + + +创建或选择新应用文件夹的父目录。 - Your OS or VS Code might ask for access to your documents, +VS Code should surface commands from the Flutter plugin. + + + +应该会出现一个文件对话框。 + +1. Select the **Flutter: New Project** command. + + + +选择或创建你想要创建项目的父目录。 + +Your OS or VS Code might ask for access to your documents, agree to continue to the next step. - 1. Choose the **Application** template. - VS Code should prompt you with **Which Flutter template?**. + +要确认你的选择, + 请点击 **Select a folder to create the project in**。 + +1. Choose the **Application** template. + + + +为你的应用输入一个名称。 + +VS Code should prompt you with **Which Flutter template?**. Choose **Application** to bootstrap a simple counter app. - 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 +VS Code 会提示你为新应用输入名称。 + 输入 `trying_flutter` 或类似的 `lowercase_with_underscores` 格式名称。 + 要确认你的选择,请按 Enter。 + +1. Create or select the parent directory for your new app's folder. + + + +等待项目初始化完成。 + +A file dialog should appear. + + + +任务进度通常会在右下角显示为通知, + 也可以从 **Output** 面板查看。 + +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**. - 1. Enter a name for your app. - VS Code should prompt you to enter a name for your new app. + +打开 `lib` 目录,然后打开 `main.dart` 文件。 + +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. - 1. Wait for project initialization to complete. - Task progress is often surfaced as a notification in the bottom right + +

      在 Web 上运行你的应用

      + +1. Wait for project initialization to complete. + + + +虽然 Flutter 应用可以在多个平台上运行, + 但先尝试在 Web 上运行你的新应用。 + +Task progress is often surfaced as a notification in the bottom right and can also be accessed from the **Output** panel. - 1. Open the `lib` directory, then the `main.dart` file. - If you're curious about what each portion of the code does, + +在 VS Code 中打开命令面板。 + +1. Open the `lib` directory, then the `main.dart` file. + + + +转到 **View** > **Command Palette** + 或按 Cmd/Ctrl + + Shift + P。 + +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

      - While Flutter apps can run on many platforms, + +在命令面板中,开始输入 `flutter:`。 + +1.

      Run your app on the web

      + + + +VS Code 应该会显示来自 Flutter 插件的命令。 + +While Flutter apps can run on many platforms, try running your new app on the web. - 1. Open the command palette in VS Code. - Go to **View** > **Command Palette** + +选择 **Flutter: Select Device** 命令。 + +1. Open the command palette in VS Code. + + + +在 **Select Device** 提示中,选择 **Chrome**。 + +Go to **View** > **Command Palette** or press Cmd/Ctrl + Shift + P. - 1. In the command palette, start typing `flutter:`. - VS Code should surface commands from the Flutter plugin. - 1. Select the **Flutter: Select Device** command. +运行或开始调试你的应用。 + +1. In the command palette, start typing `flutter:`. + + + +转到 **Run** > + **Start Debugging** 或按 F5。 + +VS Code should surface commands from the Flutter plugin. + + + +`flutter run` 用于构建和启动你的应用, + 然后会打开一个新的 Chrome 实例并开始运行你新创建的应用。 + +1. Select the **Flutter: Select Device** command. + + + +

      尝试热重载

      + +1. From the **Select Device** prompt, select **Chrome**. - 1. From the **Select Device** prompt, select **Chrome**. - 1. Run or start debugging your app. - Go to **Run** > +Flutter 通过**有状态热重载**提供快速的开发周期, + 它可以在不重启或丢失应用状态的情况下重新加载正在运行的应用代码。 + +1. Run or start debugging your app. + + + +你可以更改应用的源代码, + 在 VS Code 中运行热重载命令, + 然后在运行中的应用中查看更改。 + +Go to **Run** > **Start Debugging** or press F5. - `flutter run` is used to build and start your app, + + +在运行的应用中,尝试通过点击 + ![增加 (+)][increment-button]{: .text-icon} 按钮来增加计数器的值几次。 + +`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. - 1.

      Try hot reload

      - Flutter offers a fast development cycle with **stateful hot reload**, + +在应用仍在运行的情况下,修改 `lib/main.dart` 文件。 + +1.

      Try hot reload

      + + + +将 `_incrementCounter` 方法中的 `_counter++` 行改为递减 `_counter` 字段。 + +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. - You can change your app's source code, + + +保存你的更改(**File** > **Save All**) + 或点击 **Hot Reload** ![热重载图标][hot reload icon]{: .text-icon} 按钮。 + +You can change your app's source code, run the hot reload command in VS Code, then see the change in your running app. - 1. In the running app, try adding to the counter a few times by + + +Flutter 会在不丢失任何现有状态的情况下更新正在运行的应用。 + 注意现有的值保持不变。 + +1. In the running app, try adding to the counter a few times by clicking the ![increment (+)][increment-button]{: .text-icon} button. - 1. With your app still running, make a change in the `lib/main.dart` file. - Change the `_counter++` line in the `_incrementCounter` method + +尝试再次点击 ![增加 (+)][increment-button]{: .text-icon} 按钮。 + 注意值现在是递减而不是递增。 + +1. With your app still running, make a change in the `lib/main.dart` file. + + + +

      探索 Flutter 侧边栏

      + +Change the `_counter++` line in the `_incrementCounter` method to instead decrement the `_counter` field. - ```dart diff + + +Flutter 插件为 VS Code 添加了一个专用侧边栏, + 用于管理 Flutter 调试会话和设备、 + 查看代码和 widget 的大纲, + 以及访问 Dart 和 Flutter DevTools。 + +```dart diff setState(() { // ... - _counter++; @@ -358,50 +853,107 @@ it's time to create an app and try out Flutter development! }); ``` - 1. Save your changes +1. Save your changes (**File** > **Save All**) or click the **Hot Reload** ![hot reload icon][]{: .text-icon} button. - Flutter updates the running app without losing any existing state. + + +如果你的应用没有在运行,请重新开始调试。 + +Flutter updates the running app without losing any existing state. Notice the existing value stayed the same. - 1. Try clicking the + + +转到 **Run** > + **Start Debugging** 或按 F5。 + +1. Try clicking the ![increment (+)][increment-button]{: .text-icon} button again. Notice the value decreases instead of increases. - 1.

      Explore the Flutter sidebar

      - The Flutter plugin adds a dedicated sidebar to VS Code + +在 VS Code 中打开 Flutter 侧边栏。 + +1.

      Explore the Flutter sidebar

      + + + +可以通过 VS Code 侧边栏中的 Flutter ![Flutter 标志][Flutter logo]{: .text-icon} 按钮打开, + 也可以通过命令面板运行 **Flutter: Focus on Flutter Sidebar View** 命令来打开。 + +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. - 1. If your app isn't running, start debugging it again. - Go to **Run** > + +在 Flutter 侧边栏中的 **DevTools** 下, + 点击 **Flutter Inspector** 按钮。 + +1. If your app isn't running, start debugging it again. + + + +VS Code 中应该会打开一个单独的 **Widget Inspector** 面板。 + +Go to **Run** > **Start Debugging** or press F5. - 1. Open the Flutter sidebar in VS Code. - Either open it with the Flutter ![Flutter logo][]{: .text-icon} button in + +在 widget 检查器中,你可以查看应用的 widget 树、 + 查看每个 widget 的属性和布局等。 + +1. Open the Flutter sidebar in VS Code. + + + +在 widget 检查器中,尝试点击顶层的 `MyHomePage` widget。 + +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. - 1. In the Flutter sidebar, under **DevTools**, + + +应该会打开其属性和布局的视图, + VS Code 编辑器应该会导航到并聚焦包含该 widget 的那一行。 + +1. In the Flutter sidebar, under **DevTools**, click the **Flutter Inspector** button. - A separate **Widget Inspector** panel should open in VS Code. - In the widget inspector, you can view your app's widget tree, + +探索并尝试 widget 检查器和 Flutter 侧边栏中的其他功能。 + +A separate **Widget Inspector** panel should open in VS Code. + + + +## 继续你的 Flutter 之旅 {: #next-steps} + +In the widget inspector, you can view your app's widget tree, view the properties and layout of each widget, and more. - 1. In the widget inspector, try clicking the top-level `MyHomePage` widget. - A view of its properties and layout should open, and + +**恭喜!** +现在你已经安装并体验了 Flutter, +可以按照[构建你的第一个应用][Building your first app]的 codelab 继续学习, +为[其他目标平台][additional target platform]配置开发环境, +或者探索以下资源继续你的 Flutter 学习之旅。 + +1. In the widget inspector, try clicking the top-level `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. - 1. Explore and try out other features in +1. Explore and try out other features in the widget inspector and Flutter sidebar. {:.steps} diff --git a/src/content/jobs/index.md b/src/content/jobs/index.md index f651679d2e..a62867172f 100644 --- a/src/content/jobs/index.md +++ b/src/content/jobs/index.md @@ -1,14 +1,34 @@ --- +# title: Flutter and Dart team job openings title: Flutter and Dart team job openings +# shortTitle: Open jobs shortTitle: Open jobs +# description: Open job listings for the Flutter and Dart teams. description: Open job listings for the Flutter and Dart teams. --- ## Current openings + + +## 当前开放职位 + #### Sunnyvale, CA, USA + + +#### 美国加利福尼亚州森尼韦尔 + * [Tech Writer II](/jobs/tech_writer_ii) + + +* [技术写作 II 级](/jobs/tech_writer_ii) + The Flutter and Dart SWE teams aren't currently hiring. Thanks for your interest! + + + +Flutter 和 Dart 软件工程团队目前暂不招聘。 +感谢你的关注! diff --git a/src/content/jobs/tech_writer_ii.md b/src/content/jobs/tech_writer_ii.md index c876594f1b..1cb44c7409 100644 --- a/src/content/jobs/tech_writer_ii.md +++ b/src/content/jobs/tech_writer_ii.md @@ -1,6 +1,8 @@ --- +# title: Technical Writer, Languages title: Technical Writer, Languages showToc: false +# description: Learn about and apply to this technical writer role! description: Learn about and apply to this technical writer role! --- @@ -11,12 +13,26 @@ https://www.google.com/about/careers/applications/jobs/results/95463419917804230 ## About the team + + +## 关于团队 + This full time position is in the Frameworks and Languages team, part of Developer Relations. As such, this team is dedicated to educating developers who use our products, including Dart, Flutter, and Go. + + +这是一个全职职位,隶属于开发者关系部门的框架和语言团队。 +该团队致力于为使用我们产品的开发者提供教育资源, +包括 Dart、Flutter 和 Go。 + ## About the position + + +## 关于职位 + Technical writers plan, create, and maintain educational content as an integral part of the engineering or user experience. The content is often in the form of documentation, but may also be UI text, @@ -24,6 +40,15 @@ sample code, videos, or other educational material. Regardless of the content medium, technical writers are distinguished by their abilities to explain complex topics in a way that’s useful to their audience. + + +技术写作人员负责规划、创建和维护教育内容, +作为工程或用户体验的重要组成部分。 +内容通常以文档的形式呈现,但也可能是 UI 文本、 +示例代码、视频或其他教育材料。 +无论内容媒介如何,技术写作人员的特点在于 +他们能够以对受众有用的方式解释复杂的主题。 + The US base salary range for this full-time position is $87,000-$125,000 + bonus + equity + benefits. Our salary ranges are determined by role, level, and location. Within the range, individual pay is determined by @@ -31,15 +56,39 @@ work location and additional factors, including job-related skills, experience, and relevant education or training. Your recruiter can share more about the specific salary range for your preferred location during the hiring process. + + +该全职职位在美国的基本工资范围为 $87,000-$125,000 +外加奖金、股权和福利。我们的薪资范围由职位、 +级别和地点决定。在该范围内,个人薪酬由 +工作地点和其他因素决定,包括与工作相关的技能、 +经验以及相关教育或培训。你的招聘人员可以在招聘过程中 +分享更多关于你首选地点的具体薪资范围信息。 + Please note that the compensation details listed in US role postings reflect the base salary only, and do not include bonus, equity, or benefits. Learn more about [benefits at Google](https://www.google.com/about/careers/applications/benefits/). + + +请注意,美国职位发布中列出的薪酬详情仅反映基本工资, +不包括奖金、股权或福利。 +了解更多关于 +[Google 福利](https://www.google.com/about/careers/applications/benefits/)的信息。 + ## Our values + + +## 我们的价值观 + ### Mentorship + + +### 导师制度 + Upon joining Google, you will be paired with a formal mentor, who will help guide you in the process of ramping up, forging relationships, and learning the systems you'll need to do your job. @@ -48,8 +97,22 @@ as you navigate your career at Google. In addition to formal mentors, we work and train together so that we are always learning from one another, and we celebrate and support the career progression of our team members. + + +加入 Google 后,你将被配对一位正式导师, +他将帮助指导你快速上手、建立关系, +并学习你完成工作所需的系统。 +你的经理也可以帮助你找到导师, +在你的 Google 职业生涯中指导你。除了正式导师外, +我们一起工作和培训,以便我们始终相互学习, +我们也庆祝和支持团队成员的职业发展。 + ### Inclusion + + +### 包容性 + Here on the Flutter team and at Google, we embrace our differences and are [committed to furthering our culture of inclusion](https://flutter.dev/culture). In addition to groups like the [Flutteristas](https://flutteristas.org/), @@ -58,8 +121,22 @@ are employee-initiated networks for supporting underrepresented employees and their allies with shared values of creating belonging across their communities and Google. + + +在 Flutter 团队和 Google,我们拥抱我们的差异, +并[致力于推进我们的包容性文化](https://flutter.dev/culture)。 +除了 [Flutteristas](https://flutteristas.org/) 等组织外, +[员工资源组 (ERGs)](https://diversity.google/commitments/) +是员工发起的网络,旨在支持代表性不足的员工 +及其盟友,共同创造归属感, +跨越他们的社区和 Google。 + ### Work-Life balance + + +### 工作与生活平衡 + Our team also puts a high value on work-life balance. Striking a healthy balance between your personal and professional life is crucial to your happiness and success here, which is why we aren't focused @@ -67,12 +144,33 @@ on how many hours you spend at work or online. Instead, we're happy to offer a flexible schedule so you can have a more productive and well-balanced life—both in and outside of work. + + +我们的团队也非常重视工作与生活的平衡。 +在你的个人生活和职业生活之间取得健康的平衡 +对你在这里的幸福和成功至关重要,这就是为什么我们不关注 +你在工作中或在线上花费多少时间。相反, +我们很乐意提供灵活的时间表,以便你可以拥有更高效和 +更平衡的生活——无论是在工作中还是工作之外。 + ## Job location + + +## 工作地点 + Sunnyvale, CA, USA + + +美国加利福尼亚州森尼韦尔 + ## Job responsibilities + + +## 工作职责 + Execute assigned tasks, including issue triage, pull request management, content verification, and feedback review, under executive guidance. Write clear, concise, and organized content for defined projects, @@ -83,10 +181,30 @@ Collaborate with team members and subject matter experts. Demonstrate basic knowledge of user information needs and content creation processes. Maintain a foundational understanding of the product domain and technical writing principles. + + +在执行指导下执行分配的任务,包括 issue 分类、PR 管理、 +内容验证和反馈审查。 +为明确的项目编写清晰、简洁和有条理的内容, +满足用户需求并遵守风格指南。 +校对、编辑和重组现有文档,以增强 +开发者体验并确保风格一致性。 +与团队成员和相关领域专家合作。 +展示对用户信息需求和内容创建流程的基本了解。 +保持对产品领域和技术写作原则的基础理解。 + ## Qualifications + + +## 任职资格 + ### Minimum qualifications + + +### 最低资格 + * Bachelor's degree in a relevant field, or equivalent practical experience. * Experience creating content for technical audiences (for example, developer documentation, Computer Science course material, @@ -94,12 +212,39 @@ Maintain a foundational understanding of the product domain and technical writin * Experience with one or more programming languages (Python, Java, C++, Dart, Go, etc). + + +* 相关领域的学士学位,或同等实践经验。 +* 具有为技术受众创建内容的经验 + (例如,开发者文档、计算机科学课程材料 + 或 IT 管理手册)。 +* 具有一种或多种编程语言的经验 + (Python、Java、C++、Dart、Go 等)。 + ### Preferred qualifications + + +### 优先资格 + * 1 year of experience in technical writing. * Experience creating content for technical audiences. * Interest in programming languages such as Dart or Go. + + +* 1 年技术写作经验。 +* 具有为技术受众创建内容的经验。 +* 对 Dart 或 Go 等编程语言感兴趣。 + ## To apply + + +## 申请方式 + Please apply by [filling out the following form](https://flutter.dev/go/job). + + + +请通过[填写以下表格](https://flutter.dev/go/job)申请。 diff --git a/src/content/resources/ads-overview.md b/src/content/resources/ads-overview.md index 69a210350a..d08024392d 100644 --- a/src/content/resources/ads-overview.md +++ b/src/content/resources/ads-overview.md @@ -1,5 +1,9 @@ --- +# title: Ads overview title: Ads overview +# description: > +# Learn about the resources available for adding +# ads to your Flutter app. description: > Learn about the resources available for adding ads to your Flutter app. @@ -8,25 +12,57 @@ showBreadcrumbs: false ![adding ads](/assets/images/docs/add-ads.png) + + +![添加广告](/assets/images/docs/add-ads.png) + The Google Mobile Ads SDK for Flutter works with both AdMob and Ad Manager. It supports a variety of ads formats such as app open, banner, interstitial, native, rewarded, and rewarded interstitial formats, and features like mediation. + + +适用于 Flutter 的 Google Mobile Ads SDK 可与 +AdMob 和 Ad Manager 配合使用。它支持多种广告格式, +如开屏广告、横幅广告、插页广告、原生广告、 +激励广告和激励插页广告, +以及 mediation 等功能。 + Easily integrate Google AdMob and Ad Manager ads directly in your Flutter App + + +轻松将 Google AdMob 和 Ad Manager 广告 +直接集成到你的 Flutter 应用中。 + The the following resources can help get you started: + + +以下资源可以帮助你入门: + * [Add ads to your mobile Flutter app or game][] (cookbook recipe) * [Adding AdMob ads to a Flutter app][] (codelab) * [Adding an AdMob banner and native inline ads to a Flutter app][] (codelab) * [How to support Google Mobile Ads (AdMob) mediation][mediation] (guide) + + +* [为你的移动 Flutter 应用或游戏添加广告][Add ads to your mobile Flutter app or game](实用教程) +* [将 AdMob 广告添加到 Flutter 应用][Adding AdMob ads to a Flutter app](codelab) +* [将 AdMob 横幅广告和原生内嵌广告添加到 Flutter 应用][Adding an AdMob banner and native inline ads to a Flutter app](codelab) +* [如何支持 Google Mobile Ads (AdMob) mediation][mediation](指南) + Listed in alphabetical order, the following packages support AdMob mediation: + + +以下按字母顺序列出的 package 支持 AdMob mediation: + * AppLovin ([gma_mediation_applovin][]) * DT Exchange ([gma_mediation_dtexchange][]) * InMobi ([gma_mediation_inmobi][]) diff --git a/src/content/resources/ai-overview.md b/src/content/resources/ai-overview.md index 5006792764..4f9180715a 100644 --- a/src/content/resources/ai-overview.md +++ b/src/content/resources/ai-overview.md @@ -1,5 +1,9 @@ --- +# title: AI title: AI +# description: > +# Learn about the resources available for adding +# generative AI to your Flutter application. description: > Learn about the resources available for adding generative AI to your Flutter application. @@ -12,29 +16,64 @@ The Google AI Dart SDK enables you to use Google's state-of-the-art generative AI models (like Gemini) to build AI-powered features and applications. + + +Google AI Dart SDK 让你能够使用 Google +最先进的生成式 AI 模型(如 Gemini) +来构建 AI 驱动的功能和应用。 + Integrate features like advanced text generation, summarization, chat, and more to your Dart or Flutter apps with minimal setup, and deploy across multiple platforms. + + +将高级文本生成、摘要、聊天等功能 +以最少的设置集成到你的 Dart 或 Flutter 应用中, +并部署到多个平台。 + The **NEW** [Flutter AI Toolkit][] shows you how to integrate an AI generated chatbot into your Flutter app. **Check it out!** + + +**全新** [Flutter AI Toolkit][] 向你展示如何将 +AI 生成的聊天机器人集成到你的 Flutter 应用中。 +**快来体验吧!** + [Flutter AI Toolkit]: /ai-toolkit Other resources include: + + +其他资源包括: + * [Get started with the Gemini API in Dart or Flutter apps][tutorial] * [Google Generative AI SDK for Dart and Flutter][pkg] + + +* [在 Dart 或 Flutter 应用中开始使用 Gemini API][tutorial] +* [适用于 Dart 和 Flutter 的 Google Generative AI SDK][pkg] + [pkg]: {{site.pub-pkg}}/google_generative_ai [tutorial]: https://ai.google.dev/gemini-api/docs/get-started/dart Also, check out the following videos from Google I/O 2024: + + +另外,请查看以下来自 Google I/O 2024 的视频: + * [Build generative AI agents with Vertex AI Agent Builder and Flutter][vertex-video] * [Gemini API and Flutter: Practical, AI-driven apps with Google AI tools][gemini-video] + +* [使用 Vertex AI Agent Builder 和 Flutter 构建生成式 AI 代理][vertex-video] +* [Gemini API 和 Flutter:使用 Google AI 工具构建实用的 AI 驱动应用][gemini-video] + [gemini-video]: {{site.youtube-site}}/watch?v=B1RKFL6ASts [vertex-video]: {{site.youtube-site}}/watch?v=V8P_S9OLI_I diff --git a/src/content/resources/courses.md b/src/content/resources/courses.md index 4e96d6587d..50a08f6580 100644 --- a/src/content/resources/courses.md +++ b/src/content/resources/courses.md @@ -1,5 +1,7 @@ --- +# title: Online courses title: Online courses +# description: An index of online courses teaching Flutter development. description: An index of online courses teaching Flutter development. showBreadcrumbs: false --- @@ -10,6 +12,14 @@ up-to-date information, such as null-safe Dart code. These courses are listed alphabetically. To include your course, [submit a PR][]: + + +通过这些视频课程学习如何构建 Flutter 应用。 +在注册课程之前,请确认它包含最新信息, +例如空安全的 Dart 代码。 +这些课程按字母顺序排列。 +要添加你的课程,请[提交 PR][submit a PR]: + * [20 Hour Dart & Flutter YT Course For Beginners][] by Rivaan Ranawat * [Best Dart Course][] by Tadas Petra & Robert Brunhage * [Best Flutter Course][] by Tadas Petra & Robert Brunhage diff --git a/src/content/resources/games-toolkit.md b/src/content/resources/games-toolkit.md index 0aa612db53..88b52718df 100644 --- a/src/content/resources/games-toolkit.md +++ b/src/content/resources/games-toolkit.md @@ -1,5 +1,8 @@ --- +# title: Casual Games Toolkit title: Casual Games Toolkit +# description: >- +# Learn about free & open source multiplatform 2D game development in Flutter. description: >- Learn about free & open source multiplatform 2D game development in Flutter. showBreadcrumbs: false @@ -8,25 +11,54 @@ showBreadcrumbs: false The Flutter Casual Games Toolkit pulls together new and existing resources so you can accelerate development of games on mobile platforms. + + +Flutter 休闲游戏工具包整合了新的和现有的资源, +让你能够加速移动平台上的游戏开发。 + :::recommend Check out the latest [game updates and resources for Flutter 3.22](#updates)! ::: This page outlines where you can find these available resources. + + +本页面概述了你可以在哪里找到这些可用资源。 + ## Why Flutter for games? + + +## 为什么选择 Flutter 来开发游戏? + The Flutter framework can create performant apps for six target platforms from the desktop to mobile devices to the web. + + +Flutter 框架可以为六个目标平台创建高性能应用, +从桌面端到移动设备再到 Web。 + With Flutter's benefits of cross-platform development, performance, and open source licensing, it makes a great choice for games. + + +凭借 Flutter 跨平台开发、高性能和开源许可的优势, +它是开发游戏的绝佳选择。 + Casual games fall into two categories: turn-based games and real-time games. You might be familiar with both types of games, though perhaps you didn't think about them in quite this way. + + +休闲游戏分为两类:回合制游戏和实时游戏。 +你可能对这两种类型的游戏都很熟悉, +尽管你可能没有以这种方式去思考它们。 + _Turn-based games_ cover games meant for a mass market with simple rules and gameplay. This includes board games, card games, puzzles, and strategy games. @@ -34,6 +66,15 @@ These games respond to simple user input, like tapping on a card or entering a number or letter. These games are well suited for Flutter. + + +**回合制游戏** 涵盖面向大众市场的游戏, +具有简单的规则和玩法。 +这包括棋盘游戏、纸牌游戏、益智游戏和策略游戏。 +这些游戏响应简单的用户输入, +比如点击一张牌或输入一个数字或字母。 +这些游戏非常适合用 Flutter 开发。 + _Real-time games_ cover games a series of actions require real time responses. These include endless runner games, racing games, and so on. You might want to create a game with advanced features like collision detection, @@ -41,17 +82,44 @@ camera views, game loops, and the like. These types of games could use an open source game engine like the [Flame game engine][] built using Flutter. + + +**实时游戏** 涵盖需要实时响应一系列动作的游戏。 +这包括无尽奔跑游戏、赛车游戏等。 +你可能想要创建一个具有高级功能的游戏, +如碰撞检测、摄像机视角、游戏循环等。 +这些类型的游戏可以使用开源游戏引擎, +比如使用 Flutter 构建的 [Flame 游戏引擎][Flame game engine]。 + ## What's included in the toolkit + + +## 工具包包含什么 + The Casual Games Toolkit provides the following free resources. + + +休闲游戏工具包提供以下免费资源。 + * A repository that includes three new game templates that provide a starting point for building a casual game. - 1. A [base game template][basic-template] + + +* 一个仓库,包含三个新的游戏模板, + 为构建休闲游戏提供起点。 + +1. A [base game template][basic-template] that includes the basics for: - * Main menu + + +1. [基础游戏模板][basic-template], + 包含以下基础功能: + +* Main menu * Navigation * Settings * Level selection @@ -60,45 +128,112 @@ The Casual Games Toolkit provides the following free resources. * Sound * Themes - 1. A [card game template][card-template] + + +* 主菜单 + * 导航 + * 设置 + * 关卡选择 + * 玩家进度 + * 游戏会话管理 + * 声音 + * 主题 + +1. A [card game template][card-template] that includes everything in the base template plus: - * Drag and drop + + +1. [纸牌游戏模板][card-template], + 包含基础模板中的所有内容,以及: + +* Drag and drop * Game state management * Multiplayer integration hooks - 1. An [endless runner template][runner-template] created in partnership + + +* 拖放功能 + * 游戏状态管理 + * 多人游戏集成钩子 + +1. An [endless runner template][runner-template] created in partnership with the open source game engine, Flame. It implements: - * A FlameGame base template + + +1. [无尽奔跑模板][runner-template], + 与开源游戏引擎 Flame 合作创建。它实现了: + +* A FlameGame base template * Player steering * Collision detection * Parallax effects * Spawning * Different visual effects - 1. A sample game built on top of the endless runner template, + + +* FlameGame 基础模板 + * 玩家控制 + * 碰撞检测 + * 视差效果 + * 生成系统 + * 不同的视觉效果 + +1. A sample game built on top of the endless runner template, called SuperDash. You can play the game on iOS, Android, or [web][], [view the open source code repo][], or [read how the game was created in 6 weeks][]. + + +1. 一个基于无尽奔跑模板构建的示例游戏, + 名为 SuperDash。你可以在 iOS、Android + 或 [Web][web] 上玩这个游戏,[查看开源代码仓库][view the open source code repo], + 或[阅读游戏是如何在 6 周内创建的][read how the game was created in 6 weeks]。 + * Developer guides for integrating needed services. * A link to a [Flame Discord][game-discord] channel. If you have a Discord account, use this [direct link][discord-direct]. + + +* 用于集成所需服务的开发者指南。 +* [Flame Discord][game-discord] 频道的链接。 + 如果你有 Discord 账号,可以使用这个[直接链接][discord-direct]。 + The included game templates and cookbook recipes make certain choices to accelerate development. They include specific packages, like `provider`, `google_mobile_ads`, `in_app_purchase`, `audioplayers`, `crashlytics`, and `games_services`. If you prefer other packages, you can change the code to use them. + + +包含的游戏模板和实用教程做出了某些选择以加速开发。 +它们包含特定的 package,如 `provider`、`google_mobile_ads`、 +`in_app_purchase`、`audioplayers`、`crashlytics` 和 `games_services`。 +如果你更喜欢其他 package,可以更改代码来使用它们。 + The Flutter team understands that monetization might be a future consideration. Cookbook recipes for advertising and in-app purchases have been added. + + +Flutter 团队理解变现可能是未来的考虑因素。 +已添加广告和应用内购买的实用教程。 + As explained on the [Games][] page, you can leverage up to $900 in offers when you integrate Google services, such as [Cloud, Firebase][], and [Ads][], into your game. + + +正如 [Games][] 页面所解释的, +当你将 Google 服务(如 [Cloud、Firebase][Cloud, Firebase] +和 [Ads][])集成到游戏中时,你可以获得高达 900 美元的优惠。 + :::important You must connect your Firebase and GCP accounts to use credits for Firebase services and verify your business email during sign up to earn @@ -108,22 +243,48 @@ For the Ads offer, [check your region's eligibility][]. ## Get started + + +## 开始使用 + Are you ready? To get started: + + +准备好了吗?开始吧: + 1. If you haven't done so, [install Flutter][]. 1. [Clone the games repo][game-repo]. 1. Review the `README` file for the first type of game you want to create. - * [basic game][basic-template-readme] + + +1. 如果你还没有安装,请先[安装 Flutter][install Flutter]。 +1. [克隆游戏仓库][game-repo]。 +1. 查看你想要创建的第一种游戏类型的 `README` 文件。 + +* [basic game][basic-template-readme] * [card game][card-template-readme] * [runner game][runner-template-readme] + + +* [基础游戏][basic-template-readme] + * [纸牌游戏][card-template-readme] + * [奔跑游戏][runner-template-readme] + 1. [Join the Flame community on Discord][game-discord] (use the [direct link][discord-direct] if you already have a Discord account). 1. Review the codelabs and cookbook recipes. - * {{recipeIcon}} Build a [multiplayer game][multiplayer-recipe] with Cloud Firestore. + + +1. [加入 Discord 上的 Flame 社区][game-discord] + (如果你已有 Discord 账号,请使用[直接链接][discord-direct])。 +1. 查看 codelab 和实用教程。 + +* {{recipeIcon}} Build a [multiplayer game][multiplayer-recipe] with Cloud Firestore. * {{codelab}} Build a [word puzzle][] with Flutter.—**NEW** * {{codelab}} Build a [2D physics game][] with Flutter and Flame.—**NEW** * {{codelab}} [Add sound and music][] to your Flutter game with SoLoud.—**NEW** @@ -136,34 +297,88 @@ Are you ready? To get started: * Collect analytics about crashes and errors inside your game with {{recipeIcon}} [Firebase Crashlytics][firebase-crashlytics]. + + +* {{recipeIcon}} 使用 Cloud Firestore 构建[多人游戏][multiplayer-recipe]。 + * {{codelab}} 使用 Flutter 构建[文字谜题][word puzzle]。—**新增** + * {{codelab}} 使用 Flutter 和 Flame 构建 [2D 物理游戏][2D physics game]。—**新增** + * {{codelab}} 使用 SoLoud 为你的 Flutter 游戏[添加声音和音乐][Add sound and music]。—**新增** + * {{recipeIcon}} 通过[排行榜和成就][leaderboard-recipe] + 让你的游戏更具吸引力。 + * 通过 {{recipeIcon}}[游戏内广告][ads-recipe] + 和 {{codelab}}[应用内购买][iap-recipe]实现游戏变现。 + * 使用 {{recipeIcon}} [Firebase Authentication][firebase-auth] + 为你的游戏添加用户身份验证流程。 + * 使用 {{recipeIcon}} [Firebase Crashlytics][firebase-crashlytics] + 收集游戏中的崩溃和错误分析数据。 + 1. Set up accounts on AdMob, Firebase, and Cloud, as needed. 1. Write your game! 1. Deploy to both the Google Play and Apple stores. + + +1. 根据需要在 AdMob、Firebase 和 Cloud 上设置账号。 +1. 编写你的游戏! +1. 部署到 Google Play 和 Apple 应用商店。 + [Add sound and music]: {{site.codelabs}}/codelabs/flutter-codelab-soloud [2D physics game]: {{site.codelabs}}/codelabs/flutter-flame-forge2d [word puzzle]: {{site.codelabs}}/codelabs/flutter-word-puzzle ## Example games + + +## 示例游戏 + For Google I/O 2022, both the Flutter team and Very Good Ventures created new games. + + +在 Google I/O 2022 上,Flutter 团队和 +Very Good Ventures 都创建了新游戏。 + * VGV created the [I/O Pinball game][pinball-game] using the Flame engine. To learn about this game, check out [I/O Pinball Powered by Flutter and Firebase][] on Medium and [play the game][pinball-game] in your browser. + + +* VGV 使用 Flame 引擎创建了 [I/O Pinball 游戏][pinball-game]。 + 要了解这个游戏, + 请查看 Medium 上的 [I/O Pinball Powered by Flutter and Firebase][], + 并在浏览器中[玩这个游戏][pinball-game]。 + * The Flutter team created [I/O Flip][flip-game], a virtual [CCG]. To learn more about I/O Flip, check out [How It's Made: I/O FLIP adds a twist to a classic card game with generative AI][flip-blog] on the Google Developers blog and [play the game][flip-game] in your browser. + + +* Flutter 团队创建了 [I/O Flip][flip-game],一款虚拟 [CCG]。 + 要了解更多关于 I/O Flip 的信息, + 请查看 Google 开发者博客上的 + [How It's Made: I/O FLIP adds a twist to a classic card game with generative AI][flip-blog], + 并在浏览器中[玩这个游戏][flip-game]。 + ## Other resources + + +## 其他资源 + Once you feel ready to go beyond these games templates, investigate other resources that our community recommended. + + +当你准备好超越这些游戏模板时, +可以探索我们社区推荐的其他资源。 + {% assign pkgIcon = 'package_2' %} {% assign apiIcon = 'api' %} {% assign docIcon = 'quick_reference_all' %} @@ -198,6 +413,14 @@ investigate other resources that our community recommended. {{pkgIcon}} [rive][]
      {{pkgIcon}} [spriteWidget][] + + + + + + + + @@ -207,6 +430,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [app_review][] + + + + + @@ -218,6 +447,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [flutter_soloud][]—**NEW**
      {{codelab}} [Add sound and music to your Flutter game with SoLoud][]—**NEW** + + + + + @@ -227,6 +462,12 @@ investigate other resources that our community recommended. {{codelab}} [User Authentication using Firebase][firebase-auth] + + + + + @@ -236,6 +477,12 @@ investigate other resources that our community recommended. {{codelab}} [Add Firebase to your Flutter game][] + + + + + @@ -246,6 +493,12 @@ investigate other resources that our community recommended. {{docIcon}} [Firebase Crashlytics overview][firebase-crashlytics]
      {{pkgIcon}} [firebase_crashlytics][] + + + + + @@ -255,6 +508,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [win32_gamepad][] + + + + + @@ -266,6 +525,12 @@ investigate other resources that our community recommended. {{assetsIcon}} [Game Developer Studio][]
      {{toolIcon}} [GIMP][] + + + + + @@ -277,6 +542,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [Bonfire][bonfire-pkg]
      {{pkgIcon}} [forge2d][] + + + + + @@ -287,6 +558,12 @@ investigate other resources that our community recommended. {{recipeIcon}} [Add achievements and leaderboards to your game][leaderboard-recipe]
      {{recipeIcon}} [Add multiplayer support to your game][multiplayer-recipe] + + + + + @@ -296,6 +573,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [games_services][game-svc-pkg] + + + + + @@ -305,6 +588,12 @@ investigate other resources that our community recommended. {{codelab}} [Use the Foreign Function Interface in a Flutter plugin][] + + + + + @@ -314,6 +603,12 @@ investigate other resources that our community recommended. {{toolIcon}} [Tiled][] + + + + + @@ -326,6 +621,12 @@ investigate other resources that our community recommended. {{codelab}} [Add in-app purchases to your Flutter app][iap-recipe]
      {{docIcon}} [Gaming UX and Revenue Optimizations for Apps][] (PDF) + + + + + @@ -337,6 +638,12 @@ investigate other resources that our community recommended. {{pkgIcon}} [sqflite][]
      {{pkgIcon}} [cbl_flutter][] (Couchbase Lite)
      + + + + + @@ -347,6 +654,12 @@ investigate other resources that our community recommended. {{apiIcon}} [Paint API][]
      {{recipeIcon}} [Special effects][] + + + + + @@ -357,6 +670,12 @@ investigate other resources that our community recommended. {{codelab}} [Build next generation UIs in Flutter][]
      {{docIcon}} [Best practices for optimizing Flutter web loading speed][]—**NEW** + + + + +
      功能资源
      动画和精灵图 +
      应用评价 +
      音频 +
      身份验证 +
      云服务 +
      调试 +
      驱动 +
      游戏资源
      和资源工具
      +
      游戏引擎 +
      游戏功能 +
      游戏服务集成 +
      遗留代码 +
      关卡编辑器 +
      变现 +
      持久化 +
      特效 +
      @@ -424,9 +743,19 @@ investigate other resources that our community recommended. ## Games Toolkit updates for Flutter 3.22 {:#updates} + + + +用户体验 + + The following codelabs and guides were added for the Flutter 3.22 release: + + +## Flutter 3.22 游戏工具包更新 {:#updates} + **Low-latency, high-performance sound** : In collaboration with the Flutter community ([@Marco Bavagnoli][]), we've enabled the SoLoud audio engine. @@ -436,6 +765,10 @@ the Flutter 3.22 release: [Add sound and music to your Flutter game with SoLoud][], dedicated to adding sound and music to your game. + + +以下 codelab 和指南是为 Flutter 3.22 版本添加的: + **Word puzzle games** : Check out the new codelab, [Build a word puzzle with Flutter][], @@ -445,6 +778,17 @@ the Flutter 3.22 release: to effortlessly generate expansive crossword-style grids of interlocking words without compromising the user experience. + + +**低延迟、高性能声音** +: 我们与 Flutter 社区([@Marco Bavagnoli][])合作, + 启用了 SoLoud 音频引擎。 + 这个免费且可移植的引擎提供了许多游戏所必需的 + 低延迟和高性能声音。 + 为了帮助你入门,请查看新的 codelab, + [使用 SoLoud 为你的 Flutter 游戏添加声音和音乐][Add sound and music to your Flutter game with SoLoud], + 专门介绍如何为游戏添加声音和音乐。 + **Forge 2D physics engine** : The new Forge2D codelab, [Build a 2D physics game with Flutter and Flame][], @@ -452,6 +796,17 @@ the Flutter 3.22 release: Flutter and Flame game using a 2D physics simulation along the lines of Box2D, called [Forge2D][]. + + +**文字谜题游戏** +: 查看新的 codelab, + [使用 Flutter 构建文字谜题][Build a word puzzle with Flutter], + 专注于构建文字谜题游戏。 + 这个类型非常适合探索 Flutter 的 UI 功能, + 这个 codelab 深入介绍了如何使用 Flutter 的后台处理 + 来轻松生成大型纵横字谜风格的交错单词网格, + 同时不影响用户体验。 + **Optimize loading speed for Flutter web-based games** : In the fast-paced world of web-based gaming, a slow loading game is a major deterrent. @@ -463,6 +818,15 @@ the Flutter 3.22 release: to help you optimize your Flutter web-based games and apps for lightning-fast loading speeds. + + +**Forge 2D 物理引擎** +: 新的 Forge2D codelab, + [使用 Flutter 和 Flame 构建 2D 物理游戏][Build a 2D physics game with Flutter and Flame], + 指导你在 Flutter 和 Flame 游戏中 + 使用类似 Box2D 的 2D 物理模拟(称为 [Forge2D][]) + 来制作游戏机制。 + [@Marco Bavagnoli]: {{site.github}}/alnitak [Add sound and music to your Flutter game with SoLoud]: {{site.codelabs}}/codelabs/flutter-codelab-soloud [Best practices for optimizing Flutter web loading speed]: {{site.flutter-blog}}/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c @@ -473,13 +837,34 @@ the Flutter 3.22 release: ## Other new resources + + +**优化 Flutter Web 游戏的加载速度** +: 在快节奏的 Web 游戏世界中, + 加载缓慢的游戏是一个主要的阻碍因素。 + 玩家期望即时满足, + 会很快放弃加载不及时的游戏。 + 因此,我们发布了一份指南, + [优化 Flutter Web 加载速度的最佳实践][Best practices for optimizing Flutter web loading speed], + 由 [Cheng Lin][] 撰写, + 帮助你优化 Flutter Web 游戏和应用 + 以实现闪电般的加载速度。 + Check out the following videos: + + +## 其他新资源 + * [Building multiplatform games with Flutter][gdc-talk], a talk given at the [Game Developer Conference (GDC)][] 2024. * [How to build a physics-based game with Flutter and Flame's Forge2D][forge2d-video], from Google I/O 2024. + + +查看以下视频: + [Game Developer Conference (GDC)]: https://gdconf.com/ [forge2d-video]: {{site.youtube-site}}/watch?v=nsnQJrYHHNQ [gdc-talk]: {{site.youtube-site}}/watch?v=7mG_sW40tsw diff --git a/src/content/resources/in-app-purchases-overview.md b/src/content/resources/in-app-purchases-overview.md index 10aa2875ba..76573a7047 100644 --- a/src/content/resources/in-app-purchases-overview.md +++ b/src/content/resources/in-app-purchases-overview.md @@ -1,5 +1,9 @@ --- +# title: In-app purchases overview title: In-app purchases overview +# description: > +# Learn about the resources available for adding +# in-app purchases to your Flutter app. description: > Learn about the resources available for adding in-app purchases to your Flutter app. @@ -8,12 +12,28 @@ showBreadcrumbs: false ![adding ads](/assets/images/docs/add-in-app-purchases.png) + + +![添加应用内购买](/assets/images/docs/add-in-app-purchases.png) + Build in-app revenue with Google Play and App Store support for Flutter + + +通过 Flutter 对 Google Play 和 App Store 的支持 +构建应用内收入 + The the following resources can help get you started: + + +以下资源可以帮助你入门: + * [Add in-app purchases to your Flutter app][] (codelab) + +* [为你的 Flutter 应用添加应用内购买][Add in-app purchases to your Flutter app](codelab) + [Add in-app purchases to your Flutter app]: {{site.codelabs}}/codelabs/flutter-in-app-purchases#0 diff --git a/src/content/resources/news-toolkit.md b/src/content/resources/news-toolkit.md index 620d352345..c48ddf7d95 100644 --- a/src/content/resources/news-toolkit.md +++ b/src/content/resources/news-toolkit.md @@ -1,12 +1,23 @@ --- +# title: Flutter News Toolkit title: Flutter News Toolkit +# description: Learn more about creating a newsfeed app in Flutter. description: Learn more about creating a newsfeed app in Flutter. showBreadcrumbs: false --- The Flutter News Toolkit is now maintained by Very Good Ventures (VGV). + + +Flutter 新闻工具包现由 Very Good Ventures (VGV) 维护。 + For the latest information, see the [news_toolkit repository on GitHub][news-toolkit]. + + +如需获取最新信息,请查看 +[GitHub 上的 news_toolkit 仓库][news-toolkit]。 + [news-toolkit]: https://github.com/VGVentures/news_toolkit diff --git a/src/content/resources/payments-overview.md b/src/content/resources/payments-overview.md index 2cb91e1dec..2c1703f167 100644 --- a/src/content/resources/payments-overview.md +++ b/src/content/resources/payments-overview.md @@ -1,5 +1,9 @@ --- +# title: Payments overview title: Payments overview +# description: > +# Learn about the resources available for Google pay +# and other services to your Flutter app. description: > Learn about the resources available for Google pay and other services to your Flutter app. @@ -8,12 +12,27 @@ showBreadcrumbs: false ![adding ads](/assets/images/docs/add-payments.png) + + +![添加支付](/assets/images/docs/add-payments.png) + Seamlessly accept payments with multiple providers in your Flutter app + + +在你的 Flutter 应用中无缝接入多种支付服务提供商 + The following resource can help to get you started: + + +以下资源可以帮助你入门: + * [Add ads to your mobile Flutter app or game][] (cookbook recipe) + +* [为你的移动 Flutter 应用或游戏添加广告][Add ads to your mobile Flutter app or game](实用教程) + [Add ads to your mobile Flutter app or game]: /cookbook/plugins/google-mobile-ads diff --git a/src/content/resources/support.md b/src/content/resources/support.md index ac1e6a66f8..64f36208f8 100644 --- a/src/content/resources/support.md +++ b/src/content/resources/support.md @@ -1,11 +1,17 @@ --- +# title: Flutter support title: Flutter support +# description: Where can you get support when developing with Flutter. description: Where can you get support when developing with Flutter. showBreadcrumbs: false --- Welcome! + + +欢迎! + There are many different ways to get support when developing with Flutter. As Flutter is an open source project, @@ -15,13 +21,34 @@ to connecting with your local community, to browsing and identifying a list of commercial agencies and consultants who offer support services. + + +在使用 Flutter 进行开发时,有很多不同的方式可以获得支持。 +由于 Flutter 是一个开源项目, +你的选择范围从在遇到软件 bug 时提交 issue, +到与本地社区建立联系, +再到浏览和确定一系列提供支持服务的商业机构和顾问。 + ## I want to report an issue with Flutter + + +## 我想报告 Flutter 的问题 + Visit how to [create a useful bug report](/resources/bug-reports). Note that filing bugs is _not_ an efficient way to get tech support. + + +请访问如何[创建有用的 bug 报告](/resources/bug-reports)。 +请注意,提交 bug 并 _不是_ 获得技术支持的有效方式。 + ## I want to ask questions about how to use Flutter + + +## 我想问关于如何使用 Flutter 的问题 + Your best bet is to consult [Flutter Forum](https://forum.itsallwidgets.com/), [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter), @@ -30,14 +57,35 @@ While the community is active in these channels, there is no official SLA on responding to new posts or chats in these channels. + + +最好的选择是咨询 +[Flutter 论坛](https://forum.itsallwidgets.com/)、 +[Stack Overflow](https://stackoverflow.com/questions/tagged/flutter) +或 [Discord](https://discord.com/invite/rflutterdev)。 +虽然社区在这些渠道中很活跃, +但这些渠道中的新帖子或聊天没有官方的 SLA 响应时间。 + ## I want professional services support with Flutter + + +## 我想要 Flutter 的专业服务支持 + Check out the [Flutter consultants]({{site.main-url}}/consultants) directory. + + +请查看 [Flutter 顾问]({{site.main-url}}/consultants)目录。 + ## I want support from a Flutter expert + + +## 我想要 Flutter 专家的支持 + Google has a cadre of [Google Developer Experts][] (GDEs), non-Google employees with expertise in various areas of Google technologies, such as Flutter and Dart. @@ -46,11 +94,27 @@ lectures, host meetups, run workshops, or make videos. As part of the Dart and Flutter world-wide community, they are a great resource. + + +Google 有一批 [Google 开发者专家][Google Developer Experts] (GDE), +他们是非 Google 员工,在 Google 各种技术领域(如 Flutter 和 Dart)拥有专业知识。 +GDE _不是_ 免费顾问,但他们经常进行演讲、主持聚会、举办工作坊或制作视频。 +作为 Dart 和 Flutter 全球社区的一部分,他们是很好的资源。 + [Google Developer Experts]: https://developers.google.com/community/experts/directory?specialization=dart%2Cflutter ## I want to get paid support from Google or the Flutter project + + +## 我想从 Google 或 Flutter 项目获得付费支持 + As an open source project, we're not set up to offer paid support. We recommend checking out the [consultancy program]({{site.main-url}}/consultants). + + + +作为一个开源项目,我们不提供付费支持。 +我们建议查看[顾问计划]({{site.main-url}}/consultants)。