flutter-home-screen-widget by flutter/skills
npx skills add https://github.com/flutter/skills --skill flutter-home-screen-widget使用 home_widget 包为 Flutter 应用程序实现原生主屏幕小组件(iOS 和 Android)。它通过 App Groups(iOS)和 SharedPreferences(Android)在 Dart 环境与原生平台之间建立数据共享,支持文本更新并将 Flutter UI 组件渲染为图像以供原生显示。假设已存在配置好原生构建工具(Xcode 和 Android Studio)的 Flutter 项目环境。
初始化依赖项 将 home_widget 包添加到 Flutter 项目中。
flutter pub add home_widget flutter pub get
决策逻辑:平台与功能选择 确定目标平台和所需的小组件功能。[阻塞步骤] 用户咨询 在执行任何实现之前,您必须询问:
* "您目标平台是哪些?"
* "您需要简单的文本还是复杂的 UI?"
流程图:
* 如果是 iOS -> 继续执行步骤 4(iOS 原生设置)。
* 如果是 Android -> 继续执行步骤 5(Android 原生设置)。
* 如果需要将 Flutter UI 渲染为图像 -> 在完成基本设置后继续执行步骤 6。
3. 实现 Dart 数据共享逻辑 创建 Dart 逻辑,用于将数据保存到原生键/值存储并触发小组件更新。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import 'package:home_widget/home_widget.dart';
// 替换为 iOS 的实际 App Group ID
const String appGroupId = 'group.com.yourcompany.app';
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget';
Future<void> updateWidgetData(String title, String description) async {
await HomeWidget.setAppGroupId(appGroupId);
await HomeWidget.saveWidgetData<String>('headline_title', title);
await HomeWidget.saveWidgetData<String>('headline_description', description);
await HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
}
4. iOS 原生设置(如适用)
* 在 Xcode 中为 Runner 目标和 Widget Extension 目标配置 App Group。
* 在 Xcode 中创建一个 Widget Extension 目标(例如 `NewsWidgets`)。取消勾选 "Include Live Activity" 和 "Include Configuration Intent"。
* 在 Swift 中实现 `TimelineProvider` 和 `View`:
import WidgetKit
import SwiftUI
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description: String
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
}
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview {
entry = placeholder(in: context)
} else {
// 替换为实际的 App Group ID
let userDefaults = UserDefaults(suiteName: "group.com.yourcompany.app")
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)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getSnapshot(in: context) { (entry) in
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading) {
Text(entry.title).font(.headline)
Text(entry.description).font(.subheadline)
}
}
}
验证与修复: 运行 flutter build ios --config-only 以确保 Flutter 配置与新 Xcode 目标同步。如果构建失败,请验证 Dart 和 Swift 中的 App Group ID 是否完全匹配。
* 在 Android Studio 中创建一个 `AppWidgetProvider`(`New -> Widget -> App Widget`)。
* 定义 XML 布局(`res/layout/news_widget.xml`):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<TextView
android:id="@+id/headline_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textStyle="bold"
android:textSize="20sp" />
<TextView
android:id="@+id/headline_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:text="Description"
android:textSize="16sp" />
</RelativeLayout>
* 实现 Kotlin Provider(`NewsWidget.kt`):
package com.yourdomain.yourapp
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
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)
}
}
}
6. 将 Flutter 小组件渲染为图像(可选) 如果用户需要在小组件上显示复杂的 UI(如图表),则将 Flutter 小组件渲染为 PNG 并传递文件路径。Dart 实现:
final _globalKey = GlobalKey();
// 用 RepaintBoundary/Key 包装您的目标小组件
// Center(key: _globalKey, child: const LineChart())
Future<void> renderAndSaveWidget() async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio: MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
await HomeWidget.updateWidget(iOSName: iOSWidgetName, androidName: androidWidgetName);
}
}
原生图像加载(Android 示例):
// 在 RemoteViews apply 块内:
val imageName = widgetData.getString("filename", null)
val imageFile = java.io.File(imageName)
if (imageFile.exists()) {
val myBitmap = android.graphics.BitmapFactory.decodeFile(imageFile.absolutePath)
setImageViewBitmap(R.id.widget_image, myBitmap)
}
iOSName 和 androidName 必须分别与 Swift 结构体名称和 Kotlin 类名完全匹配。group. 为前缀,并且在 Xcode 功能、Swift 的 UserDefaults(suiteName:) 和 Dart 的 HomeWidget.setAppGroupId() 中必须完全一致。renderFlutterWidget 生成静态图像。flutter run。res/xml/*_info.xml 中的 Android 小组件尺寸必须以单元格为单位计算(例如 minWidth="250dp")。请勿使用任意像素值。flutter build ios / flutter build apk),以便立即捕获语法或链接错误。每周安装量
543
代码仓库
GitHub 星标数
822
首次出现
2026年3月9日
安全审计
安装于
codex530
cursor529
gemini-cli528
github-copilot528
kimi-cli527
amp527
Implements native home screen widgets (iOS and Android) for a Flutter application using the home_widget package. It establishes data sharing between the Dart environment and native platforms via App Groups (iOS) and SharedPreferences (Android), enabling text updates and rendering Flutter UI components as images for native display. Assumes a pre-existing Flutter project environment with native build tools (Xcode and Android Studio) configured.
Initialize Dependencies Add the home_widget package to the Flutter project.
flutter pub add home_widget
flutter pub get
Decision Logic: Platform & Feature Selection Determine the target platforms and required widget capabilities. [BLOCKING] User Consultation BEFORE performing any implementation, you MUST ask:
Flowchart:
* If iOS -> Proceed to Step 4 (iOS Native Setup).
* If Android -> Proceed to Step 5 (Android Native Setup).
* If rendering Flutter UI as images -> Proceed to Step 6 after basic setup.
3. Implement Dart Data Sharing Logic Create the Dart logic to save data to the native key/value store and trigger widget updates.
import 'package:home_widget/home_widget.dart';
// Replace with actual App Group ID for iOS
const String appGroupId = 'group.com.yourcompany.app';
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget';
Future<void> updateWidgetData(String title, String description) async {
await HomeWidget.setAppGroupId(appGroupId);
await HomeWidget.saveWidgetData<String>('headline_title', title);
await HomeWidget.saveWidgetData<String>('headline_description', description);
await HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
}
4. iOS Native Setup (If applicable)
* Configure an App Group in Xcode for both the Runner target and the Widget Extension target.
* Create a Widget Extension target in Xcode (e.g., `NewsWidgets`). Uncheck "Include Live Activity" and "Include Configuration Intent".
* Implement the `TimelineProvider` and `View` in Swift:
import WidgetKit
import SwiftUI
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description: String
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
}
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview {
entry = placeholder(in: context)
} else {
// Replace with actual App Group ID
let userDefaults = UserDefaults(suiteName: "group.com.yourcompany.app")
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)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getSnapshot(in: context) { (entry) in
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading) {
Text(entry.title).font(.headline)
Text(entry.description).font(.subheadline)
}
}
}
Validate-and-Fix: Run flutter build ios --config-only to ensure the Flutter configuration syncs with the new Xcode targets. If build fails, verify the App Group ID matches exactly between Dart and Swift.
Android Native Setup (If applicable)
AppWidgetProvider in Android Studio (New -> Widget -> App Widget).res/layout/news_widget.xml):<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<TextView
android:id="@+id/headline_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textStyle="bold"
android:textSize="20sp" />
<TextView
android:id="@+id/headline_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:text="Description"
android:textSize="16sp" />
</RelativeLayout>
* Implement the Kotlin Provider (`NewsWidget.kt`):
package com.yourdomain.yourapp
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
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)
}
}
}
6. Render Flutter Widgets as Images (Optional) If the user requires complex UI (like charts) on the widget, render the Flutter widget to a PNG and pass the file path. Dart Implementation:
final _globalKey = GlobalKey();
// Wrap your target widget with a RepaintBoundary/Key
// Center(key: _globalKey, child: const LineChart())
Future<void> renderAndSaveWidget() async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio: MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
await HomeWidget.updateWidget(iOSName: iOSWidgetName, androidName: androidWidgetName);
}
}
Native Image Loading (Android Example):
// Inside RemoteViews apply block:
val imageName = widgetData.getString("filename", null)
val imageFile = java.io.File(imageName)
if (imageFile.exists()) {
val myBitmap = android.graphics.BitmapFactory.decodeFile(imageFile.absolutePath)
setImageViewBitmap(R.id.widget_image, myBitmap)
}
iOSName and androidName in Dart MUST exactly match the Swift struct name and Kotlin class name respectively.group. and match exactly in Xcode capabilities, Swift UserDefaults(suiteName:), and Dart HomeWidget.setAppGroupId().renderFlutterWidget to generate a static image if complex UI is required.flutter run.Weekly Installs
543
Repository
GitHub Stars
822
First Seen
Mar 9, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex530
cursor529
gemini-cli528
github-copilot528
kimi-cli527
amp527
Flutter 插件开发指南:从架构设计到 Android/Windows 平台实现
2,600 周安装
res/xml/*_info.xml must be calculated in cells (e.g., minWidth="250dp"). Do not use arbitrary pixel values.flutter build ios / flutter build apk) after modifying native files to catch syntax or linking errors immediately.