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

iOSAndroid
在这里插入图片描述在这里插入图片描述

平台设置

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

iOS

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

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

在这里插入图片描述

添加groupId

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

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

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

在这里插入图片描述

同步 CFBundleVersion(可选)

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

在这里插入图片描述

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

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")
Android (Jetpack Glance)

将 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来选择,回退到.androidName ,如果未提供,它将回退到.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(‘id’, defaultValue: data)

交互式小部件

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

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

    ("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

    在这里插入图片描述

    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()
       }
    }   
    
  4. 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)
    
  5. 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,
  });

  
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: LineChartPainter(),
      child: const SizedBox(
        height: 200,
        width: 200,
      ),
    );
  }
}

在这里插入图片描述

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是设备上键/值存储中的键,存储文件的路径,方便本机端检索)
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
        }
    

在这里插入图片描述

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

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();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值