改进 widget

本页详细介绍了从 Android 12(API 级别 31)开始提供的可选 widget 增强功能。这些功能并非必需的,但实现起来很简单,并且可以改善用户的 widget 体验。

使用动态配色

从 Android 12 开始,微件可以为按钮、背景及其他组件使用设备主题颜色。这可以实现更顺畅的过渡,并在不同的 widget 之间保持一致。

您可以通过以下两种方式实现动态配色:

在根布局中设置主题后,您可以在根布局或其任何子布局中使用常见的颜色属性来获取动态颜色。

以下是您可以使用的颜色属性的一些示例:

  • ?attr/primary
  • ?attr/primaryContainer
  • ?attr/onPrimary
  • ?attr/onPrimaryContainer

在以下使用 Material 3 主题的示例中,设备的主题颜色为“紫色”。强调色和微件背景会根据浅色和深色模式进行调整,如图 1 和图 2 所示。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="?attr/colorPrimaryContainer"
  android:theme="@style/Theme.Material3.DynamicColors.DayNight">

  <ImageView
    ...
    app:tint="?attr/colorPrimaryContainer"
    android:src="@drawable/ic_partly_cloudy" />

    <!-- Other widget content. -->

</LinearLayout>
采用浅色模式主题的微件
图 1. 采用浅色主题的微件。
采用深色模式主题的微件
图 2. widget 采用深色主题。

动态配色的向后兼容性

动态配色仅适用于搭载 Android 12 或更高版本的设备。如需为较低版本提供自定义主题,请使用您的自定义颜色和默认主题属性创建新的限定符 (values-v31)。

下面是一个使用 Material 3 主题的示例:

/values/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="Theme.Material3.DynamicColors.DayNight">
    <!-- Override default colorBackground attribute with custom color. -->
    <item name="android:colorBackground">@color/my_background_color</item>

    <!-- Add other colors/attributes. -->

  </style>
</resources>

/values-v31/styles.xml

<resources>
  <!-- Do not override any color attribute. -->
  <style name="MyWidgetTheme" parent="Theme.Material3.DynamicColors.DayNight" />
</resources>

/layout/my_widget_layout.xml

<resources>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:background="?android:attr/colorBackground"
    android:theme="@style/MyWidgetTheme" />
</resources>

启用语音支持

借助与应用有关的 Action,Google 助理可以显示 widget,以响应用户的相关语音指令。通过将 widget 配置为响应内置 intent (BII),您的应用可以在 Google 助理平台(例如 Android 和 Android Auto)上主动显示 widget。用户可以选择将 Google 助理显示的 widget 固定到启动器,从而鼓励用户日后继续互动。

例如,您可以为锻炼应用配置锻炼摘要 widget,以执行触发 GET_EXERCISE_OBSERVATION BII 的用户语音指令。当用户通过发出“Hey Google, how many miles have I run this week on ExampleApp?”等请求来触发此 BII 时,Google 助理会主动显示您的 widget。

有数十种 BII 涵盖多种类别的用户互动,让几乎所有 Android 应用都可以增强其 widget 的语音功能。如需开始使用,请参阅将与应用有关的 Action 与 Android widget 集成

改进应用的微件选择器体验

借助 Android 12,您可以添加缩放的微件预览和微件说明。借助 Android 15,您可以使用生成的微件预览来改进应用的微件选择器体验。

如需提升应用的 widget 选择器体验,请在 Android 15 及更高版本的设备上提供生成的 widget 预览,针对搭载 Android 12 到 Android 14 的设备提供缩放后的 widget 预览(通过指定 previewLayout),以及针对更低版本提供 previewImage

向微件选择器添加生成的微件预览

应用必须在模块 build.gradle 文件中将 compileSdk 值设置为 35 或更高级别,才能向搭载 Android 15 或更高版本的设备上的 widget 选择器提供 RemoteViews。这意味着应用可以更新选择器中的内容,以更准确地反映用户看到的内容。

应用可以使用 AppWidgetManagersetWidgetPreviewgetWidgetPreview 方法,使用最新的个性化信息更新其微件的外观。

使用 Jetpack Glance 生成更新后的预览

Glance.compose 会运行一个组合,因此可组合项的正文中不会使用挂起函数、流程或类似的异步调用。相反,您必须使用常量数据。

