Flutter+Android/ios 桌面小组件

Flutter+Android/ios 桌面小组件

总结:

  1. Android和iOS 桌面小组件 需要原生去绘制小组件和更新数据,Flutter层 可以使用 MethodChannel 与原生 通信 来控制 更新数据,app无法主动控制 小组件的添加 和 删除, 只能是用户手动操作 。
  2. 小组件支持显示的内容包括:文字,图片,列表,虚拟时钟,勾选框,进度条等,注意:只能显示原生的view 不可显示自定义画的view。
  3. 负一屏:Google原生暂时还没开放API可以直接添加小组件到负一屏,目前Google原生的负一屏 是一个简单的 新闻 feed流 不可自定义编辑,国内的很多厂商整合了负一屏和桌面小组件,也就说小组件可以直接添加在负一屏,不过开发需要适配不同的厂商系统如小米,华为,Oppo,vivo等,iOS负一屏 也可以自定义编辑,小组件直接添加在负一屏。

官方文档:

Android小组件:https://developer.android.com/develop/ui/views/appwidgets?hl=zh-cn
IOS小组件:https://developer.apple.com/cn/documentation/widgetkit/creating-a-widget-extension/
小米小部件:https://dev.mi.com/console/doc/detail?pId=2465
华为荣耀小组件:https://developer.honor.com/cn/doc/guides/100170
Oppo小组件:https://open.oppomobile.com/new/developmentDoc/info?id=12704
vivo小组件:https://developers.vivo.com/doc/d/e88425fe41c94924a052e98dd956c0fb

参考文档:

Flutter小组件开发:https://juejin.cn/post/6933559401462628365
Android小组件开发:https://juejin.cn/post/7296031991320870912

上手开发:

使用Flutter插件:home_widget
插件地址:https://pub-web.flutter-io.cn/packages/home_widget

iOS Android
iOS.png
Android.png

平台设置

为了正常工作,需要一些特定于平台的设置。请查看以下内容,了解如何添加对 Android 和 iOS 的支持

<details><summary>iOS</summary>

在 Xcode 中将小部件添加到您的应用程序

通过添加小部件扩展 <kbd>File</kbd> > <kbd>New</kbd> > <kbd>Target</kbd> > <kbd>Widget Extension</kbd>

Widget Extension.png

添加groupId

您需要向应用程序和小部件扩展添加一个groupId

注意:为了添加 groupId,您需要一个付费的 Apple 开发者帐户

转到您的Apple 开发者帐户并添加一个新组。将此组添加到您的 Runner 和 XCode 内的 Widget Extension: <kbd>Signing & Capabilities</kbd> > <kbd>App Groups</kbd> > <kbd>+</kbd>。
(要在您的应用程序和扩展程序之间交换,请更改目标)

acf086a2-4848-4a57-8140-7b410fa0ebea.png

同步 CFBundleVersion(可选)

此步骤是可选的,这会将小部件扩展构建版本与您的应用程序版本同步,因此您在上传应用程序时不会收到 App Store Connect 版本不匹配的警告。

7520debc-0434-4722-b865-308435627e63.png

在您的 Runner(应用程序)目标中,转到 <kbd>Build Phases</kbd> > <kbd>+</kbd> > <kbd>New Run Script Phase</kbd> 并添加以下脚本:

generatedPath="$SRCROOT/Flutter/Generated.xcconfig"

# Read and trim versionNumber and buildNumber
versionNumber=$(grep FLUTTER_BUILD_NAME "$generatedPath" | cut -d '=' -f2 | xargs)
buildNumber=$(grep FLUTTER_BUILD_NUMBER "$generatedPath" | cut -d '=' -f2 | xargs)

infoPlistPath="$SRCROOT/HomeExampleWidget/Info.plist"

# Check and add CFBundleVersion if it does not exist
/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$infoPlistPath" 2>/dev/null
if [ $? != 0 ]; then
    /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $buildNumber" "$infoPlistPath"
else
    /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$infoPlistPath"
fi

# Check and add CFBundleShortVersionString if it does not exist
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$infoPlistPath" 2>/dev/null
if [ $? != 0 ]; then
    /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $versionNumber" "$infoPlistPath"
