应用小部件是您可以嵌入到其他应用(如主屏幕)中的微型应用视图,并接收定期更新。这些视图在用户界面中被称为小部件,您可以使用应用小部件提供程序(或小部件提供程序)发布一个。包含其他小部件的应用组件称为应用小部件主机(或小部件主机)。图 1 显示了一个示例音乐小部件
本文档介绍了如何使用小部件提供程序发布小部件。有关创建您自己的AppWidgetHost
来托管应用小部件的详细信息,请参阅构建小部件主机.
有关如何设计小部件的信息,请参阅应用小部件概述.
小部件组件
要创建小部件,您需要以下基本组件
AppWidgetProviderInfo
对象- 描述小部件的元数据,例如小部件的布局、更新频率和
AppWidgetProvider
类。AppWidgetProviderInfo
在XML 中定义,如本文档所述。 AppWidgetProvider
类- 定义了允许您以编程方式与小部件交互的基本方法。通过它,您会在小部件更新、启用、禁用或删除时收到广播。您在清单中声明
AppWidgetProvider
,然后实现 它,如本文档所述。 - 视图布局
- 定义小部件的初始布局。布局在XML 中定义,如本文档所述。
图 2 显示了这些组件如何在整个应用小部件处理流程中相互配合。
如果您的一个小部件需要用户配置,请实现应用小部件配置活动。此活动允许用户修改小部件设置,例如时钟小部件的时区。
- 从 Android 12(API 级别 31)开始,您可以提供默认配置,并让用户稍后重新配置小部件。有关更多详细信息,请参阅使用小部件的默认配置 和允许用户重新配置已放置的小部件。
- 在 Android 11(API 级别 30)或更低版本中,此活动会在用户每次将小部件添加到其主屏幕时启动。
我们还建议以下改进:灵活的小部件布局、其他增强功能、高级小部件、集合小部件 和构建小部件主机.
声明 AppWidgetProviderInfo XML
The AppWidgetProviderInfo
对象定义了小部件的基本属性。使用单个 <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>
小部件大小属性
默认的主屏幕根据具有定义高度和宽度的单元格网格将小部件放置在其窗口中。大多数主屏幕只允许小部件采用网格单元格的整数倍的大小,例如,水平两个单元格乘以垂直三个单元格。
小部件大小属性允许您指定小部件的默认大小,并提供小部件大小的下限和上限。在这种情况下,小部件的默认大小是小部件第一次添加到主屏幕时所采用的大小。
下表描述了与小部件大小相关的 <appwidget-provider>
属性
属性和描述 | |
---|---|
targetCellWidth 和 targetCellHeight (Android 12)、minWidth 和 minHeight |
targetCellWidth 和 targetCellHeight ,以及 minWidth 和 minHeight ),以便如果用户的设备不支持 targetCellWidth 和 targetCellHeight ,您的应用可以回退使用 minWidth 和 minHeight 。如果支持,targetCellWidth 和 targetCellHeight 属性优先于 minWidth 和 minHeight 属性。 |
minResizeWidth 和 minResizeHeight |
指定小部件的绝对最小大小。这些值指定小部件变得难以辨认或无法使用的大小。使用这些属性,用户可以将小部件调整为小于默认小部件大小的大小。如果 minResizeWidth 属性大于 minWidth 或水平调整大小未启用,则该属性会被忽略。请参阅resizeMode 。同样,如果 minResizeHeight 属性大于 minHeight 或垂直调整大小未启用,则该属性会被忽略。 |
maxResizeWidth 和 maxResizeHeight |
指定小部件的推荐最大大小。如果值不是网格单元格尺寸的倍数,则它们将向上舍入到最接近的单元格大小。如果 maxResizeWidth 属性小于 minWidth 或水平调整大小未启用,则该属性会被忽略。请参阅resizeMode 。同样,如果 maxResizeHeight 属性大于 minHeight 或垂直调整大小未启用,则该属性会被忽略。在 Android 12 中引入。 |
resizeMode |
指定小部件可以调整大小的规则。您可以使用此属性使主屏幕小部件水平、垂直或在两个轴上都可以调整大小。用户触摸并按住小部件以显示其调整大小手柄,然后拖动水平或垂直手柄以更改其在布局网格上的大小。resizeMode 属性的值包括 horizontal 、vertical 和 none 。要将小部件声明为水平和垂直可调整大小,请使用 horizontal|vertical 。 |
示例
为了说明前表中属性如何影响小部件的大小,假设以下规格
- 一个网格单元格宽 30 dp,高 50 dp。
- 提供以下属性规范
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp"
android:minHeight="80dp"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:maxResizeWidth="120dp"
android:maxResizeHeight="120dp"
android:resizeMode="horizontal|vertical" />
从 Android 12 开始
使用 targetCellWidth
和 targetCellHeight
属性作为小部件的默认大小。
小部件的默认大小为 2x2。小部件可以调整大小到 2x1 或 4x3。
Android 11 及更低版本
使用 minWidth
和 minHeight
属性计算小部件的默认大小。
默认宽度 = Math.ceil(80 / 30)
= 3
默认高度 = Math.ceil(80 / 50)
= 2
小部件的默认大小为 3x2。小部件可以调整大小到 2x1 或全屏。
其他小部件属性
下表描述了与小部件大小无关的其他质量相关的<appwidget-provider>
属性。
属性和描述 | |
---|---|
updatePeriodMillis |
定义小部件框架多久调用一次onUpdate() 回调方法,以向AppWidgetProvider 请求更新。实际更新并不保证完全按照此值的时间进行,建议尽可能减少更新频率,每小时不超过一次,以节省电量。有关选择适当更新周期的完整注意事项列表,请参阅 更新小部件内容的优化。 |
initialLayout |
指向定义小部件布局的布局资源。 |
configure |
定义当用户添加小部件时启动的活动,允许他们配置小部件属性。请参阅 启用用户配置小部件。从 Android 12 开始,您的应用可以跳过初始配置。有关详细信息,请参阅 使用小部件的默认配置。 |
description |
为小部件选择器指定描述,以显示您的 widget。在 Android 12 中引入。 |
previewLayout (Android 12) 和 previewImage (Android 11 及更低版本) |
previewImage 和 previewLayout 属性,以便您的应用可以在用户的设备不支持 previewLayout 时回退到使用 previewImage 。有关更多详细信息,请参阅 与可缩放小部件预览向后兼容。 |
autoAdvanceViewId |
指定由小部件主机自动前进的小部件子视图的视图 ID。 |
widgetCategory |
声明您的 widget 是否可以在主屏幕 (home_screen )、锁屏 (keyguard ) 或两者上显示。对于 Android 5.0 及更高版本,仅 home_screen 有效。 |
widgetFeatures |
声明小部件支持的功能。例如,如果您希望您的 widget 在用户添加时使用其默认配置,请同时指定 configuration_optional 和 reconfigurable 标志。这将绕过在用户添加小部件后启动配置活动。用户仍然可以 重新配置小部件。 |
使用 AppWidgetProvider 类处理小部件广播
AppWidgetProvider
类处理小部件广播,并根据小部件生命周期事件更新小部件。以下部分介绍如何在清单中声明 AppWidgetProvider
,然后实现它。
在清单中声明小部件
首先,在您的应用程序的 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
属性,该属性指定小部件使用的 AppWidgetProvider
。除非单独的进程需要向您的 AppWidgetProvider
广播,否则组件不得导出,这种情况通常不会发生。
<intent-filter>
元素必须包含一个具有 android:name
属性的 <action>
元素。此属性指定 AppWidgetProvider
接受 ACTION_APPWIDGET_UPDATE
广播。这是您必须显式声明的唯一广播。 AppWidgetManager
会自动将所有其他小部件广播发送到 AppWidgetProvider
,根据需要。
<meta-data>
元素指定 AppWidgetProviderInfo
资源,并需要以下属性
android:name
:指定元数据名称。使用android.appwidget.provider
来识别数据作为AppWidgetProviderInfo
描述符。android:resource
:指定AppWidgetProviderInfo
资源位置。
实现 AppWidgetProvider 类
AppWidgetProvider
类扩展 BroadcastReceiver
作为方便类来处理小部件广播。它只接收与小部件相关的事件广播,例如当小部件更新、删除、启用和禁用时。当这些广播事件发生时,将调用以下 AppWidgetProvider
方法
onUpdate()
- 这在
AppWidgetProviderInfo
中由updatePeriodMillis
属性定义的间隔内调用以更新小部件。有关更多信息,请参阅此页面中的 描述其他小部件属性的表格。 - 当用户添加小部件时也会调用此方法,因此它会执行基本设置,例如定义
View
对象的事件处理程序或启动作业以加载数据以显示在小部件中。但是,如果您声明了没有configuration_optional
标志的配置活动,则当用户添加小部件时,不会调用此方法,但会在后续更新中调用此方法。配置活动的职责是在配置完成后执行第一次更新。有关更多信息,请参阅 启用用户配置应用小部件。 - 最重要的回调是
onUpdate()
。有关更多信息,请参阅此页面中的 使用onUpdate()
类处理事件。 onAppWidgetOptionsChanged()
当小部件第一次放置以及小部件大小调整时,会调用此方法。使用此回调根据小部件的大小范围显示或隐藏内容。通过调用
getAppWidgetOptions()
获取大小范围(以及从 Android 12 开始的小部件实例可以采用的可能大小列表),该方法返回一个包含以下内容的Bundle
:OPTION_APPWIDGET_MIN_WIDTH
:包含小部件实例宽度的下限,以 dp 单位表示。OPTION_APPWIDGET_MIN_HEIGHT
:包含小部件实例高度的下限,以 dp 单位表示。OPTION_APPWIDGET_MAX_WIDTH
:包含小部件实例宽度的上限,以 dp 单位表示。OPTION_APPWIDGET_MAX_HEIGHT
:包含小部件实例高度的上限,以 dp 单位表示。OPTION_APPWIDGET_SIZES
:包含小部件实例可以采用的可能大小列表 (List<SizeF>
),以 dp 单位表示。在 Android 12 中引入。
onDeleted(Context, int[])
每次从小部件主机删除小部件时都会调用此方法。
onEnabled(Context)
当第一次创建小部件实例时调用此方法。例如,如果用户添加了您的 widget 的两个实例,则此方法只在第一次调用。如果您需要打开一个新的数据库或执行其他仅需为所有小部件实例执行一次的设置,那么这是执行此操作的理想位置。
onDisabled(Context)
当您的小部件的最后一个实例从小部件主机中删除时,会调用此方法。这是您清理在
onEnabled(Context)
中完成的任何工作的地方,例如删除临时数据库。onReceive(Context, Intent)
这在每次广播之前以及每个前面回调方法之前调用。通常您不需要实现此方法,因为默认的
AppWidgetProvider
实现会过滤所有小部件广播,并根据需要调用前面的方法。
您必须使用 AndroidManifest
中的 <receiver>
元素,将 AppWidgetProvider
类实现声明为广播接收器。有关更多信息,请参阅此页面中的 在清单中声明小部件。
使用 onUpdate() 类处理事件
最重要的 AppWidgetProvider
回调是 onUpdate()
,因为当每个小部件添加到主机时都会调用它,除非您使用没有 configuration_optional
标志的配置活动。如果您的 widget 接受任何用户交互事件,则在此回调中注册事件处理程序。如果您的 widget 不创建临时文件或数据库,也不执行需要清理的其他工作,那么 onUpdate()
可能是您需要定义的唯一回调方法。
例如,如果您想要一个带有按钮的小部件,该按钮在点击时启动一个活动,您可以使用以下 AppWidgetProvider
的实现
Kotlin
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) } } }
Java
public class ExampleAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Perform this loop procedure for each widget that belongs to this // provider. for (int i=0; i < appWidgetIds.length; i++) { int appWidgetId = appWidgetIds[i]; // Create an Intent to launch ExampleActivity Intent intent = new Intent(context, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( /* context = */ context, /* requestCode = */ 0, /* intent = */ intent, /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // Get the layout for the widget and attach an onClick listener to // the button. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget_layout); views.setOnClickPendingIntent(R.id.button, pendingIntent); // Tell the AppWidgetManager to perform an update on the current app // widget. appWidgetManager.updateAppWidget(appWidgetId, views); } } }
此 AppWidgetProvider
只定义了 onUpdate()
方法,使用它来创建一个 PendingIntent
,该 PendingIntent
启动一个 Activity
,并使用 setOnClickPendingIntent(int, PendingIntent)
将其附加到小部件的按钮。它包含一个循环,循环遍历 appWidgetIds
中的每个条目,appWidgetIds
是一个 ID 数组,标识由该提供程序创建的每个小部件。如果用户创建了 widget 的多个实例,那么它们都会同时更新。但是,只为 widget 的所有实例管理一个 updatePeriodMillis
计划。例如,如果更新计划定义为每两小时一次,并且在第一个 widget 之后一小时添加了第二个 widget 实例,那么它们都会在第一个定义的周期内更新,并且第二个更新周期会被忽略。它们每两小时更新一次,而不是每小时更新一次。
有关更多详细信息,请参阅 ExampleAppWidgetProvider.java
示例类。
接收 widget 广播意图
AppWidgetProvider
是一个便利类。如果您想直接接收 widget 广播,您可以实现自己的 BroadcastReceiver
或覆盖 onReceive(Context,Intent)
回调。您需要关注的意图如下:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_OPTIONS_CHANGED
创建 widget 布局
您必须在 XML 中定义 widget 的初始布局,并将其保存在项目的 res/layout/
目录中。有关详细信息,请参阅 设计指南。
如果您熟悉 布局,创建 widget 布局很简单。但是,请注意,widget 布局基于 RemoteViews
,它不支持所有类型的布局或视图 widget。您不能使用自定义视图或 RemoteViews
支持的视图的子类。
RemoteViews
还支持 ViewStub
,它是一个不可见的、大小为零的 View
,您可以使用它在运行时延迟加载布局资源。
支持有状态行为
Android 12 添加了对使用以下现有组件的有状态行为的支持:
widget 仍然是无状态的。您的应用程序必须存储状态并注册状态更改事件。
以下代码示例展示了如何实现这些组件。
Kotlin
// Check the view. remoteView.setCompoundButtonChecked(R.id.my_checkbox, true) // Check a radio group. remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2) // Listen for check changes. The intent has an extra with the key // EXTRA_CHECKED that specifies the current checked state of the view. remoteView.setOnCheckedChangeResponse( R.id.my_checkbox, RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent) )
Java
// Check the view. remoteView.setCompoundButtonChecked(R.id.my_checkbox, true); // Check a radio group. remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2); // Listen for check changes. The intent has an extra with the key // EXTRA_CHECKED that specifies the current checked state of the view. remoteView.setOnCheckedChangeResponse( R.id.my_checkbox, RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent));
提供两个布局:一个针对运行 Android 12 或更高版本的设备,位于 res/layout-v31
中,另一个针对之前的 Android 11 或更低版本,位于默认的 res/layout
文件夹中。
实现圆角
Android 12 引入了以下系统参数来设置 widget 圆角的半径:
system_app_widget_background_radius
:widget 背景的角半径,不超过 28 dp。system_app_widget_inner_radius
:widget 内部的任何视图的角半径。这正好比背景半径小 8 dp,以便在使用 8 dp 填充时很好地对齐。
以下示例展示了一个使用 system_app_widget_background_radius
设置 widget 角,并使用 system_app_widget_inner_radius
设置 widget 内部视图角的 widget。
1 widget 的角。
2 widget 内部视图的角。
有关圆角的重要注意事项
- 第三方启动器和设备制造商可以覆盖
system_app_widget_background_radius
参数,使其小于 28 dp。system_app_widget_inner_radius
参数始终比system_app_widget_background_radius
的值小 8 dp。 - 如果您的 widget 不使用
@android:id/background
或不定义一个基于轮廓剪切其内容的背景(android:clipToOutline
设置为true
),启动器会自动识别背景并使用具有最多 16 dp 圆角的矩形剪切 widget。请参阅 确保您的 widget 与 Android 12 兼容。
为了使 widget 与之前的 Android 版本兼容,我们建议定义自定义属性并使用自定义主题来覆盖 Android 12 的自定义属性,如下面的示例 XML 文件所示:
/values/attrs.xml
<resources>
<attr name="backgroundRadius" format="dimension" />
</resources>
/values/styles.xml
<resources>
<style name="MyWidgetTheme">
<item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
</style>
</resources>
/values-31/styles.xml
<resources>
<style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
</style>
</resources>
/drawable/my_widget_background.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="?attr/backgroundRadius" />
...
</shape>
/layout/my_widget_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
android:background="@drawable/my_widget_background" />