Android 12 微件改进

Android 12 改进了现有的 Widgets API,以改善平台和启动器中的用户和开发者体验。您可以使用本指南了解如何确保微件与 Android 12 兼容,也可以将其用作更新现有微件的 API 的参考。

替代文本

确保微件与 Android 12 兼容

Android 12 中的微件具有圆角。将应用微件用在搭载 Android 12 或更高版本的设备上时,启动器会自动识别微件的背景,并将其剪裁成具有圆角。

在这种情况下,微件在以下任一条件下可能无法正确显示:

  • 微件的角包含内容:这可能会导致角区域的某些内容被剪裁。

  • 微件使用不易裁剪的背景:这包括透明背景、空视图或布局,或者其他任何类型的不易裁剪的特殊背景。系统可能无法正确识别要使用的背景。

如果微件将受此变更的影响,我们建议您将其更新为具有圆角(如下一部分所述),以确保其正确显示。

使用示例

如需了解所有这些新 API 的实际运用,请查看我们的示例列表微件

实现圆角

Android 12 引入了以下系统参数来设置微件圆角的半径:

以下示例展示了一个微件,为微件的角使用 system_app_widget_background_radius,而为微件内的视图使用 system_app_widget_inner_radius

替代文本

1 微件的角。

2 微件内视图的角。

圆角的向后兼容性

为了确保微件与以前的 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" />

应用动态颜色

在 Android 12 中,微件可以为按钮、背景及其他组件使用设备主题颜色。这样可使过渡更流畅,而且还能在不同的微件之间保持一致。

在以下示例中,设备主题颜色为“呈褐色”,这使得强调色和微件背景进行调整。为此,您可以使用系统的默认主题 (@android:style/Theme.DeviceDefault.DayNight) 及其颜色属性。一些常用的颜色属性如下:

  • ?android:attr/colorAccent
  • ?android:attr/colorBackground
  • ?android:attr/textColorPrimary?android:attr/textColorSecondary
采用浅色模式主题的微件
采用浅色主题的微件
采用深色模式主题的微件
采用深色主题的微件

动态颜色的向后兼容性

我们建议您为 Android 12 创建自定义主题并将其替换。以下示例展示了如何使用各种类型的 XML 文件执行此操作:

/values/styles.xml

<resources>
  <style name="MyWidget.TextView">
    <item name="android:textColor">@color/my_text_color</item>
  </style>
  <style name="MyWidgetTheme">
    <item name="textViewStyle">@style/MyWidget.TextView</item>
  </style>
</resources>

/values-31/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="Theme.DeviceDefault.DayNight" />
</resources>

/layout/my_widget_layout.xml

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

使微件的个性化设置更容易

如果您使用 appwidget-providerconfigure 属性指定配置 activity,在用户将微件添加到其主屏幕之后,应用微件宿主会立即启动该 activity。

Android 12 添加了一些新选项,可让您为用户提供更好的配置体验。

允许用户重新配置已放置的微件

如需配置标记为可重新配置的微件,用户可以长按微件。此时会显示一个重新配置按钮,用户可以点按该按钮来更改设置。

替代文本

1“重新配置”按钮。

appwidget-providerwidgetFeatures 属性中指定 reconfigurable 标志:

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable">
</appwidget-provider>

使用微件的默认配置

如果您希望微件在用户添加它时使用其默认配置,您可以跳过配置步骤,方法是在 widgetFeatures 字段中同时指定 configuration_optionalreconfigurable 标志。这样会在用户添加微件后绕过启动配置 activity。(如前所述,用户以后仍可重新配置微件。)

例如,时钟微件可以绕过初始配置,并在默认情况下显示设备时区。

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

微件配置选项的向后兼容性

您的应用可以在以前的 Android 版本中使用 configuration_optionalreconfigurable 标志。不过,这些标志不会有任何作用,系统仍会启动配置 activity。

添加新的复合按钮

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 will have 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 will have 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 版本(在默认的 res/layout 文件夹中)。

使用改进的 API 设置微件大小和布局

从 Android 12 开始,您可以提供更精细的大小属性和更灵活的布局,具体方法如下:

  1. 指定额外的微件大小调整限制

  2. 提供自适应布局精确布局

指定额外的微件大小调整限制

Android 12 添加了一些新的 API,可让您确保微件在具有不同屏幕尺寸的不同设备上更可靠地调整大小。

除了现有的 minWidthminHeightminResizeWidthminResizeHeight 属性之外,还可以使用下面这些新的 appwidget-provider 属性:

以下 XML 说明了如何使用调整大小属性。

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

提供自适应布局

