1. 简介
widget 是什么?
对于 Flutter 开发者,术语 widget 通常是指使用 Flutter 框架创建的界面组件。在本 Codelab 中,主屏幕微件是指应用的紧凑视图,无需打开应用即可显示应用中的信息。在 Android 设备上,微件位于主屏幕上。在 iOS 设备上,可将小组件添加到主屏幕、锁定屏幕或“今天”视图中。
|
|
微件可以有多复杂?
大多数主屏幕微件都侧重于一目了然的内容。其中可能包含简明文本、图形,或者在 Android 设备上包含基本控件。Android 和 iOS 都会限制您在主屏幕微件中可以使用的界面组件和功能。
|
|
为微件创建界面
由于这些平台界面限制,您无法直接使用 Flutter 框架绘制主屏幕微件的界面。而是需要将使用平台框架(例如 iOS 上的 SwiftUI [WidgetKit] 或 Android 上的 AppWidget XML 布局)创建的微件添加到 Flutter 应用中。此 Codelab 演示了如何在主应用和主屏幕微件之间共享数据、字体资源和渲染的 Flutter 微件,这样您就不必在平台代码中重写复杂的界面。
构建内容
在此 Codelab 中,您将与 Google Antigravity 合作,构建一个 Flutter 新闻阅读器应用,并使用 home_widget 软件包将其连接到 Android 和 iOS 上的主屏幕微件。主屏幕微件将:
- 显示 Flutter 应用中的文章标题和说明数据。
- 使用从 Flutter 应用共享的自定义字体资源 (
Chewy) 显示文本。 - 显示渲染后的 Flutter
CustomPaint图表 widget 的图片。