else
    /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $versionNumber" "$infoPlistPath"
fi

HomeExampleWidget 替换为您创建的小部件扩展文件夹的名称。

编写你的小组件

Check the Example App for an Implementation of a Widget.
A more detailed overview on how to write Widgets for iOS 14 can be found on the Apple Developer documentation.
In order to access the Data send with Flutter can be access with
检查示例应用程序以了解小部件的实现。有关如何为 iOS 14 编写 Widget 的更详细概述可以在Apple 开发人员文档中找到。为了访问使用 Flutter 发送的数据,可以使用

let data = UserDefaults.init(suiteName:"YOUR_GROUP_ID")

</details>

<details><summary>Android (Jetpack Glance)</summary>

将 Jetpack Glance 添加为应用程序 Gradle 文件的依赖项

implementation 'androidx.glance:glance-appwidget:LATEST-VERSION'

android/app/src/main/res/xml中创建小部件配置

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="10000">
</appwidget-provider>

将 WidgetReceiver 添加到 AndroidManifest

<receiver android:name=".glance.HomeWidgetReceiver"
          android:exported="true">
   <intent-filter>
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>
   <meta-data
           android:name="android.appwidget.provider"
           android:resource="@xml/home_widget_glance_example" />
</receiver>

创建 WidgetReceiver

要获得自动更新,您应该从 HomeWidgetGlanceWidgetReceiver 扩展

Your Receiver should then look like this
你的Receiver 应该看起来像这样

package es.antonborri.home_widget_example.glance

import HomeWidgetGlanceWidgetReceiver

class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver<HomeWidgetGlanceAppWidget>() {
    override val glanceAppWidget = HomeWidgetGlanceAppWidget()
}

构建您的 AppWidget


class HomeWidgetGlanceAppWidget : GlanceAppWidget() {

    /**
     * Needed for Updating
     */
    override val stateDefinition = HomeWidgetGlanceStateDefinition()

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            GlanceContent(context, currentState())
        }
    }

    @Composable
    private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
        // Use data to access the data you save with 
        val data = currentState.preferences
       

        // Build Your Composable Widget
       Column(
         ...
    }

Android (XML)

android/app/src/main/res/layout中创建小部件布局

android/app/src/main/res/xml中创建小部件配置

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/example_layout"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

将 WidgetReceiver 添加到 AndroidManifest

<receiver android:name="HomeWidgetExampleProvider" android:exported="true">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/home_widget_example" />
</receiver>

编写你的 WidgetProvider

For convenience, you can extend from HomeWidgetProvider which gives you access to a SharedPreferences Object with the Data in the onUpdate method.
In case you don't want to use the convenience Method you can access the Data using
为了方便起见,您可以从HomeWidgetProvider进行扩展,它使您可以通过onUpdate方法中的数据访问 SharedPreferences 对象。如果您不想使用方便的方法,您可以使用以下方式访问数据

import es.antonborri.home_widget.HomeWidgetPlugin
...
HomeWidgetPlugin.getData(context)

这将使您可以访问相同的 SharedPreferences

更多信息

For more Information on how to create and configure Android Widgets, check out this guide on the Android Developers Page.
有关如何创建和配置 Android Widget 的更多信息,请查看 Android 开发人员页面上的本指南。

用法

设置

iOS

For iOS, you need to call HomeWidget.setAppGroupId('YOUR_GROUP_ID');
Without this you won't be able to share data between your App and the Widget and calls to saveWidgetData and getWidgetData will return an error
对于iOS,您需要调用HomeWidget.setAppGroupId('YOUR_GROUP_ID');如果没有这个,您将无法在应用程序和小部件之间共享数据,并且调用saveWidgetData和getWidgetData将返回错误

保存数据

In order to save Data call HomeWidget.saveWidgetData<String>('id', data)

更新小组件

In order to force a reload of the HomeScreenWidget you need to call
为了强制重新加载 HomeScreenWidget,您需要调用

HomeWidget.updateWidget(
    name: 'HomeWidgetExampleProvider',
    androidName: 'HomeWidgetExampleProvider',
    iOSName: 'HomeWidgetExample',
    qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
);

