Flutter+Android/ios 桌面小组件

news2024/9/24 8:32:54

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1966384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利率虽降,贷款依旧难?政策暖风下的融资冷思考

​朋友们&#xff0c;最近听到不少人在吐槽贷款难的问题&#xff0c;咱们就来聊聊这背后的故事。你们知道吗&#xff1f;深圳的朱先生可是为这事儿头疼不已。他的公司急需一笔钱来结清货物尾款&#xff0c;可前一年的经营状况不佳&#xff0c;备用金早就见底了。朱先生四处求人…

随机化快速排序 python C C++ 图解 代码 及解析

一&#xff0c;概念及其介绍 快速排序由 C. A. R. Hoare 在 1960 年提出。 随机化快速排序基本思想&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对这两部分数据分别…

真假公主:一场容貌相似引发的宫廷阴谋

真假公主&#xff1a;一场容貌相似引发的宫廷阴谋 从古至今&#xff0c;每个人的心思都不相同&#xff0c;正如他们的面貌各不相同一样。即使人们的外貌没有明显的差异&#xff0c;但他们的内心和性格却是难以改变的。 话说人的面貌是最为独特的&#xff0c;因为每个人都是由不…

LNMP黄金架构搭建部署论坛网站

哈哈哈哈哈我终于部署出来了一个属于自己的论坛&#xff0c;虽然不怎么懂 部署Linux环境 关闭防火墙Systemctl stop firewalldSystemctl disable firewalld关闭selinuxSetence 0查看selinuxGeteforce 部署nginx环境 Yum i install httpd过滤apache的端口号看是否有80端口N…

移动自组织网络(MANET)与互联网连接的网关选择方案文献综述

以下内容节选自这篇综述论文《Gateway Selection Scheme for MANET to Internet Connectivity: A Survey》由Ritu Singh和Jay Prakash撰写&#xff0c;主要探讨了移动自组织网络&#xff08;MANET&#xff09;与互联网连接的网关选择方案。 在接下来的部分中&#xff0c;我们对…

gstreamer实现视频的3D旋转(一)的实现思路

效果&#xff1a; 一、实现思路 首先我们要知道&#xff0c;gstreamer有OpenGL相关的插件&#xff0c;网址如下&#xff1a; GstOpengl (gstreamer.freedesktop.org) 其中&#xff0c;有不少有趣的插件&#xff0c;比如我发现的gltransformation插件&#xff0c;可以实现OpenG…

甄选范文“论层次式架构在系统中的应用”软考高级论文系统架构设计师论文

论文真题 层次架构作为软件系统设计的一种基本模式,对于实现系统的模块化、可维护性和可扩展性具有至关重要的作用。在软件系统的构建过程中,采用层次架构不仅可以使系统结构更加清晰,还有助于提高开发效率和质量。因此,对层次架构的理解和应用是软件工程师必备的技能之一…

普元Devops学习笔记-devops对接jenkins提示crumb不可用问题

前言 普元devops需要对接jenkins&#xff0c;对接jenkins后&#xff0c;devops会调用jenkins的提供的API。 问题 新版本的jenkins提供跨域保护&#xff0c;即大名鼎鼎的CSRF问题。 因此&#xff0c;普元devops调用jenkins的时候&#xff0c;会出现跨域问题。 后台报错信息如…

Python Socket 编程基础

在计算机网络的世界里&#xff0c;Socket 编程是实现不同计算机之间通信的一种基础而强大的方式。Python 作为一种广泛使用的编程语言&#xff0c;其内置的 socket 库使得进行网络编程变得简单而直观。本文将带你走进 Python Socket 编程的世界&#xff0c;通过构建简单的客户端…

若依分离版本部署流程—开启HTTPS访问。

目录 前言 一、申请证书 二、后端打包 三、前端打包 四、服务器部署 ① Redis启动 ② 运行Jar包 ③ 上传ssl证书到服务器 ④ Nginx配置前端部分 五、访问 前言 在若依分离版本的项目部署过程中&#xff0c;跟大多数前后端分离项目差不多&#xff0c;都是前后端分别打包到服…