Flutter 新闻应用包含两个屏幕(路由):
- 第一个界面显示新闻报道列表,其中包含标题和说明。
- 第二个屏幕显示了完整文章、使用
CustomPaint创建的折线图,以及用于更新主屏幕 widget 的按钮。
|
|
您的学习之旅
此 Codelab 将引导您逐步构建应用及其主屏幕 widget:
- 设置 Flutter 环境 - 为目标平台配置 Flutter SDK、Google Antigravity 以及 Xcode 或 Android Studio。
- 创建起始新闻应用 - 提示 Antigravity 使用
home_widget、Chewy字体以及文章列表和详情屏幕来搭建homescreen_widgetsFlutter 应用。 - 添加基本主屏幕微件 - 在 Xcode 中创建
NewsWidgetsiOS 微件扩展程序,并在 Android Studio 中创建NewsWidgetAndroid 应用微件。 - 将数据从 Flutter 应用发送到主屏幕 widget - 配置 iOS 应用组,提示 Antigravity 连接
HomeWidget.saveWidgetData和HomeWidget.updateWidget,并使用 Swift 和 Kotlin 读取共享数据。 - 在 iOS 主屏幕微件中使用自定义字体 - 从 iOS 微件扩展程序内的 Flutter 应用软件包中加载并注册捆绑的
Chewy-Regular.ttf字体资源。 - 将 Flutter widget 渲染为图片 - 提示 Antigravity 使用
HomeWidget.renderFlutterWidget将LineChartwidget 捕获为 PNG,并在 iOS 和 Android 主屏幕 widget 中显示渲染后的图表。
学习内容
- 如何与 Google Antigravity 协作,以迭代方式构建 Flutter 应用并将其与 iOS 和 Android 主屏幕微件连接。
- 如何在 iOS 和 Android 设备上创建主屏幕微件。
- 如何使用
home_widget软件包在主屏幕微件和 Flutter 应用之间共享数据。 - 如何在主屏幕微件中重复使用 Flutter 字体和渲染的 Flutter 微件,以减少重复的界面代码。
- 如何从 Flutter 应用触发主屏幕微件更新。
前提条件
- 熟悉 Flutter widget 和 Dart 语法。
2. 设置您的 Flutter 环境
编辑者
此 Codelab 使用 Google Antigravity 作为 AI 优先的开发环境,并搭配 Xcode(适用于 iOS widget 目标)或 Android Studio(适用于 Android widget 目标)。在每个步骤中,您都需要提示 Antigravity 生成或更新 Dart 和平台 widget 文件,查看生成的代码中的关键概念,并可选择探索创意变体。
选择目标开发平台
创建 iOS 主屏幕微件
- 您可以在 iOS 设备或 iOS 模拟器上运行此 Codelab。
- 您必须使用安装了 Xcode IDE 的 macOS 系统来添加 Widget Extension 目标、配置应用组,以及构建应用的 iOS 版本。
创建 Android 主屏幕微件
- 您可以在实体 Android 设备或 Android 模拟器上运行此 Codelab。
- 您必须使用 Android Studio 配置开发系统,才能构建应用的 Android 版本并使用应用 widget 向导。
安装 Flutter
如需获取关于如何安装 Flutter SDK 的最新说明,请随时访问 docs.flutter.dev。
对于此 Codelab,请安装以下软件:
- Flutter SDK
- 已启用官方 Dart 和 Flutter 插件的 Google Antigravity(可在插件市场的 Build with Google 部分中找到)。
- 您选择的移动目标平台所需的编译器和平台工具(以 iOS 为目标平台时,需要 Xcode;以 Android 为目标平台时,需要 Android Studio)。
3. 创建起始新闻应用
在此步骤中,您将提示 Antigravity 构建 homescreen_widgets Flutter 项目、安装 home_widget 和 material_ui 软件包、捆绑 Chewy 自定义字体,并创建双屏新闻阅读器应用。
提示 Antigravity 创建项目
- 启动 Google Antigravity,然后打开一个空目录,用于构建项目(选择不需要提权或不包含空格的路径,例如主目录中的文件夹或
C:\src\)。 - 在 Antigravity 智能体对话面板中,发送以下提示,以创建 Flutter 项目、添加依赖项和字体资源,并生成初始新闻应用:
Create a new empty Flutter project named `homescreen_widgets` targeting Android and iOS (`flutter create --empty homescreen_widgets --platforms=android,ios --org=com.mydomain`). Add the `cupertino_icons`, `shared_preferences`, `home_widget`, and `material_ui` packages. Download `Chewy-Regular.ttf` from `https://raw.githubusercontent.com/flutter/codelabs/main/homescreen_codelab/step_03/fonts/Chewy-Regular.ttf` into `fonts/Chewy-Regular.ttf` and register the `Chewy` font family in `pubspec.yaml`. Using `package:material_ui/material_ui.dart`, create the starter news reader app:
- `lib/news_data.dart` defining a `NewsArticle` class (`title`, `description`, `articleText = loremIpsum`, and optional `image`) and a `getNewsStories()` function returning sample Flutter news articles.
- `lib/article_screen.dart` defining an `ArticleScreen` stateful widget that displays the article's `description`, `articleText`, a centered 200x200 `LineChart` widget drawn with a `CustomPainter` (`LineChartPainter`), and a `FloatingActionButton.extended` labeled `'Update Homescreen'` that shows a `SnackBar` (`'Updating home screen widget...'`).
- `lib/home_screen.dart` defining a `MyHomePage` stateful widget with an `AppBar` titled `'Top Stories'` and a `ListView.separated` of `getNewsStories()` where tapping a `ListTile` navigates to `ArticleScreen(article: article)`.
- `lib/main.dart` configuring `MaterialApp` with a `Colors.deepPurple` seed color scheme, `titleMedium: TextStyle(fontFamily: 'Chewy', fontSize: 20)`, and `home: const MyHomePage()`.
了解生成的代码
新闻报道建模 (lib/news_data.dart)
lib/news_data.dart 定义了 NewsArticle 数据类,并通过 getNewsStories() 提供模拟故事:
class NewsArticle {
final String title;
final String description;
final String? articleText;
final String? image;
NewsArticle({
required this.title,
required this.description,
this.articleText = loremIpsum,
this.image,
});
}
绘制文章图表 (lib/article_screen.dart)
lib/article_screen.dart 会呈现所选的 NewsArticle,以及一个由 LineChartPainter 支持的 200x200 LineChart widget,是一个 CustomPainter,用于在 Canvas 上绘制轴、阈值线和连接的数据点:
class LineChart extends StatelessWidget {
const LineChart({super.key});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: LineChartPainter(),
child: const SizedBox(height: 200, width: 200),
);
}
}
运行并测试应用
在 iOS 模拟器、Android 模拟器或连接的移动设备上运行起始应用,验证 Top Stories 列表和 ArticleScreen 是否按预期呈现:
flutter run
4. 添加基本主屏幕微件
在连接 Dart 代码之前,请使用适用于 iOS、Android 或两者的平台工具添加基本主屏幕微件目标。
创建基本的 iOS 主屏幕微件
向 Flutter iOS 应用添加微件扩展程序的工作流程与向 SwiftUI 或 UIKit 应用添加微件扩展程序的工作流程相同:
- 在
homescreen_widgets项目目录中运行open ios/Runner.xcworkspace,在 Xcode 中打开ios/Runner.xcworkspace。 - 从 Xcode 菜单中依次选择 File → New → Target,向项目添加新目标。
- 从模板列表中选择微件扩展程序,然后点击下一步。
- 在产品名称框中输入
NewsWidgets。清除包含实时活动和包含配置 intent(或包含配置应用 intent)复选框,然后点击完成。
检查生成的代码
添加目标后,Xcode 会在 ios/NewsWidgets/NewsWidgets.swift 和 ios/NewsWidgets/NewsWidgetsBundle.swift 中生成 WidgetKit 样板代码。如需详细了解 WidgetKit 扩展程序,请参阅 Apple 的应用扩展程序文档。
调试和测试示例微件
- 每当您在通过 Xcode 构建应用之前添加新的软件包或目标时,请更新 Flutter 应用的 iOS 配置。在
homescreen_widgets目录中,运行以下命令:
flutter build ios --config-only
- 在 Xcode 的 scheme 选择器中(位于 Run 按钮旁边),点击 Runner,选择您刚刚创建的 NewsWidgetsExtension(或 NewsWidgets)目标,然后点击 Run。