The name for Android will be chosen by checking qualifiedAndroidName, falling back to <packageName>.androidName and if that was not provided it will fallback to <packageName>.name.
This Name needs to be equal to the Classname of the WidgetProvider
Android 的名称将通过检查qualifiedAndroidName来选择,回退到<packageName>.androidName ,如果未提供,它将回退到<packageName>.name 。该名称需要等于WidgetProvider的类名

The name for iOS will be chosen by checking iOSName if that was not provided it will fallback to name.
This name needs to be equal to the Kind specified in you Widget
iOS 的名称将通过检查iOSName来选择,如果未提供,它将回退到name 。该名称需要等于您在 Widget 中指定的 Kind

Android (Jetpack Glance)

If you followed the guide and use HomeWidgetGlanceWidgetReceiver as your Receiver, HomeWidgetGlanceStateDefinition as the AppWidgetStateDefinition, currentState() in the composable view and currentState.preferences for data access. No further work is necessary.
如果您按照指南操作并使用HomeWidgetGlanceWidgetReceiver作为接收器、使用HomeWidgetGlanceStateDefinition作为 AppWidgetStateDefinition、可组合视图中的currentState()以及用于数据访问的currentState.preferences 。不需要进一步的工作。

Android (XML)

Calling HomeWidget.updateWidget only notifies the specified provider.
To update widgets using this provider,
update them from the provider like this:
调用HomeWidget.updateWidget仅通知指定的提供程序。要使用此提供程序更新小部件,请从提供程序更新它们,如下所示:

class HomeWidgetExampleProvider : HomeWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.example_layout).apply {
                // ...
            }

            // Update widget.
            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}

检索数据

To retrieve the current Data saved in the Widget call HomeWidget.getWidgetData<String>('id', defaultValue: data)
要检索保存在小部件中的当前数据,请调用HomeWidget.getWidgetData<String>('id', defaultValue: data)

交互式小部件

Android 和 iOS(从 iOS 17 开始)允许小部件具有按钮等交互式元素

<details><summary>Dart</summary>

  1. Write a static function that takes a Uri as an argument. This will get called when a user clicks on the View
    编写一个以 Uri 作为参数的静态函数。当用户单击视图时将调用此函数

    @pragma("vm:entry-point")
    FutureOr<void> backgroundCallback(Uri data) async {
      // do something with data
      ...
    }
    

    @pragma('vm:entry-point') must be placed above the callback function to avoid tree shaking in release mode.

  2. Register the callback function by calling
    通过调用注册回调函数

    HomeWidget.registerInteractivityCallback(backgroundCallback);
    
iOS
  1. Adjust your Podfile to add home_widget as a dependency to your WidgetExtension
    调整 Podfile 以将home_widget添加为 WidgetExtension 的依赖项

    target 'YourWidgetExtension' do
       use_frameworks!
       use_modular_headers!
    
       pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
    end
    
  2. To be able to use plugins with the Background Callback add this to your AppDelegate's application function
    为了能够通过后台回调使用插件,请将其添加到您的 AppDelegate 的application函数中

    if #available(iOS 17, *) {
     HomeWidgetBackgroundWorker.setPluginRegistrantCallback { registry in
         GeneratedPluginRegistrant.register(with: registry)
     }
    }
    
  3. Create a custom AppIntent in your App Target (Runner) and make sure to select both your App and your WidgetExtension in the Target Membership panel
    在您的 App Target (Runner) 中创建自定义AppIntent ,并确保在 Target Membership 面板中选择您的 App 和 WidgetExtension

    ed465c7f-5287-433d-b051-02fc8f80267a.png

In this Intent you should import home_widget and call HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!) in the perform method. url and appGroup can be either hardcoded or set as parameters from the Widget
在此 Intent 中,您应该导入home_widget并在执行方法中调用HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!) 。 url和appGroup可以是硬编码的,也可以设置为 Widget 的参数

import AppIntents
import Flutter
import Foundation
import home_widget

@available(iOS 16, *)
public struct BackgroundIntent: AppIntent {
   static public var title: LocalizedStringResource = "HomeWidget Background Intent"
   