如果布局需要根据微件的大小进行更改,我们建议您创建一小组布局,每个布局对一定范围的大小有效。(如果无法做到这一点,另一种方法是根据运行时的精确微件大小来提供布局。)

实现此功能可使缩放更流畅并让系统的整体运行状况更好;这是因为,系统不必每次以不同的大小显示微件时都唤醒应用。

以下代码示例展示了如何提供布局列表。

Kotlin

override fun onUpdate(...) {
  val smallView = ...
  val tallView = ...
  val wideView = ...

  val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(100f, 100f) to smallView,
    SizeF(100f, 200f) to tallView,
    SizeF(200f, 100f) to wideView
  )
  val remoteViews = RemoteViews(viewMapping)

  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
  RemoteViews smallView = ...;
  RemoteViews tallView = ...;
  RemoteViews wideView = ...;

  Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
  viewMapping.put(new SizeF(100f, 100f), smallView);
  viewMapping.put(new SizeF(100f, 200f), tallView);
  viewMapping.put(new SizeF(200f, 100f), wideView);
  RemoteViews remoteViews = new RemoteViews(viewMapping);

  appWidgetManager.updateAppWidget(id, remoteViews);
}

提供精确布局

如果一小组自适应布局不可行,您可以改为提供根据微件的显示大小量身定制的不同布局。通常,手机有两种大小(竖屏和横屏模式),可折叠设备有四种大小。

如需实现此解决方案,您的应用需要执行以下步骤:

  1. 过载 AppWidgetProvider#onAppWidgetOptionsChanged(...),当一组大小发生更改时,就会调用此方法。

  2. 调用 getAppWidgetManager#getAppWidgetOptions(...),这样会返回包含大小的 Bundle

  3. 访问 Bundle 中的 AppWidgetManager.OPTION_APPWIDGET_SIZES 键。

以下代码示例展示了如何提供精确布局。

Kotlin

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

override fun onAppWidgetOptionsChanged(
  context: Context,
  manager: AppWidgetManager,
  id: Int,
  newOptions: Bundle?
) {
  super.onAppWidgetOptionsChanged(context, manager, id, newOptions)
  // Get the new sizes.
  val sizes = newOptions?.getParcelableArrayList<SizeF>(
    AppWidgetManager.OPTION_APPWIDGET_SIZES
  )
  // Check that the list of sizes is provided by the launcher.
  if (sizes.isNullOrEmpty()) {
    return
  }
  // Map the sizes to the desired RemoteViews
  val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager
  appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
      newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the desired RemoteViews.
    Map<SizesF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
      viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

微件布局大小的向后兼容性

以前,可以使用 OPTION_APPWIDGET_MIN_WIDTHOPTION_APPWIDGET_MIN_HEIGHTOPTION_APPWIDGET_MAX_WIDTHOPTION_APPWIDGET_MAX_HEIGHT extra 获取微件的大小范围,并估计微件的大小,但该逻辑并非在所有情况下都适用。对于以 Android 12 为目标平台的微件,我们建议您改为提供自适应布局精确布局,如前所述。

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

Android 12 可让您通过添加动态微件预览和微件说明来改进应用的微件选择器体验。

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

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

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

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

理想情况下,此布局应与具有真实默认值或测试值的实际微件相同。

可缩放微件预览的向后兼容性

为使 Android 11 或更低版本中的微件选择器能够显示微件的预览,请继续指定 previewImage 属性。

如果您对微件的外观进行更改,请务必同时更新预览图片。

为微件添加说明

在 Android 12 中,您可以选择性地为微件提供要由微件选择器显示的说明。

替代文本

使用 appwidget-provider 的说明属性为微件提供说明:

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

微件说明的向后兼容性

您的应用可以在以前的 Android 版本中使用 widgetDescription 属性,但它不会显示在微件选择器中。

实现更流畅的过渡

在 Android 12 中,当用户从微件启动您的应用时,启动器会提供更流畅的过渡。

为了实现这种改进的过渡,请使用 @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 添加了 setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) 方法,可让您的应用在填充 ListView 时直接传递集合。以前,在使用 ListView 时,必须实现并声明 RemoteViewsService 以返回 RemoteViewsFactory

如果集合未使用一组恒定的布局(换句话说,如果某些项只是有时存在),请使用 setViewTypeCount 指定集合可以包含的独特布局的数量上限。

以下示例说明了如何实现简化的 RemoteViews 集合。

Kotlin

remoteView.setRemoteAdapter(
  R.id.list_view,
  RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, RemoteViews(...))
    .addItem(/* id= */ ID_2, RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
)

Java

remoteView.setRemoteAdapter(
  R.id.list_view,
  new RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, new RemoteViews(...))
    .addItem(/* id= */ ID_2, new RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
);

使用 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);