大型边缘物联平台实战系列01-为什么我们放弃Springboot选择了Nestjs?

引言 我真的很爱Nestjs&#xff0c;那是一种很纯粹、很理性的爱&#xff0c;四年了&#xff0c;我每天都在用它…哦&#xff0c;不对&#xff0c;是我们都在用它。 四年前&#xff0c;在那场剑拔弩张的技术选型会议上&#xff0c;经过十几轮Battle&#xff0c;楼主力排众议将…

MySQL:ORDER BY 排序查询

通过 ORDER BY 条件查询语句可以查询到符合用户需求的数据&#xff0c;但是查询到的数据一般都是按照数据最初被添加到表中的顺序来显示。 基本语法 在MySQL中&#xff0c;排序查询主要通过ORDER BY子句实现。其基本语法如下&#xff1a; SELECT column1, column2, ... FR…

阿里云上进行开发

目的&#xff1a; 直接在阿里云的ECS上面写代码学代码&#xff0c;而不是在本机上写好以后进行部署。 已有前提&#xff1a; 1&#xff0c;WSL 2&#xff0c; vscode 3&#xff0c;阿里云 47.120.66.77 4&#xff0c;通过WSL的 ssh root47.120.66.77 远程登录阿里云。 …

我的《Java全栈高级架构师高薪就业课》学完有什么收获?

我的《Java全栈高级架构师高薪就业课》上线了~ 这是一套Java全栈微服务架构、以实战项目驱动的课程&#xff01;包含34个模块&#xff0c;1514课时。对标阿里P7级别技术栈而研发&#xff0c;有着循序渐进的学习体系&#xff0c;助你开启Java进阶之旅。 学完我的这套《Java全栈高…

学习测试16-仪表项目

项目框架 项目地址 车厂&#xff1a;oem 主机厂 厂商 – 长城魏牌、问界、小米 车型&#xff1a;魏牌c01 供应商&#xff1a;XXX有限公司 人员&#xff1a;总动15人&#xff0c;两种车型&#xff0c;两个组&#xff0c;4人写用例&#xff08;30天&#xff0c;每天100条&#x…

关于实时ODS层数仓搭建的三个问题

目录 问题一&#xff1a;数据同步的实时性无法满足 问题二&#xff1a;批量数据同步计算处理效率低 问题三&#xff1a;没有稳定的数据传输管道 FineDataLink的解决方案 实战案例-销售部门与财务部门数据同步 设置ODS层实时同步任务 设置DW层增量数据同步 设置 DM 层任务汇总 关…

基于YOLOv5的智能路面病害检测系统

随着城市化进程的加速发展&#xff0c;公路基础设施的维护变得日益重要。路面病害&#xff0c;如裂缝、坑洼等&#xff0c;不仅影响行车安全&#xff0c;还会缩短道路使用寿命。因此&#xff0c;快速准确地检测并评估这些病害成为了一个关键任务。本项目旨在开发一款基于YOLOv5…

【常用小机器】下载保存语雀文档

安装工具 npm i -g yuque-dl检查安装&#xff1a; yuque-dl -h私有库 通过别人私有知识库 分享的链接&#xff0c;需使用-t添加token&#xff08;cookie中_yuque_session的值&#xff09;才能下载 yuque-dl "https://www.yuque.com/yuque/thyzgp" -t "abcd.…

GD32 ADC模数转换器

前言&#xff1a; ... 1&#xff1a;简介 12 位 ADC 是一种采用逐次逼近方式的模拟数字转换器。它有 18 个多路复用通道&#xff0c;可以转换来自 16 个外部通道和 2 个内部通道的模拟信号。模拟看门狗允许应用程序来检测输入电压是否超出用户设定的高低阈值。各种通道的 A/D …

解决m-tabbar部分页面元素浮动导致第一个单元格元素点击失效问题

工作中遇到一个神奇的bug&#xff0c;代码没有问题点击第一个单元格的时候无法正常点击&#xff0c;通过调试工具定位发现是m-tabbar组件的m-tabbar-box元素有浮动导致了点击失效。 解决办法1&#xff1a; 设置元素的z-index大于tabbar的&#xff0c;这样要对页面元素底部做一…