   @Parameter(title: "Widget URI")
   var url: URL?
   
   @Parameter(title: "AppGroup")
   var appGroup: String?
   
   public init() {}
   
   public init(url: URL?, appGroup: String?) {
      self.url = url
      self.appGroup = appGroup
   }
   
   public func perform() async throws -> some IntentResult {
      await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
   
      return .result()
   }
}   
  1. Add a Button to your Widget. This Button might be encapsulated by a Version check. Pass in an instance of the AppIntent created in the previous step
    将按钮添加到您的小部件。该按钮可能由版本检查封装。传入上一步创建的AppIntent实例

    Button(
       intent: BackgroundIntent(
         url: URL(string: "homeWidgetExample://titleClicked"), appGroup: widgetGroupId)
     ) {
       Text(entry.title).bold().font( /*@START_MENU_TOKEN@*/.title /*@END_MENU_TOKEN@*/)
     }.buttonStyle(.plain)
    
  2. With the current setup the Widget is now Interactive as long as the App is still in the background. If you want to have the Widget be able to wake the App up you need to add the following to your AppIntent file
    在当前设置下,只要应用程序仍在后台,小部件就可以交互。如果您想让 Widget 能够唤醒应用程序,您需要将以下内容添加到您的AppIntent文件中

    @available(iOS 16, *)
    @available(iOSApplicationExtension, unavailable)
    extension BackgroundIntent: ForegroundContinuableIntent {}
    

    This code tells the system to always perform the Intent in the App and not in a process attached to the Widget. Note however that this will start your Flutter App using the normal main entrypoint meaning your full app might be run in the background. To counter this you should add checks in the very first Widget you build inside runApp to only perform necessary calls/setups while the App is launched in the background
    此代码告诉系统始终在应用程序中执行 Intent,而不是在附加到 Widget 的进程中。但请注意,这将使用正常的主入口点启动您的 Flutter 应用程序,这意味着您的完整应用程序可能会在后台运行。为了解决这个问题,您应该在runApp内构建的第一个 Widget 中添加检查,以便仅在应用程序在后台启动时执行必要的调用/设置

Android Jetpack Glance
  1. Add the necessary Receiver and Service to your AndroidManifest.xml file
    将必要的接收器和服务添加到您的AndroidManifest.xml文件中
    <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"  android:exported="true">
        <intent-filter>
            <action android:name="es.antonborri.home_widget.action.BACKGROUND" />
        </intent-filter>
    </receiver>
    <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
        android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
    
  2. Create a custom Action 创建自定义操作
    class InteractiveAction : ActionCallback {
         override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
          val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("homeWidgetExample://titleClicked"))
          backgroundIntent.send()
        }
    }
    
  3. Add the Action as a modifier to a view
    将操作作为修改器添加到视图中
    Text(
         title,
         style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold),
         modifier = GlanceModifier.clickable(onClick = actionRunCallback<InteractiveAction>()),
    )
    
Android XML
  1. Add the necessary Receiver and Service to your AndroidManifest.xml file
    将必要的接收器和服务添加到您的AndroidManifest.xml文件中

    <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"  android:exported="true">
        <intent-filter>
            <action android:name="es.antonborri.home_widget.action.BACKGROUND" />
        </intent-filter>
    </receiver>
    <service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
        android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/>
    
  2. Add a HomeWidgetBackgroundIntent.getBroadcast PendingIntent to the View you want to add a click listener to
    将HomeWidgetBackgroundIntent.getBroadcast PendingIntent 添加到要添加点击侦听器的视图

    val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
        context,
        Uri.parse("homeWidgetExample://titleClicked")
    )
    setOnClickPendingIntent(R.id.widget_title, backgroundIntent)
    

Using images of Flutter widgets (使用 Flutter 小组件的图像)

In some cases, you may not want to rewrite UI code in the native frameworks for your widgets.
在某些情况下,您可能不想在小部件的本机框架中重写 UI 代码。

Dart

For example, say you have a chart in your Flutter app configured with CustomPaint:
例如,假设您的 Flutter 应用程序中有一个配置了“CustomPaint”的图表:

class LineChart extends StatelessWidget {
  const LineChart({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: LineChartPainter(),
      child: const SizedBox(
        height: 200,
        width: 200,
      ),
    );
  }
}
819c6cd5-a0e4-4f59-9500-87d084a4a804.png

Rewriting the code to create this chart on both Android and iOS might be time consuming.
Instead, you can generate a png file of the Flutter widget and save it to a shared container
between your Flutter app and the home screen widget.
重写代码以在 Android 和 iOS 上创建此图表可能非常耗时。相反,您可以生成 Flutter 小部件的 png 文件,并将其保存到 Flutter 应用程序和主屏幕小部件之间的共享容器中。

var path = await HomeWidget.renderFlutterWidget(
  const LineChart(),
  key: 'lineChart',
  logicalSize: const Size(400, 400),
);
  • LineChart() is the widget that will be rendered as an image. (LineChart()是将呈现为图像的小部件。)
  • key is the key in the key/value storage on the device that stores the path of the file for easy retrieval on the native side.(key是设备上键/值存储中的键,存储文件的路径,方便本机端检索)
    </details>
iOS

To retrieve the image and display it in a widget, you can use the following SwiftUI code:
要检索图像并将其显示在小部件中,您可以使用以下 SwiftUI 代码:

  1. In your TimelineEntry struct add a property to retrieve the path:
    在您的TimelineEntry结构中添加一个属性来检索路径:

    struct MyEntry: TimelineEntry {
        …
        let lineChartPath: String
    }
    
  2. Get the path from the UserDefaults in getSnapshot:
    从getSnapshot中的UserDefaults获取路径:

    func getSnapshot(
        ...
        let lineChartPath = userDefaults?.string(forKey: "lineChart") ?? "No screenshot available"
    
  3. Create a View to display the chart and resize the image based on the displaySize of the widget:
    创建一个View来显示图表并根据小部件的displaySize调整图像大小:

    struct WidgetEntryView : View {
      …
       var ChartImage: some View {
            if let uiImage = UIImage(contentsOfFile: entry.lineChartPath) {
                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())
        }
    …
    }
    
  4. Display the chart in the body of the widget's View:
    在小部件的View主体中显示图表:

    VStack {
            Text(entry.title)
            Text(entry.description)
            ChartImage
        }
    
e06dbc2a-f417-446e-9608-ecaa8a301624.png
Android (Jetpack Glance)
// Access data
val data = currentState.preferences

// Get Path
val imagePath = data.getString("lineChart", null)

// Add Image to Compose Tree
imagePath?.let {
   val bitmap = BitmapFactory.decodeFile(it)
   Image(androidx.glance.ImageProvider(bitmap), null)
}
Android (XML)
  1. Add an image UI element to your xml file:
    将图像 UI 元素添加到您的 xml 文件中:

    <ImageView
           android:id="@+id/widget_image"
           android:layout_width="200dp"
           android:layout_height="200dp"
           android:layout_below="@+id/headline_description"
           android:layout_alignBottom="@+id/headline_title"
           android:layout_alignParentStart="true"
           android:layout_alignParentLeft="true"
           android:layout_marginStart="8dp"
           android:layout_marginLeft="8dp"
           android:layout_marginTop="6dp"
           android:layout_marginBottom="-134dp"
           android:layout_weight="1"
           android:adjustViewBounds="true"
           android:background="@android:color/white"
           android:scaleType="fitCenter"
           android:src="@android:drawable/star_big_on"
           android:visibility="visible"
           tools:visibility="visible" />
    
  2. Update your Kotlin code to get the chart image and put it into the widget, if it exists.
    更新您的 Kotlin 代码以获取图表图像并将其放入小部件(如果存在)中。

    class NewsWidget : AppWidgetProvider() {
       override fun onUpdate(
           context: Context,
           appWidgetManager: AppWidgetManager,
           appWidgetIds: IntArray,
       ) {
           for (appWidgetId in appWidgetIds) {
               // Get reference to SharedPreferences
               val widgetData = HomeWidgetPlugin.getData(context)
               val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
                   // Get chart image and put it in the widget, if it exists
                   val imagePath = widgetData.getString("lineChart", null)
                   val imageFile = File(imagePath)
                   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 @: $imagePath")
                   }
                   // End new code
               }
               appWidgetManager.updateAppWidget(appWidgetId, views)
           }
       }
    }
    

启动应用程序并检测哪个小部件被点击

To detect if the App has been initially started by clicking the Widget you can call HomeWidget.initiallyLaunchedFromHomeWidget() if the App was already running in the Background you can receive these Events by listening to HomeWidget.widgetClicked. Both methods will provide Uris, so you can easily send back data from the Widget to the App to for example navigate to a content page.
要检测应用程序是否已通过单击小部件初始启动,您可以调用HomeWidget.initiallyLaunchedFromHomeWidget()如果应用程序已经在后台运行,您可以通过监听HomeWidget.widgetClicked来接收这些事件。两种方法都会提供 Uris,因此您可以轻松地将数据从 Widget 发送回应用程序,例如导航到内容页面。

In order for these methods to work you need to follow these steps:
为了使这些方法发挥作用,您需要执行以下步骤:

iOS

Add .widgetUrl to your WidgetComponent
将.widgetUrl添加到您的 WidgetComponent

Text(entry.message)
    .font(.body)
    .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget"))

In order to only detect Widget Links you need to add the queryParameterhomeWidget to the URL
为了仅检测 Widget 链接,您需要将 queryParameter homeWidget添加到 URL
</details>

Android Jetpack Glance

Add an IntentFilter to the Activity Section in your AndroidManifest
将IntentFilter添加到AndroidManifest的Activity部分

<intent-filter>
    <action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>

Add the following modifier to your Widget (import from HomeWidget)
将以下修改器添加到您的小部件(从 HomeWidget 导入)

Text(
   message,
   style = TextStyle(fontSize = 18.sp),
   modifier = GlanceModifier.clickable(
     onClick = actionStartActivity<MainActivity>(
       context,
       Uri.parse("homeWidgetExample://message?message=$message")
     )
   )
)
Android XML

Add an IntentFilter to the Activity Section in your AndroidManifest
将IntentFilter添加到AndroidManifest的Activity部分

<intent-filter>
    <action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>

In your WidgetProvider add a PendingIntent to your View using HomeWidgetLaunchIntent.getActivity
在你的 WidgetProvider 中使用HomeWidgetLaunchIntent.getActivity添加一个 PendingIntent 到你的视图中

val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
        context,
        MainActivity::class.java,
        Uri.parse("homeWidgetExample://message?message=$message"))
setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData)

后台更新

As the methods of HomeWidget are static it is possible to use HomeWidget in the background to update the Widget even when the App is in the background.
由于 HomeWidget 的方法是静态的,因此即使应用程序在后台,也可以在后台使用 HomeWidget 来更新 Widget。

The example App is using the flutter_workmanager plugin to achieve this.
Please follow the Setup Instructions for flutter_workmanager (or your preferred background code execution plugin). Most notably make sure that Plugins get registered in iOS in order to be able to communicate with the HomeWidget Plugin.
In case of flutter_workmanager this achieved by adding:
示例应用程序使用flutter_workmanager插件来实现此目的。请遵循 flutter_workmanager(或您首选的后台代码执行插件)的设置说明。最值得注意的是,请确保插件在 iOS 中注册,以便能够与 HomeWidget 插件进行通信。对于 flutter_workmanager,可以通过添加以下内容来实现:

WorkmanagerPlugin.setPluginRegistrantCallback { registry in
    GeneratedPluginRegistrant.register(with: registry)
}

to AppDelegate.swift

Request Pin Widget

Requests to Pin (Add) the Widget to the users HomeScreen by pinning it to the users HomeScreen.
请求通过将小部件固定到用户主屏幕来将小部件固定(添加)到用户主屏幕。

HomeWidget.requestPinWidget(
    name: 'HomeWidgetExampleProvider',
    androidName: 'HomeWidgetExampleProvider',
    qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
);

This method is only supported on Android, API 26+.
If you want to check whether it is supported on current device, use:
此方法仅在Android、API 26+上受支持。如果您想检查当前设备是否支持它,请使用:

HomeWidget.isRequestPinWidgetSupported();

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容