- 模拟器或设备会显示一个占位主屏幕微件,其中显示当前时间。如果该应用未自动显示,请轻触并按住 iOS 主屏幕上的空白区域,然后点按左上角的 + 按钮:

- 在微件库中搜索主屏幕微件,然后将该微件添加到主屏幕:

创建基本的 Android 主屏幕微件
- 在 Android Studio 中打开
homescreen_widgets/android目录。 - Gradle 同步完成后,在左侧的“Project”窗格中找到 app 模块。右键点击 app,然后依次选择 New → Widget → App Widget。

- 在 New Android Component 对话框中,使用以下值配置主屏幕 widget,然后点击 Finish:
- 类名称:
NewsWidget - 最小宽度(单元格数):
3 - 最小高度(单元格数):
3
检查生成的文件
Android Studio 会在您的 Android 项目中创建和更新以下文件(如果您不使用 Android Studio 向导,也可以让 Antigravity 直接搭建这些文件):
操作 | 目标文件 | 更改 |
更新 |
| 为处理 |
创建 |
| 定义主屏幕 widget XML 布局。 |
创建 |
| 配置 widget 尺寸、更新周期和预览布局。 |
创建 |
| 定义 |
调试和测试示例微件
在 Android 设备或模拟器上构建并运行 Flutter 应用。安装完成后,长按启动器屏幕上的应用图标,然后从菜单中选择微件,即可将微件放置在主屏幕上。

5. 将数据从 Flutter 应用发送到主屏幕微件
现在,您的设备上已经有一个基本的主屏幕微件,接下来您将更新该微件,使其显示 Flutter 应用中新闻报道的标题和说明。

在 Flutter 应用和主屏幕微件之间共享数据涉及三个部分:
- 在 Xcode 中配置 iOS 应用组,以便 Runner 应用和微件扩展程序共享本地存储数据容器。
- 更新 Dart 代码,以写入标题数据并使用
package:home_widget/home_widget.dart触发 widget 刷新。 - 更新 iOS (
NewsWidgets.swift) 和 Android (news_widget.xml和NewsWidget.kt) widget 实现,以读取和呈现保存的字符串。
配置 iOS 应用群组
在 iOS 上,父级应用及其微件扩展程序在不同的沙盒中运行。如需在两个目标之间共享本地 UserDefaults 数据和文件,这两个目标必须属于同一个应用组。如需了解应用群组的背景信息,请参阅 Apple 的“配置应用群组”文档。
- 在 Xcode 中,打开 Runner 目标设置,然后选择 Signing & Capabilities(签名和功能)标签页。验证您的团队和软件包标识符是否已设置。
- 点击 + Capability(+ 功能),选择 App Groups(应用群组),然后点击 App Groups(应用群组)下的 + 按钮,以添加新的群组标识符(例如
group.com.mydomain.homescreenWidgets)。 - 在 Xcode 中选择 NewsWidgetsExtension 目标,打开 Signing & Capabilities,添加 App Groups 功能,然后选择完全相同的应用组标识符。

home_widget如何分享数据
iOS 和 Android 都提供本地键值对存储区:iOS 上的 UserDefaults 和 Android 上的 SharedPreferences。home_widget 软件包封装了这些平台 API,以便您的 Flutter 代码可以保存键值对,并通知活跃的主屏幕微件重新加载其视图。

提示 Antigravity 发送和显示文章数据
在 Antigravity 智能体对话面板中,发送以下提示,以在 Dart 中关联 home_widget,并更新 iOS 和 Android 主屏幕 widget,使其显示 headline_title 和 headline_description:
Connect the Flutter app to the iOS and Android Home Screen widgets using `package:home_widget/home_widget.dart`:
1. In `lib/home_screen.dart`, define constants `appGroupId = '<YOUR APP GROUP>'`, `iOSWidgetName = 'NewsWidgets'`, and `androidWidgetName = 'NewsWidget'`. Add a top-level `updateHeadline(NewsArticle newHeadline)` function that saves `'headline_title'` (`newHeadline.title`) and `'headline_description'` (`newHeadline.description`) using `HomeWidget.saveWidgetData<String>` and triggers `HomeWidget.updateWidget(iOSName: iOSWidgetName, androidName: androidWidgetName)`. In `_MyHomePageState.initState()`, call `HomeWidget.setAppGroupId(appGroupId)` and `updateHeadline(getNewsStories()[0])`.
2. In `lib/article_screen.dart`, import `home_screen.dart` and call `updateHeadline(widget.article)` inside the `FloatingActionButton.extended` `onPressed` callback.
3. Update `ios/NewsWidgets/NewsWidgets.swift` (creating it if needed) with a `NewsArticleEntry: TimelineEntry` struct (`date`, `title`, `description`), a `NewsWidgetsEntryView` displaying `Text(entry.title)` and `Text(entry.description)` in a `VStack`, and a `Provider: TimelineProvider` that reads `"headline_title"` and `"headline_description"` from `UserDefaults(suiteName: "<YOUR APP GROUP>")` in `getSnapshot`.
4. Update `android/app/src/main/res/layout/news_widget.xml` and `android/app/src/main/java/com/mydomain/homescreen_widgets/NewsWidget.kt` (creating them and registering `.NewsWidget` in `AndroidManifest.xml` if needed) so `onUpdate` reads `"headline_title"` and `"headline_description"` using `HomeWidgetPlugin.getData(context)` and updates `R.id.headline_title` and `R.id.headline_description` with `setTextViewText`.
Antigravity 应用这些更改后,请将 lib/home_screen.dart 和 ios/NewsWidgets/NewsWidgets.swift 中的 替换为您在 Xcode 中配置的应用组标识符。
了解生成的代码
保存来自 Flutter 的 widget 数据(lib/home_screen.dart 和 lib/article_screen.dart)
在 lib/home_screen.dart 中,updateHeadline 会将 headline_title 和 headline_description 写入共享平台存储区,并调用 HomeWidget.updateWidget 以请求在 iOS 和 Android 上立即重新加载 widget:
const String appGroupId = '<YOUR APP GROUP>';
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget';
void updateHeadline(NewsArticle newHeadline) {
HomeWidget.saveWidgetData<String>('headline_title', newHeadline.title);
HomeWidget.saveWidgetData<String>(
'headline_description',
newHeadline.description,
);
HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
}
在 _MyHomePageState.initState() 中,应用会向 HomeWidget.setAppGroupId(appGroupId) 注册 iOS 应用组 ID,并为微件提供第一篇报道。在 lib/article_screen.dart 中,按更新主屏幕 FloatingActionButton 会调用 updateHeadline(widget.article),将当前查看的新闻推送到主屏幕 widget。
在 iOS WidgetKit TimelineProvider (ios/NewsWidgets/NewsWidgets.swift) 中读取数据
在 iOS 上,NewsArticleEntry 符合 TimelineEntry 并包含传递给 NewsWidgetsEntryView 的 date、title 和 description:
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description: String
}
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.title)
Text(entry.description)
}
}
}
Provider 使用以下三种方法实现 TimelineProvider:
placeholder(in:):当用户首次在 iOS 微件库中预览微件时,返回占位符NewsArticleEntry。

getSnapshot(in:completion:):从UserDefaults(suiteName: "读取") "headline_title"和"headline_description",并为当前状态构建NewsArticleEntry:
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview {
entry = placeholder(in: context)
} else {
let userDefaults = UserDefaults(suiteName: "<YOUR APP GROUP>")
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
entry = NewsArticleEntry(date: Date(), title: title, description: description)
}
completion(entry)
}
getTimeline(in:completion:):使用.atEnd重新加载政策将快照条目封装在Timeline中,以便 WidgetKit 在收到请求时刷新 widget。
读取 Android AppWidgetProvider(NewsWidget.kt 和 news_widget.xml)中的数据
在 Android 上,android/app/src/main/res/layout/news_widget.xml 在 RelativeLayout 内定义了两个 TextView 元素(@+id/headline_title 和 @+id/headline_description)。
在 android/app/src/main/java/com/mydomain/homescreen_widgets/NewsWidget.kt 中,onUpdate 使用 HomeWidgetPlugin.getData(context) 检索共享偏好设置,并在 widget 的 RemoteViews 上填充两个 TextView:
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
运行并测试应用
重新构建并运行您的应用(如果在 iOS 上进行测试,则在 Xcode 中重新构建并运行 NewsWidgetsExtension 目标)。在应用中打开任意文章,然后点按更新主屏幕。返回设备的主屏幕,验证该微件是否显示所选文章的标题和说明:

6. 在 iOS 主屏幕微件中使用自定义字体
您的 Flutter 应用使用自定义 Chewy 字体系列 (fonts/Chewy-Regular.ttf) 作为文章子标题。在 iOS 上,Flutter 会将资源打包到主应用包内的 Frameworks/App.framework/flutter_assets 中。您的 iOS widget 扩展程序可以访问该共享 bundle 目录,并向 Core Text 注册字体,以便您的 SwiftUI widget 与 Flutter 应用的排版保持一致。
提示 Antigravity 在 iOS 上注册并应用自定义字体
在 Antigravity 智能体对话面板中发送以下提示:
Update `NewsWidgetsEntryView` in `ios/NewsWidgets/NewsWidgets.swift` to use the Flutter app's bundled `Chewy` font for the headline title:
1. Add a computed `bundle: URL` property on `NewsWidgetsEntryView` that checks if `Bundle.main.bundleURL.pathExtension == "appex"` and, if so, navigates up two directory levels and appends `"Frameworks/App.framework/flutter_assets"`.
2. Add an `init(entry: Provider.Entry)` initializer that assigns `self.entry = entry` and registers `bundle.appending(path: "/fonts/Chewy-Regular.ttf")` using `CTFontManagerRegisterFontsForURL(..., CTFontManagerScope.process, nil)`.
3. Apply `.font(Font.custom("Chewy", size: 13))` to `Text(entry.title)` in `body`.
了解生成的代码
从 .appex 扩展程序中查找 Flutter 资源包
由于该 widget 在 .appex 软件包 (MY_APP.app/PlugIns/MY_APP_EXTENSION.appex) 内运行,因此计算出的 bundle 属性会剥离两个目录级别以到达父级应用软件包,并附加 Frameworks/App.framework/flutter_assets:
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
注册和应用字体
在 NewsWidgetsEntryView.init(entry:) 中,CTFontManagerRegisterFontsForURL 会为当前进程注册 /fonts/Chewy-Regular.ttf,以便 SwiftUI 的 Font.custom("Chewy", size: 13) 可以呈现标题:
init(entry: Provider.Entry) {
self.entry = entry
CTFontManagerRegisterFontsForURL(
bundle.appending(path: "/fonts/Chewy-Regular.ttf") as CFURL,
CTFontManagerScope.process,
nil
)
}
var body: some View {
VStack {
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description)
}
}
运行并测试应用
从 Xcode 重新运行 widget 目标。iOS 主屏幕 widget 现在会以 Chewy 字体呈现文章标题:

7. 将 Flutter widget 渲染为图片
如果要在 SwiftUI 和 Android XML 中重新创建自定义 Flutter 可视化效果(例如使用 CustomPainter 绘制的 LineChart),则需要使用三种语言维护重复的绘制代码。相反,HomeWidget.renderFlutterWidget 允许您将任何 Flutter 微件光栅化为共享容器中的 PNG 文件,并将其路径传递给 iOS 和 Android 主屏幕微件。
提示 Antigravity 渲染 Flutter 图表并将其显示在微件中
在 Antigravity 智能体对话面板中发送以下提示:
Render the `LineChart` Flutter widget to an image and display it in both the iOS and Android Home Screen widgets:
1. In `lib/article_screen.dart`, import `package:home_widget/home_widget.dart` and add `final _globalKey = GlobalKey();` and `String? imagePath;` to `_ArticleScreenState`. Attach `key: _globalKey` to the `Center` widget wrapping `const LineChart()`. Make the `FloatingActionButton.extended` `onPressed` callback `async`: when `_globalKey.currentContext != null`, call `await HomeWidget.renderFlutterWidget(const LineChart(), key: 'filename', logicalSize: _globalKey.currentContext!.size!, pixelRatio: MediaQuery.of(_globalKey.currentContext!).devicePixelRatio)`, store the returned path in `imagePath` with `setState()`, and then call `updateHeadline(widget.article)`.
2. In `ios/NewsWidgets/NewsWidgets.swift`, add `let filename: String` and `let displaySize: CGSize` to `NewsArticleEntry`, pass placeholder values in `placeholder(in:)`, read `userDefaults?.string(forKey: "filename") ?? "No screenshot available"` and `context.displaySize` in `getSnapshot`, and add a `ChartImage` computed view on `NewsWidgetsEntryView` that loads `UIImage(contentsOfFile: entry.filename)` into a resizable `Image` framed at `entry.displaySize.height * 0.5` and renders `ChartImage` below the description in `VStack`.
3. In `android/app/src/main/res/layout/news_widget.xml`, add an `<ImageView android:id="@+id/widget_image" ... />` below `@+id/headline_description`. In `android/app/src/main/java/com/mydomain/homescreen_widgets/NewsWidget.kt`, read `widgetData.getString("filename", null)` in `onUpdate`, check if `File(imageName).exists()`, decode the file with `BitmapFactory.decodeFile(imageFile.absolutePath)`, and call `setImageViewBitmap(R.id.widget_image, myBitmap)`.
更新 getSnapshot 时,请在 ios/NewsWidgets/NewsWidgets.swift 中保留您的真实应用群组 ID。
了解生成的代码
将 Flutter widget 捕获为 PNG (lib/article_screen.dart)
在 _ArticleScreenState 中,_globalKey 附加到封装 const LineChart() 的 Center widget,以便应用可以读取其渲染的 size 和 devicePixelRatio。当用户点按 Update Homescreen(更新主屏幕)时,HomeWidget.renderFlutterWidget 会将 const LineChart() 渲染为共享容器中的 PNG 文件,并将其路径存储在本地存储空间中的 'filename' 键下:
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
key: 'filename',
logicalSize: _globalKey.currentContext!.size!,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path;
});
}
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
在 iOS (ios/NewsWidgets/NewsWidgets.swift) 上显示渲染的图片
在 NewsWidgets.swift 中,getSnapshot 会从 UserDefaults 中读取 "filename" 路径,并将其与 context.displaySize 一起传入 NewsArticleEntry。NewsWidgetsEntryView 定义了一个 ChartImage 计算属性,该属性使用 UIImage(contentsOfFile: entry.filename) 加载 PNG 文件,并将其缩放到微件高度的 50%:
var ChartImage: some View {
if let uiImage = UIImage(contentsOfFile: entry.filename) {
let image = Image(uiImage: uiImage)
.resizable()
.frame(
width: entry.displaySize.height * 0.5,
height: entry.displaySize.height * 0.5,
alignment: .center
)
return AnyView(image)
}
print("The image file could not be loaded")
return AnyView(EmptyView())
}
在 Android(news_widget.xml 和 NewsWidget.kt)上显示渲染后的图片
在 android/app/src/main/res/layout/news_widget.xml 中, 位于 @+id/headline_description 下方。在 NewsWidget.kt 中,onUpdate 会从 widgetData 读取 "filename" 路径,使用 BitmapFactory.decodeFile 将 PNG 文件解码为 Bitmap,并使用 setImageViewBitmap 更新 ImageView:
val imageName = widgetData.getString("filename", null)
val imageFile = File(imageName)
val imageExists = imageFile.exists()
if (imageExists) {
val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
setImageViewBitmap(R.id.widget_image, myBitmap)
} else {
println("image not found!, looked @: ${imageName}")
}
运行并测试应用
重新运行 Flutter 应用和平台 widget 目标。在应用中打开一篇文章页面,然后点按更新主屏幕。主屏幕微件会更新,显示文章标题、摘要和渲染的 LineChart 图片:

8. 恭喜!
恭喜!您已使用 Google Antigravity 和 home_widget 为 iOS 和 Android 构建了主屏幕 widget,并将其连接到 Flutter 应用。
您构建的内容
- 一个包含两个屏幕的 Flutter 新闻应用,使用了
material_ui、自定义Chewy排版和CustomPainter折线图。 - 原生 iOS(WidgetKit/SwiftUI)和 Android(
AppWidgetProvider/RemoteViews)主屏幕微件。 - Dart、
UserDefaults(使用 iOS 应用组)和SharedPreferences之间的键值对数据同步。 - iOS 微件扩展程序中的跨软件包自定义字体注册。
- 在 iOS 和 Android 主屏幕 widget 中显示的屏幕外 Flutter widget 光栅化 (
HomeWidget.renderFlutterWidget)。
后续步骤
链接到 Flutter 应用中的内容
您可能希望在用户点按主屏幕微件时,将其引导至 Flutter 应用中的特定屏幕,例如打开所显示标题的完整文章。您可以使用 home_widget 提供的 widgetClicked 流来检测 widget 启动情况,并将深层链接 URI 传递到您的应用中。如需了解详情,请参阅 docs.flutter.dev 上的深层链接文档。
正在后台更新微件
在此 Codelab 中,您通过应用内按钮触发了主屏幕 widget 更新。在生产环境中,您可以使用 workmanager 插件和 home_widget 安排定期后台更新。如需了解详情,请参阅 home_widget 的后台更新文档。
在 iOS 上,您还可以直接通过 WidgetKit 的 TimelineProvider 安排定期时间轴刷新或后台网络请求。请参阅 Apple 的“Keeping a widget up to date”文档。





