flutter-adding-home-screen-widgets by flutter/skills
npx skills add https://github.com/flutter/skills --skill flutter-adding-home-screen-widgets主屏幕小组件需要原生 UI 实现(iOS 使用 SwiftUI,Android 使用 XML/Kotlin)。Flutter 应用通过 home_widget 包,使用共享本地存储(iOS 使用 UserDefaults,Android 使用 SharedPreferences)与这些原生小组件进行通信。
使用此清单来实现主屏幕小组件集成的 Dart 端。
initState() 或应用启动时调用 HomeWidget.setAppGroupId('<YOUR_APP_GROUP>')。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
HomeWidget.saveWidgetData<T>('key', value) 将数据写入共享存储。HomeWidget.updateWidget(iOSName: 'YourIOSWidget', androidName: 'YourAndroidWidget') 来通知操作系统。如果目标平台是 iOS,请使用 Xcode 和 SwiftUI 实现小组件。
ios/Runner.xcworkspace。添加一个新的 Widget Extension 目标。除非明确需要,否则禁用 "Include Live Activity" 和 "Include Configuration Intent"。TimelineEntry 协议的结构体,用于保存从共享存储传递过来的数据。getSnapshot 和 getTimeline 中,实例化 UserDefaults(suiteName: "<YOUR_APP_GROUP>")。userDefaults?.string(forKey: "your_key") 提取值。TimelineEntry。View 来显示来自 TimelineEntry 的数据。如果目标平台是 Android,请使用 Android Studio 和 XML/Kotlin 实现小组件。
android 文件夹。右键点击应用目录 -> 新建 -> Widget -> App Widget。res/layout/<widget_name>.xml,使用标准的 Android XML 布局(例如 RelativeLayout、TextView、ImageView)来定义 UI。AppWidgetProvider 的 Kotlin 类。onUpdate 方法中,使用 HomeWidgetPlugin.getData(context) 检索共享数据。widgetData.getString("your_key", null) 提取值。RemoteViews 和 setTextViewText 或 setImageViewBitmap 更新 UI。appWidgetManager.updateAppWidget(appWidgetId, views)。如果 UI 过于复杂,无法用原生代码重新创建(例如自定义图表),可以将 Flutter 小组件渲染为图像,并在原生小组件中显示该图像。
GlobalKey 包装目标 Flutter 小组件。HomeWidget.renderFlutterWidget(),传入小组件、文件名和 key。UserDefaults 读取文件路径,并在 SwiftUI Image 中使用 UIImage(contentsOfFile:) 进行渲染。SharedPreferences 读取文件路径,使用 BitmapFactory.decodeFile() 解码,并使用 setImageViewBitmap() 进行渲染。如果在 iOS 主屏幕小组件中使用 Flutter 中定义的自定义字体:
CTFontManagerRegisterFontsForURL 注册字体。Font.custom() 应用字体。import 'package:home_widget/home_widget.dart';
const String appGroupId = 'group.com.example.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,
);
}
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: "加载中...", description: "加载中...")
}
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let userDefaults = UserDefaults(suiteName: "group.com.example.app")
let title = userDefaults?.string(forKey: "headline_title") ?? "无标题"
let description = userDefaults?.string(forKey: "headline_description") ?? "无描述"
let 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)
}
}
}
package com.example.app.widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
import com.example.app.R
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", "无标题")
setTextViewText(R.id.headline_title, title)
val description = widgetData.getString("headline_description", "无描述")
setTextViewText(R.id.headline_description, description)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
// 将此添加到你的 SwiftUI View 结构体中
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
init(entry: Provider.Entry) {
self.entry = entry
CTFontManagerRegisterFontsForURL(
bundle.appending(path: "/fonts/YourCustomFont.ttf") as CFURL,
CTFontManagerScope.process,
nil
)
}
每周安装量
1.9K
代码仓库
GitHub 星标数
808
首次出现
13 天前
安全审计
安装于
gemini-cli1.8K
opencode1.8K
codex1.8K
github-copilot1.8K
cursor1.8K
kimi-cli1.8K
Home Screen Widgets require native UI implementation (SwiftUI for iOS, XML/Kotlin for Android). The Flutter app communicates with these native widgets via shared local storage (UserDefaults on iOS, SharedPreferences on Android) using the home_widget package.
Use this checklist to implement the Dart side of the Home Screen Widget integration.
HomeWidget.setAppGroupId('<YOUR_APP_GROUP>') in initState() or app startup.HomeWidget.saveWidgetData<T>('key', value) to write data to shared storage.HomeWidget.updateWidget(iOSName: 'YourIOSWidget', androidName: 'YourAndroidWidget') to notify the OS.If targeting iOS, implement the widget using Xcode and SwiftUI.
ios/Runner.xcworkspace in Xcode. Add a new Widget Extension target. Disable "Include Live Activity" and "Include Configuration Intent" unless explicitly required.TimelineEntry to hold the data passed from shared storage.getSnapshot and getTimeline, instantiate UserDefaults(suiteName: "<YOUR_APP_GROUP>").userDefaults?.string(forKey: "your_key").TimelineEntry.If targeting Android, implement the widget using Android Studio and XML/Kotlin.
android folder in Android Studio. Right-click the app directory -> New - > Widget -> App Widget.res/layout/<widget_name>.xml to define the UI using standard Android XML layouts (e.g., RelativeLayout, TextView, ImageView).AppWidgetProvider.onUpdate method, retrieve shared data using HomeWidgetPlugin.getData(context).If the UI is too complex to recreate natively (e.g., custom charts), render the Flutter widget to an image and display the image in the native widget.
GlobalKey.HomeWidget.renderFlutterWidget(), passing the widget, a filename, and the key.UserDefaults and render using UIImage(contentsOfFile:) inside a SwiftUI Image.SharedPreferences, decode using BitmapFactory.decodeFile(), and render using setImageViewBitmap().If utilizing custom fonts defined in Flutter on iOS Home Screen Widgets:
CTFontManagerRegisterFontsForURL.Font.custom().import 'package:home_widget/home_widget.dart';
const String appGroupId = 'group.com.example.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,
);
}
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: "Loading...", description: "Loading...")
}
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let userDefaults = UserDefaults(suiteName: "group.com.example.app")
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description"
let 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)
}
}
}
package com.example.app.widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
import com.example.app.R
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", "No Title")
setTextViewText(R.id.headline_title, title)
val description = widgetData.getString("headline_description", "No Description")
setTextViewText(R.id.headline_description, description)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
// Add this to your SwiftUI View struct
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
init(entry: Provider.Entry) {
self.entry = entry
CTFontManagerRegisterFontsForURL(
bundle.appending(path: "/fonts/YourCustomFont.ttf") as CFURL,
CTFontManagerScope.process,
nil
)
}
Weekly Installs
1.9K
Repository
GitHub Stars
808
First Seen
13 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli1.8K
opencode1.8K
codex1.8K
github-copilot1.8K
cursor1.8K
kimi-cli1.8K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
View to display the data from the TimelineEntry.widgetData.getString("your_key", null).RemoteViews and setTextViewText or setImageViewBitmap.appWidgetManager.updateAppWidget(appWidgetId, views).