Android 桌面小组件 AppWidgetProvider

news2024/12/25 9:12:47

Android 桌面小组件 AppWidgetProvider

简介

小组件就是可以添加到手机桌面的窗口。点击窗口可以进入应用或者进入应用的某一个页面。

widget 组件

如需创建 widget,您需要以下基本组件:

  • AppWidgetProviderInfo 对象

    描述 widget 的元数据,例如 widget 的布局、更新频率和 AppWidgetProvider 类。AppWidgetProviderInfo 是使用 XML 定义的,如本文档中所述。

  • AppWidgetProvider

    定义可让您以编程方式与 widget 进行交互的基本方法。通过它,您可以在更新、启用、停用或删除 widget 时接收广播。您在清单中声明 AppWidgetProvider,然后实现它

主要步骤

1.声明 AppWidgetProviderInfo XML

AppWidgetProviderInfo 对象定义了 widget 的基本特性。您可以使用单个 <appwidget-provider> 元素在 XML 资源文件中定义 AppWidgetProviderInfo 对象,并将其保存在项目的 res/xml/ 文件夹中。

具体可见以下示例:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/example_loading_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

widget 大小调整属性

默认主屏幕根据已定义高度和宽度的单元格网格将 widget 放置在其窗口中。大多数主屏幕仅允许 widget 采用网格单元的整数倍的尺寸,例如水平两个单元格的尺寸,垂直方向为三个单元格。

借助 widget 大小调整属性,您可以为 widget 指定默认尺寸,并为 widget 的尺寸提供下限和上限。在此上下文中,widget 的默认大小是该 widget 首次添加到主屏幕时采用的尺寸。

下表介绍了与 widget 大小调整相关的 <appwidget-provider> 属性:

属性和说明
targetCellWidthtargetCellHeight (Android 12)、minWidthminHeight从 Android 12 开始,targetCellWidthtargetCellHeight 属性会根据网格单元指定 widget 的默认大小。这些属性在 Android 11 及更低版本中会被忽略,如果主屏幕不支持基于网格的布局,则可以忽略这些属性。minWidthminHeight 属性以 dp 为单位指定微件的默认大小。如果微件的最小宽度或高度的值与单元格的尺寸不匹配,则这些值将向上舍入为最接近的单元格大小。我们建议您同时指定 targetCellWidthtargetCellHeight 以及 minWidthminHeight 这两组属性,这样,当用户的设备不支持 targetCellWidthtargetCellHeight 时,您的应用可以回退到使用 minWidthminHeight。如果支持,targetCellWidthtargetCellHeight 属性优先于 minWidthminHeight 属性。
minResizeWidthminResizeHeight指定 widget 的绝对最小尺寸。这些值用于指定微件在多大程度上无法辨认或因其他原因而无法使用。使用这些属性,用户可以将 widget 的大小调整为小于默认 widget 大小。如果 minResizeWidth 属性大于 minWidth 或未启用水平大小调整,则系统会忽略该属性。请参阅 resizeMode。同样,如果 minResizeHeight 属性大于 minHeight 或未启用垂直大小调整,则会被忽略。
maxResizeWidthmaxResizeHeight指定微件建议的大小上限。如果值不是网格单元格尺寸的倍数,则将其四舍五入为最接近的单元格大小。如果 maxResizeWidth 属性小于 minWidth 或者未启用水平大小调整,则会被忽略。请参阅 resizeMode。同样,如果 maxResizeHeight 属性大于 minHeight 或未启用垂直大小调整,则会被忽略。在 Android 12 中引入。
resizeMode指定可以按什么规则调整微件的大小。您可以使用此属性让主屏幕微件在水平、垂直或两个轴上均可调整大小。用户轻触并按住某个 widget 以显示其大小调整手柄,然后拖动水平或垂直手柄以更改其在布局网格上的大小。resizeMode 属性的值包括 horizontalverticalnone。如需将 widget 声明为在水平和垂直方向上均可调整大小,请使用 `horizontal

从 Android 12 开始

使用 targetCellWidthtargetCellHeight 属性作为该 widget 的默认尺寸。

默认情况下,微件的大小为 2x2。该 widget 可以缩小到 2x1 或最大 4x3。

Android 11 及更低版本

使用 minWidthminHeight 属性计算 widget 的默认大小。

默认宽度 = Math.ceil(80 / 30) = 3

默认高度 = Math.ceil(80 / 50) = 2

默认情况下,微件的大小为 3x2。该 widget 可以缩小到 2x1 或最大全屏。

其他微件属性

下表介绍了与 widget 大小调整以外的质量相关的 <appwidget-provider> 属性。

属性和说明
updatePeriodMillis定义 widget 框架通过调用 onUpdate() 回调方法从 AppWidgetProvider 请求更新的频率。使用此值无法保证实际更新会准时进行,我们建议您尽可能降低更新频率(每小时不超过一次),以节省电量。 如需查看选择适当更新周期的注意事项的完整列表,请参阅更新微件内容的优化。
initialLayout指向定义 widget 布局的布局资源。
configure定义在用户添加微件时启动的 activity,以便用户配置微件属性。请参阅允许用户配置 widget。 从 Android 12 开始,您的应用可以跳过初始配置。如需了解详情,请参阅使用微件的默认配置。
description指定为您的 widget 显示的 widget 选择器的说明。在 Android 12 中引入。
previewLayout (Android 12) 和 previewImage(Android 11 及更低版本)从 Android 12 开始,previewLayout 属性可指定可缩放的预览,并以 XML 布局形式提供该预览,并将其设置为微件的默认尺寸。理想情况下,指定为此属性的布局 XML 与具有真实默认值的实际 widget 具有相同的布局 XML。在 Android 11 或更低版本中,previewImage 属性指定微件在配置后的外观的预览,用户在选择应用微件时会看到该预览。如果未提供,则用户会看到应用的启动器图标。此字段对应于 AndroidManifest.xml 文件的 <receiver> 元素中的 android:previewImage 属性。注意:我们建议您同时指定 previewImagepreviewLayout 属性,这样一来,当用户的设备不支持 previewLayout 时,您的应用可以回退到使用 previewImage。如需了解详情,请参阅与可缩放的 widget 预览的向后兼容性。
autoAdvanceViewId指定由 widget 托管应用自动前进的 widget 子视图的视图 ID。
widgetCategory声明 widget 是否可以显示在主屏幕 (home_screen) 和/或锁定屏幕 (keyguard) 上。对于 Android 5.0 及更高版本,只有 home_screen 有效。
widgetFeatures声明 widget 支持的功能。例如,如果您希望 widget 在用户添加它时使用其默认配置,请同时指定 configuration_optionalreconfigurable 标志。这样会绕过在用户添加 widget 后启动配置 activity。之后,用户仍然可以重新配置 widget。

组件大小的调整

在这里插入图片描述

2.使用 AppWidgetProvider 类处理 widget 广播

AppWidgetProvider 类会处理 widget 广播并更新 widget,以响应 widget 生命周期事件。以下部分介绍了如何在清单中声明 AppWidgetProvider,然后实现它。

2.1 在清单中声明 widget

首先,在应用的 AndroidManifest.xml 文件中声明 AppWidgetProvider 类,如以下示例所示:

<receiver android:name="ExampleAppWidgetProvider"
                 android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>

<receiver> 元素需要 android:name 属性,该属性指定 widget 使用的 AppWidgetProvider。不得导出该组件,除非需要一个单独的进程向您的 AppWidgetProvider 广播(通常情况并非如此)。

<intent-filter> 元素必须包含具有 android:name 属性的 <action> 元素。此属性指定 AppWidgetProvider 接受 ACTION_APPWIDGET_UPDATE 广播。这是您必须明确声明的唯一一项广播。AppWidgetManager 会根据需要自动将所有其他 widget 广播发送到 AppWidgetProvider

<meta-data> 元素指定 AppWidgetProviderInfo 资源,并且需要以下属性:

  • android:name:指定元数据名称。使用 android.appwidget.provider 将数据标识为 AppWidgetProviderInfo 描述符。
  • android:resource:指定 AppWidgetProviderInfo 资源位置。

2.2 实现 AppWidgetProvider 类

AppWidgetProvider 类扩展了 BroadcastReceiver 作为处理 widget 广播的便捷类。它仅接收与 widget 相关的事件广播,例如当 widget 更新、删除、启用和停用时。当发生这些广播事件时,系统会调用以下 AppWidgetProvider 方法:

  • [onUpdate()](https://developer.android.com/reference/android/appwidget/AppWidgetProvider?hl=zh-cn#onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[]))

    调用此方法可按照 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的间隔更新 widget。如需了解详情,请参阅本页中描述其他微件属性的表。

    用户添加 widget 时也会调用此方法,因此它会执行基本的设置,例如为 View 对象定义事件处理脚本,或启动作业以加载数据以在 widget 中显示。不过,如果您声明的配置 activity 不带 configuration_optional 标志,则当用户添加 widget 时,系统不会调用此方法,但会针对后续更新调用此方法。配置 activity 应负责在配置完成后执行首次更新。如需了解详情,请参阅允许用户配置应用微件。

    最重要的回调是 onUpdate()。如需了解详情,请参阅本页中的使用 onUpdate() 类处理事件。

  • [onAppWidgetOptionsChanged()](https://developer.android.com/reference/android/appwidget/AppWidgetProvider?hl=zh-cn#onAppWidgetOptionsChanged(android.content.Context, android.appwidget.AppWidgetManager, int, android.os.Bundle))

    首次放置 widget 时以及每次调整 widget 大小时,系统都会调用此方法。使用此回调可以根据 widget 的尺寸范围显示或隐藏内容。通过调用 getAppWidgetOptions() 获取尺寸范围,并且从 Android 12 开始,还可获取 widget 实例可以采用的尺寸范围列表,该方法会返回包含以下内容的 BundleOPTION_APPWIDGET_MIN_WIDTH:包含 widget 实例宽度的下限(以 dp 为单位)。OPTION_APPWIDGET_MIN_HEIGHT:包含 widget 实例高度的下限(以 dp 为单位)。OPTION_APPWIDGET_MAX_WIDTH:包含 widget 实例宽度的上限(以 dp 为单位)。OPTION_APPWIDGET_MAX_HEIGHT:包含 widget 实例的高度上限(以 dp 为单位)。OPTION_APPWIDGET_SIZES:包含 widget 实例可以采用的可能尺寸 (List<SizeF>) 的列表(以 dp 为单位)。在 Android 12 中引入。

  • [onDeleted(Context, int[\])](https://developer.android.com/reference/android/appwidget/AppWidgetProvider?hl=zh-cn#onDeleted(android.content.Context, int[]))

    每次从 widget 托管应用中删除 widget 时,系统都会调用此方法。

  • onEnabled(Context)

    首次创建 widget 实例时,系统会调用此方法。例如,如果用户添加 widget 的两个实例,则仅在第一次调用时调用此方法。如果您需要打开新数据库或执行只需要对所有 widget 实例执行一次的其他设置,则非常适合执行此操作。

  • onDisabled(Context)

    当 widget 的最后一个实例从 widget 宿主中删除时,系统会调用此方法。您可以在此处清理在 onEnabled(Context) 中完成的所有工作,例如删除临时数据库。

  • [onReceive(Context, Intent)](https://developer.android.com/reference/android/appwidget/AppWidgetProvider?hl=zh-cn#onReceive(android.content.Context, android.content.Intent))

    系统会针对每个广播调用此方法,它会在上述每个回调方法之前调用。您通常不需要实现此方法,因为默认的 AppWidgetProvider 实现会过滤所有 widget 广播,并视情况调用前面的方法。

您必须使用 AndroidManifest 中的 <receiver> 元素将 AppWidgetProvider 类实现声明为广播接收器。如需了解详情,请参阅本页中的在清单中声明 widget。

2.2.1 使用 onUpdate() 类处理事件

最重要的 AppWidgetProvider 回调是 onUpdate(),因为除非您使用不带 configuration_optional 标志的配置 activity,否则在将每个 widget 添加到宿主时都会调用该回调。如果您的 widget 接受任何用户互动事件,请在此回调中注册事件处理脚本。如果您的 widget 不会创建临时文件或数据库,或执行其他需要清理的工作,那么 onUpdate() 可能是您需要定义的唯一回调方法。

例如,如果您需要一个带有按钮的 widget,该按钮可在点按时启动 activity,则可以使用以下 AppWidgetProvider 实现:

KotlinJava

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        appWidgetIds.forEach { appWidgetId ->
            // Create an Intent to launch ExampleActivity.
            val pendingIntent: PendingIntent = PendingIntent.getActivity(
                    /* context = */ context,
                    /* requestCode = */  0,
                    /* intent = */ Intent(context, ExampleActivity::class.java),
                    /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            val views: RemoteViews = RemoteViews(
                    context.packageName,
                    R.layout.appwidget_provider_layout
            ).apply {
                setOnClickPendingIntent(R.id.button, pendingIntent)
            }

            // Tell the AppWidgetManager to perform an update on the current
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

AppWidgetProvider 仅定义了 onUpdate() 方法,使用该方法创建可启动 Activity 并使用 [setOnClickPendingIntent(int, PendingIntent)](https://developer.android.com/reference/android/widget/RemoteViews?hl=zh-cn#setOnClickPendingIntent(int, android.app.PendingIntent)) 将其附加到微件按钮的 PendingIntent。它包含一个循环遍历 appWidgetIds 中的每个条目,该循环是 ID 数组,用于标识此提供程序创建的每个 widget。如果用户创建多个 widget 实例,那么这些实例将全部同时更新。但是,只能为微件的所有实例管理一个 updatePeriodMillis 时间表。例如,如果将更新时间表定义为每两小时更新一次,并且在第一个实例一小时后添加了该 widget 的第二个实例,那么这两个实例都会根据第一个更新周期定义的更新周期进行更新,而忽略第二个更新周期。两者每两小时更新一次,而不是每小时更新一次。

接收 widget 广播 intent

AppWidgetProvider 是一个便捷类。如果您想直接接收 widget 广播,可以实现自己的 BroadcastReceiver 或替换 [onReceive(Context,Intent)](https://developer.android.com/reference/android/appwidget/AppWidgetProvider?hl=zh-cn#onReceive(android.content.Context, android.content.Intent)) 回调。您需要关注的 intent 如下:

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

2.3 创建 widget 布局

您必须在 XML 中定义 widget 的初始布局,并将其保存在项目的 res/layout/ 目录中。

如果您熟悉布局,那么创建 widget 布局将非常简单。但请注意,widget 布局基于 RemoteViews,并不支持所有类型的布局或视图 widget。您无法使用自定义视图或 RemoteViews 支持的视图子类。

RemoteViews 还支持 ViewStub,这是一种不可见、零大小的 View,可用于在运行时延迟膨胀布局资源。

以下代码示例展示了如何实现这些组件。

// Check
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true);

// 点击单选框
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2);

// 监听选择响应
remoteView.setOnCheckedChangeResponse(
    R.id.my_checkbox,
    RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent));

res/layout-v31 中提供两个布局:一个以搭载 Android 12 或更高版本的设备为目标平台,另一个以旧版 Android 11 或更低版本在默认的 res/layout 文件夹中提供。

支持的部分view

在这里插入图片描述

比如说EditText组件就不支持,然后外面通过代码创建的 RemoteView 就会报错。会有错误提示的,一般都是不支持xxx的view。

示例

1.声明AppWidgetProviderInfo XML

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/desktop_layout"
    android:minWidth="110dp"
    android:minHeight="40dp"
    android:previewImage="@mipmap/ic_launcher_round"
    android:updatePeriodMillis="8640000"
    android:widgetCategory="home_screen|keyguard">

</appwidget-provider>

2.使用 AppWidgetProvider 类处理 widget 广播

<receiver
    android:name=".desktop.DeskTopWidget"
    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/appwidget_info" />
</receiver>

3.实现 AppWidgetProvider 类

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.RemoteViews
import com.example.zanglidemo.MainActivity
import com.example.zanglidemo.R


class DeskTopWidget : AppWidgetProvider() {
    var TAG = "MyWidget:"
    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)
        Log.i(TAG, "接受广播")
    }

    /**
     * 第一个widget被添加调用
     *
     * @param context
     */
    override fun onEnabled(context: Context) {
        super.onEnabled(context)
        Log.i(TAG, "widget  onEnabled 状态")
    }

    /**
     * widget被添加 || 更新时调用
     *
     * @param context
     */
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager?,
        appWidgetIds: IntArray?
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        Log.i(TAG, "widget  onUpdate 状态")

        // 构建布局的 RemoteViews 对象
        // 定义布局文件 R.layout.widget_layout
        val intent = PendingIntent.getActivity(
            context,
            0,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "0")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val intent11 = PendingIntent.getActivity(
            context,
            1,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "1")
                putExtra("name", "观音心咒")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        val intent12 = PendingIntent.getActivity(
            context,
            2,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "1")
                putExtra("name", "百字明")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        val intent13 = PendingIntent.getActivity(
            context,
            3,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "1")
                putExtra("name", "顶礼")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val intent21 = PendingIntent.getActivity(
            context,
            4,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "2")
                putExtra("name", "早")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        val intent22 = PendingIntent.getActivity(
            context,
            5,
            Intent(context, MainActivity::class.java).apply {
                putExtra("type", "2")
                putExtra("name", "晚")
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 更新内容
        val remoteViews: RemoteViews =
            RemoteViews(context.packageName ?: "", R.layout.desktop_layout).apply {
                setOnClickPendingIntent(R.id.desk_bg, intent)
                setOnClickPendingIntent(R.id.desk_title, intent)

                setOnClickPendingIntent(R.id.desk_vf1, intent11)
                setOnClickPendingIntent(R.id.desk_vf2, intent12)
                setOnClickPendingIntent(R.id.desk_vf3, intent13)

                setOnClickPendingIntent(R.id.desk_fast_morn, intent21)
                setOnClickPendingIntent(R.id.desk_fast_even, intent22)
            }

        // 更新小组件 UI
        appWidgetManager?.updateAppWidget(appWidgetIds, remoteViews)
    }

    /**
     * 最后一个widget被删除时调用
     *
     * @param context
     */
    override fun onDisabled(context: Context) {
        super.onDisabled(context)
        Log.i(TAG, "widget  onDisabled 状态")
    }
}

4. widget 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/desk_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_very"
    android:backgroundTint="@color/white"
    android:orientation="vertical"
    android:padding="5dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/desk_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:drawableLeft="@drawable/heart_24dp"
            android:drawablePadding="2dp"
            android:drawableTint="@color/red"
            android:gravity="center"
            android:text="修行"
            android:textColor="@color/gray"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/desk_fast_morn"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginLeft="5dp"
            android:background="@drawable/bg_circle"
            android:backgroundTint="@color/blue"
            android:gravity="center"
            android:text="早"
            android:textColor="@color/white" />

        <TextView
            android:id="@+id/desk_fast_even"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginLeft="5dp"
            android:background="@drawable/bg_circle"
            android:backgroundTint="@color/spring_green"
            android:gravity="center"
            android:text="晚"
            android:textColor="@color/white" />
    </LinearLayout>

    <ViewFlipper
        android:id="@+id/desk_vf"
        android:layout_width="140dp"
        android:layout_height="35dp"
        android:layout_marginTop="10dp"
        android:autoStart="true"
        android:background="@drawable/bg_very"
        android:backgroundTint="@color/greenyellow"
        android:inAnimation="@anim/anim_in"
        android:outAnimation="@anim/anim_out">

        <TextView
            android:id="@+id/desk_vf1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center"
            android:text="观音心咒"
            android:textColor="@color/gray"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/desk_vf2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center"
            android:text="bzm"
            android:textColor="@color/gray"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/desk_vf3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center"
            android:text="顶礼"
            android:textColor="@color/gray"
            android:textSize="18sp"
            android:textStyle="bold" />
    </ViewFlipper>

</LinearLayout>

5.activity中的处理

fun onHandlePendingIntent() {
        Log.e(TAG, "onHandlePendingIntent:type= ${intent.getStringExtra("type")}")
        Log.e(TAG, "onHandlePendingIntent:name= ${intent.getStringExtra("name")}")

        when (intent.getStringExtra("type")) {
            "0" -> {
                Log.i(TAG, "onActivityResult: 打开界面0")
                //do nothing
            }

            "1", "2" -> {
                Log.i(TAG, "onActivityResult: 打开界面1、2")
                Handler(Looper.getMainLooper()).postDelayed({
                    //同样也需要延迟
                }, 500)
            }
        }
    }

这里最好延迟处理一下,否则容易出问题

6.允许用户固定 widget

在搭载 Android 8.0(API 级别 26)及更高版本的设备上,允许用户创建固定快捷方式的启动器还可让用户将 widget 固定到主屏幕上。与固定快捷方式类似,这些固定的 widget 让用户能够访问应用中的特定任务,并且可以直接从应用添加到主屏幕

val appWidgetManager = AppWidgetManager.getInstance(context)
val myProvider = ComponentName(requireContext(), DeskTopWidget::class.java)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    if (appWidgetManager.isRequestPinAppWidgetSupported()){
        val successCallback = PendingIntent.getBroadcast(
            /* context = */ context,
            /* requestCode = */ 0,
            /* intent = */ Intent(),
        /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT)

        appWidgetManager.requestPinAppWidget(myProvider, null, successCallback)
    }
}

注意 :如果您的应用不需要接收系统是否成功将 widget 固定到受支持的启动器上的通知,您可以将 null 作为 requestPinAppWidget() 的第三个参数传入。

参考链接

官方文档

如何开发自己的 AppWidget 小组件程序:构建应用微件

如何开发「托管 AppWidget 小组件」的宿主应用(类似于手机桌面 Launcher 应用):构建应用微件托管应用

如何设置 AppWidget 小组件的最小尺寸:应用微件设计指南

Android 小组件 AppWidgetProvider

Android 桌面小组件 AppWidgetProvider

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

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

相关文章

Linux基础配置(镜像挂载,FQDN)

CentOS基础配置&#xff1a; 以下是appsrv的基础配置脚本&#xff0c;CentOS系统只需要把appsrv改成需要的主机名即可&#xff08;因为Linux基础配置都差不多&#xff0c;写脚本是最省时间的做法&#xff0c;IP地址的配置一般用nmtui图形化界面工具&#xff09; #!/bin/bash …

告别杂乱桌面,开启纯净视界!DeskCover Pro,Mac用户的桌面神器!

DeskCover Pro for Mac是一款专为macOS设计的桌面图标隐藏软件&#xff0c;其主要功能和特点包括&#xff1a; 桌面图标隐藏&#xff1a;通过单击鼠标或按全局热键&#xff0c;可以快速隐藏桌面上的所有图标&#xff0c;为您提供一个干净整洁的工作环境。窗口聚焦&#xff1a;…

ChatGPT-Next-Web漏洞利用分析(CVE-2023-49785)

1. 漏洞介绍 ​ 日常网上冲浪&#xff0c;突然粗看以为是有关Chat-GPT的CVE披露出来了&#xff0c;但是仔细一看原来是ChatGPT-Next-Web的漏洞。漏洞描述大致如下&#xff1a;&#xff08;如果有自己搭建了还没更新的速速修复升级防止被人利用&#xff0c;2.11.3已经出来了&am…

MySQL—多表设计与查询

目录 多表设计 ▐ 数据库设计范式 ▐ 多对一 关系表设计 ▐ 多对多 关系表设计 关联查询 ▐ 概述 ▐ 内连接 ○ 思考&#xff1f; ▐ 左外连接 ▐ 右外连接 多表设计 ▐ 数据库设计范式 • 第一范式&#xff1a;确保每列保持原子性 ( 列不可再分解 ) 例如联系方式…

Mac数据恢复软件快速比较:适用于Macbook的10佳恢复软件

数据丢失导致无数个人和组织每天损失大量资金。更糟糕的是&#xff0c;某些文件具有货币价值和情感意义&#xff0c;使它们不可替代&#xff0c;并使数据恢复成为唯一可行的选择。最好的消息是Mac用户可以从各种数据恢复程序中进行选择。为了帮助您尽可能快速、轻松地恢复丢失的…

弹性云服务器是什么,为何如此受欢迎

云计算作为当下炙手可热的技术领域&#xff0c;已然成为现代企业不可或缺的核心能力。云服务器作为云计算的基石之一&#xff0c;在这个数字化时代发挥着至关重要的作用。而弹性云服务器&#xff0c;作为云服务器的一种演进形式&#xff0c;更是备受瞩目。 弹性云服务器&#…

求知导刊-知网收录//旬刊//如何投稿?

求知导刊-知网收录//旬刊//如何投稿&#xff1f; 《求知导刊》栏目设置 理论探索、课堂教学、教改课改、教育管理、教师教育、教学案例、学科进展、学术论坛。 《求知导刊》征稿对象&#xff1a; 全国科技工作者、教育工作者&#xff0c;各级科技与教育部门的领导者以及管理…

鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位

时间概念太重要了&#xff0c;在鸿蒙内核又是如何管理和使用时间的呢? 时间管理以系统时钟 g_sysClock 为基础&#xff0c;给应用程序提供所有和时间有关的服务。 用户以秒、毫秒为单位计时.操作系统以Tick为单位计时&#xff0c;这个认识很重要. 每秒的tick大小很大程度上决…

AI绘画成果展(第一期)

免费获取更多原图&#xff0c;备注“AI绘画”&#xff0c;可在文章末尾点击名片进qun获取。 免费获取更多原图&#xff0c;备注“AI绘画”&#xff0c;可在文章末尾点击名片进qun获取。

Python密码测试程序

下面是一个简单的 Python 密码测试程序&#xff0c;用于检查用户输入的密码是否符合一些基本的安全要求&#xff0c;如长度、包含字母和数字等。这个程序可以作为一个基本的密码验证器&#xff0c;你可以根据需要进行修改和扩展。 1、问题背景 我们正在编写一个程序&#xff0…

花园牛奶:从靠谱奶牛到新鲜牛奶的匠心之旅

在花园乳业有限公司&#xff0c;我们深知生产出优质牛奶的秘诀——从靠谱的奶牛开始。为此&#xff0c;我们特意引进了品质卓越的荷斯坦奶牛&#xff0c;它们以“黑白花”的优雅身姿&#xff0c;成为了我们牧场上的明星。荷斯坦奶牛以其出色的生产性能和高产奶量而著称&#xf…

uniapp 自定义App UrlSchemes

需求&#xff1a;外部浏览器H5页面&#xff0c;跳转到uniapp开发的原生app内部。 1、uniapp内部的配置&#xff1a; &#xff08;1&#xff09;打开manifest->App常用其他设置&#xff0c;如下&#xff0c;按照提示输入您要设置的urlSchemes&#xff1a; &#xff08;2&am…

3W 1.5KVDC 3KVDC 隔离宽范围输入,单、双输出 DC/DC 电源模块——TP2L-3W 系列

TP2L-3W系列是一款高性能、超小型的电源模块&#xff0c;宽范围2:1,4:1输入&#xff0c;输出有稳压和连续短路保护功能&#xff0c;隔离电压为1.5KVDC、3KVDC工作温度范围为–40℃到85℃。特别适合对输出电压的精度有严格要求的地方&#xff0c;外部遥控功能对您的设计又多一项…

读源码系列文章--开源项目openjob之alarm告警模块

一、背景 告警模块&#xff0c;作为大多数应用都存在的一个基础功能&#xff0c;今天我们就以开源项目openjob 为例&#xff0c;分析其设计及实现。 首先&#xff0c;我们梳理一下需求&#xff1a; 支持多种告警方式&#xff0c;包括钉钉、飞书、微信和webhook。方便业务模块…

多模态大模型学杂了能力反下降?新研究:MoE+通用专家解决冲突

微调&#xff0c;能让通用大模型更加适配具体的行业应用。 但现在&#xff0c;研究人员们却发现&#xff1a; 对多模态大模型做“多任务指令微调”&#xff0c;大模型可能会“学得多错得多”&#xff0c;因为不同任务之间的冲突&#xff0c;导致泛化能力下降。 △多模态指令微…

扩展学习|一文读懂知识图谱

一、知识图谱的技术实现流程及相关应用 文献来源&#xff1a;曹倩,赵一鸣.知识图谱的技术实现流程及相关应用[J].情报理论与实践,2015, 38(12):127-132. &#xff08;一&#xff09;知识图谱的特征及功能 知识图谱是为了适应新的网络信息环境而产生的一种语义知识组织和服务的方…

WRT1900ACS搭建openwrt服务器小记

参考链接 wrt1900acs openwrt wrt1900acs openwrt 刷机 wrt1900acs原生固件刷openwrt-23.05.3-mvebu-cortexa9-linksys_wrt1900acs-squashfs-factory.img wrt1900acs openwrt更新刷openwrt-23.05.3-mvebu-cortexa9-linksys_wrt1900acs-squashfs-sysupgrade.bin 通过WEB UI来…

linux的信号量的使用

1.信号量 在多线程情况下&#xff0c;线程要进入关键代码就得获取信号量&#xff08;钥匙&#xff09;{sem_init(&sem, 0, 0);}&#xff0c;没有信号量的情况下就一直等待sem_wait(&sem)&#xff0c;只到别人把钥匙&#xff08;sem_post(&sem)&#xff09;给你。 …

从哪些方面可以看出现货黄金价格走势?

现货黄金价格的走势受到多种因素的影响&#xff0c;我们可以从宏观经济环境、货币政策、供需关系、市场情绪和技术分析几个主要方面来观察和分析这一贵金属的价格动态。现货黄金作为全球投资市场中的避险资产&#xff0c;其价格波动往往能体现出复杂的经济和政治变化。 宏观经济…

QT+多线程编程

QT的多线程编程有两种 1、自定义类继承QThread 第一种是自定义一个类继承于QThread&#xff0c;重写run()方法来实现。然后当需要使用线程的时候你就新建一个自定义对象&#xff0c;然后调用start方法开始运行。 下面的例子是widget里面创建一个线程&#xff0c;然后调用sta…