应用小部件是您可以嵌入其他应用(例如主屏幕)并接收定期更新的微型应用视图。这些视图在用户界面中被称为小部件,您可以使用应用小部件提供程序(或小部件提供程序)发布一个小部件。容纳其他小部件的应用程序组件称为应用小部件宿主(或小部件宿主)。图1显示了一个示例音乐小部件
本文档介绍如何使用小部件提供程序发布小部件。有关创建您自己的AppWidgetHost
以托管应用小部件的详细信息,请参阅构建小部件宿主。
有关如何设计小部件的信息,请参阅应用小部件概述。
小部件组件
要创建小部件,您需要以下基本组件
AppWidgetProviderInfo
对象- 描述小部件的元数据,例如小部件的布局、更新频率和
AppWidgetProvider
类。AppWidgetProviderInfo
是在XML中定义的,如本文档所述。 AppWidgetProvider
类- 定义允许您以编程方式与小部件交互的基本方法。通过它,您可以接收小部件更新、启用、禁用或删除时的广播。您在清单中声明
AppWidgetProvider
,然后实现它,如本文档所述。 - 视图布局
- 定义小部件的初始布局。布局是在XML中定义的,如本文档所述。
图2显示了这些组件如何融入整体应用小部件处理流程。
如果您的部件需要用户配置,请实现应用小部件配置活动。此活动允许用户修改部件设置,例如时钟部件的时区。
- 从Android 12(API级别 31)开始,您可以提供默认配置,并让用户稍后重新配置小部件。有关更多详细信息,请参阅使用小部件的默认配置和允许用户重新配置已放置的小部件。
- 在Android 11(API级别 30)或更低版本中,每次用户将小部件添加到主屏幕时都会启动此活动。
我们还建议以下改进:灵活的小部件布局、其他增强功能、高级小部件、集合小部件和构建小部件宿主。
声明AppWidgetProviderInfo XML
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 |
为部件选择器指定要为您的部件显示的描述。在 Android 12 中引入。 |
previewLayout (Android 12)和 previewImage (Android 11 及更低版本) |
previewImage 和 previewLayout 属性,以便如果用户的设备不支持 previewLayout ,您的应用可以回退到使用 previewImage 。有关更多详细信息,请参阅 与可缩放部件预览的向后兼容性。 |
autoAdvanceViewId |
指定部件子视图的视图 ID,该子视图由部件的主机自动推进。 |
widgetCategory |
声明您的部件是否可以在主屏幕 (home_screen )、锁屏 (keyguard ) 或两者上显示。对于 Android 5.0 及更高版本,只有 home_screen 有效。 |
widgetFeatures |
声明部件支持的特性。例如,如果您希望您的部件在用户添加它时使用其默认配置,则同时指定 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>
元素必须包含一个 <action>
元素,其中包含 android:name
属性。此属性指定 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)
当部件的实例第一次创建时调用此方法。例如,如果用户添加了两个您的部件实例,则只在第一次调用此方法。如果您需要打开新的数据库或执行其他仅需为所有部件实例执行一次的设置,那么这是一个好地方。
onDisabled(Context)
当您的部件的最后一个实例从部件主机中删除时调用此方法。在这里,您可以清理在
onEnabled(Context)
中完成的任何工作,例如删除临时数据库。onReceive(Context, Intent)
这将针对每个广播以及每个前面的回调方法之前调用。您通常不需要实现此方法,因为默认的
AppWidgetProvider
实现会过滤所有部件广播并根据需要调用前面的方法。
您必须使用 AndroidManifest
中的 <receiver>
元素将您的 AppWidgetProvider
类实现声明为广播接收器。有关更多信息,请参阅本页中的 在清单中声明部件。
使用 onUpdate() 类处理事件
最重要的 AppWidgetProvider
回调是 onUpdate()
,因为除非您使用没有 configuration_optional
标志的配置活动,否则会在每个部件添加到主机时调用它。如果您的部件接受任何用户交互事件,则在此回调中注册事件处理程序。如果您的部件不创建临时文件或数据库,或执行需要清理的其他工作,则 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
,启动一个Activity
,并使用setOnClickPendingIntent(int, PendingIntent)
将其附加到小部件的按钮上。它包含一个循环,迭代appWidgetIds
中的每个条目,这是一个标识此提供程序创建的每个小部件的ID数组。如果用户创建了多个小部件实例,则它们将同时更新。但是,所有小部件实例都只管理一个updatePeriodMillis
调度。例如,如果更新计划定义为每两小时一次,并且在第一个小部件添加一小时后添加了第二个小部件实例,则它们都将在第一个小部件定义的周期内更新,而第二个更新周期将被忽略。它们都每两小时更新一次,而不是每小时一次。
更多详情,请参阅ExampleAppWidgetProvider.java
示例类。
接收小部件广播意图
AppWidgetProvider
是一个便捷类。如果您想直接接收小部件广播,您可以实现您自己的BroadcastReceiver
或覆盖onReceive(Context,Intent)
回调。您需要注意的意图如下:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_OPTIONS_CHANGED
创建小部件布局
您必须在XML中定义小部件的初始布局,并将其保存在项目的res/layout/
目录中。有关详细信息,请参阅设计指南。
如果您熟悉布局,创建小部件布局很简单。但是,请注意,小部件布局基于RemoteViews
,它不支持所有类型的布局或视图小部件。您不能使用自定义视图或受RemoteViews
支持的视图的子类。
RemoteViews
还支持ViewStub
,它是一个不可见的、零大小的View
,您可以使用它在运行时延迟加载布局资源。
支持有状态行为
Android 12 使用以下现有组件添加了对有状态行为的支持:
小部件仍然是无状态的。您的应用必须存储状态并注册状态更改事件。
以下代码示例显示如何实现这些组件。
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 引入了以下系统参数来设置小部件圆角的半径:
system_app_widget_background_radius
:小部件背景的角半径,绝不超过 28 dp。system_app_widget_inner_radius
:小部件内任何视图的角半径。这比背景半径正好小 8 dp,以便在使用 8 dp 填充时很好地对齐。
以下示例显示一个小部件,它使用system_app_widget_background_radius
作为小部件的角,使用system_app_widget_inner_radius
作为小部件内的视图。
1 小部件的角。
2 小部件内视图的角。
圆角的重要注意事项
- 第三方启动器和设备制造商可以将
system_app_widget_background_radius
参数覆盖为小于 28 dp。system_app_widget_inner_radius
参数始终比system_app_widget_background_radius
的值小 8 dp。 - 如果您的窗口小部件不使用
@android:id/background
或不定义一个基于轮廓剪辑其内容的背景——android:clipToOutline
设置为true
——启动器会自动识别背景并使用具有最大 16 dp 圆角的矩形剪辑窗口小部件。请参阅确保您的窗口小部件与 Android 12 兼容。
为了使小部件与之前的 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" />