以下示例使用 Jetpack Glance 生成更新后的预览。为了使 setWidgetPreview 在此代码段中显示为方法,需要将 compileSdk build 设置为 35 或更高。

AppWidgetManager.getInstance(appContext).setWidgetPreview(
    ComponentName(
        appContext,
        ExampleAppWidgetReceiver::class.java
    ),
    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
    ExampleAppWidget().compose(
        context = appContext
    ),
)

在不使用 Jetpack Glance 的情况下生成更新后的预览

您可以在没有 Glance 的情况下使用 RemoteViews。以下示例会加载 XML 微件布局资源,并将其设置为预览。必须将 compileSdk build 设置设为 35 或更高版本,才能在此代码段中将 setWidgetPreview 显示为方法。

AppWidgetManager.getInstance(appContext).setWidgetPreview(
    ComponentName(
        appContext,
        ExampleAppWidgetReceiver::class.java
    ),
    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
    RemoteViews("com.example", R.layout.widget_preview)
)

MyAppWidget().update(context, glanceId)

向微件选择器添加可缩放的微件预览

从 Android 12 开始,微件选择器中显示的微件预览是可缩放的。您可以将其作为 XML 布局提供,该布局设置为微件的默认大小。以前,微件预览是静态可绘制资源,在某些情况下,会导致预览在添加到主屏幕后不能准确地反映微件的显示方式。

如需实现可缩放的 widget 预览,请改用 appwidget-provider 元素的 previewLayout 属性提供 XML 布局:

<appwidget-provider
    android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>

我们建议使用与实际微件相同的布局,并使用真实的默认值或测试值。大多数应用使用相同的 previewLayoutinitialLayout。如需了解如何创建准确的预览布局,请参阅本页中的以下部分。

我们建议您同时指定 previewLayoutpreviewImage 属性,以便在用户设备不支持 previewLayout 时,应用可以回退使用 previewImagepreviewLayout 属性优先于 previewImage 属性。

构建准确预览的推荐方法

如需实现可缩放的 widget 预览,请使用 appwidget-provider 元素的 previewLayout 属性提供 XML 布局:

<appwidget-provider
    ...
    android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>
显示 widget 预览的图片
图 3.微件预览,默认显示在 3x3 区域中,但由于其 XML 布局,它也可以适应 3x1 区域。

如需显示准确的预览,您可以通过完成以下步骤,直接为实际 widget 布局提供默认值:

  • TextView 元素设置 android:text="@string/my_widget_item_fake_1"

  • ImageView 组件设置默认或占位图片或图标,例如 android:src="@drawable/my_widget_icon"

如果没有默认值,预览可能会显示不正确的值或空值。这种方法的一个重要优势是,您可以提供本地化的预览内容。

如需详细了解针对包含 ListViewGridViewStackView 的更复杂的预览的推荐方法,请参阅构建包含动态项的精确预览

向后兼容可缩放的 widget 预览

如需让 Android 11(API 级别 30)或更低版本中的微件选择器显示微件的预览,请指定 previewImage 属性。

如果您更改微件的外观,请更新预览图片。

为微件添加说明

从 Android 12 开始,请为要为 widget 显示的 widget 选择器提供说明。

一张图片,显示微件选择器,其中显示了微件及其说明
图 4. 显示 widget 及其说明的示例 widget 选择器。

使用 &lt;appwidget-provider&gt; 元素的 description 属性为微件提供说明:

<appwidget-provider
    android:description="@string/my_widget_description">
</appwidget-provider>

您可以在以前的 Android 版本上使用 descriptionRes 属性,但微件选择器会忽略该属性。

实现更流畅的过渡

从 Android 12 开始,当用户从 widget 启动您的应用时,启动器会提供更顺畅的过渡。

为了实现这种改进的过渡,请使用 @android:id/backgroundandroid.R.id.background 标识背景元素:

// Top-level layout of the widget.
<LinearLayout
    android:id="@android:id/background">
</LinearLayout>

您的应用可以在以前的 Android 版本中使用 @android:id/background,而不会发生崩溃,但系统会忽略它。

使用 RemoteViews 的运行时修改

从 Android 12 开始,您可以利用多种 RemoteViews 方法,它们为 RemoteViews 属性提供运行时修改。如需查看所添加方法的完整列表,请参阅 RemoteViews API 参考文档。

以下代码示例展示了如何使用其中的一些方法。

Kotlin

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList())

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP)

Java

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList());

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP);