提供灵活的 widget 布局

试用 Compose 方式
Jetpack Compose 是推荐用于构建 Android 界面的工具包。了解如何使用 Compose 样式的 API 构建微件。

本页介绍了 Android 12(API 级别 31)中引入的微件大小调整改进和更大的灵活性。此外,还详细介绍了如何 确定微件的大小

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

从 Android 12(API 级别 31)开始,您可以提供更精细的大小属性和灵活的布局,具体方法如下(如后续部分所述):

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

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

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

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

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

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

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

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

提供自适应布局

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

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

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

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

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

    appWidgetManager.updateAppWidget(id, remoteViews)
}

假设微件具有以下属性:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

前面的代码段表示以下内容:

  • smallView 支持从 160dp (minResizeWidth) × 110dp (minResizeHeight) 到 160dp × 199dp(下一个分界点 - 1dp)。
  • tallView 支持从 160dp × 200dp 到 214dp(下一个分界点 - 1)× 200dp。
  • wideView 支持从 215dp × 110dp (minResizeHeight) 到 250dp (maxResizeWidth) × 200dp (maxResizeHeight)。

您的微件必须支持从 minResizeWidth × minResizeHeightmaxResizeWidth × maxResizeHeight 的大小范围。在该范围内,您可以决定切换布局的分界点。

自适应布局示例
图 1. 自适应布局示例。

提供精确布局

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

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

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

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

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

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

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

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

确定微件的大小

每个微件都必须为搭载 Android 12 或更高版本的设备定义 targetCellWidthtargetCellHeight,或者为所有 Android 版本定义 minWidthminHeight,以指明其默认占用的最小空间量。不过,当用户向其主屏幕添加微件时,微件占用的宽度和高度通常会超过您指定的最小值。

Android 主屏幕为用户提供了一个可用空间网格,供他们放置微件和图标。此网格可能因设备而异;例如,许多手机提供 5x4 网格,而平板电脑可以提供更大的网格。添加微件后,它会拉伸以占用满足其在搭载 Android 12 或更高版本的设备上的 targetCellWidthtargetCellHeight 约束或在搭载 Android 11(API 级别 30)或更低版本的设备上的 minWidthminHeight 约束所需的最小单元格数(横向和纵向)。

单元格尺寸和自动外边距因设备而异。您可以使用下表根据所需占用的网格单元格数,估算微件在典型的 5x4 网格手机上的最小尺寸:

单元格数(宽 x 高) 竖屏模式下的可用大小 (dp) 横屏模式下的可用大小 (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
... ... ...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

使用竖屏模式单元格大小来确定您为 minWidthminResizeWidthmaxResizeWidth 属性提供的值。同样,使用横屏模式单元格大小来确定您为 minHeightminResizeHeightmaxResizeHeight 属性提供的值。

原因是,单元格宽度在竖屏模式下通常比在横屏模式下小,同样,单元格高度在横屏模式下通常比在竖屏模式下小。

例如,如果您希望微件宽度在 Google Pixel 4 上可调整为 1 个单元格,则需要将 minResizeWidth 设置为最多 56dp,以确保 minResizeWidth 属性的值小于 57dp,因为单元格在竖屏模式下至少为 57dp 宽。 同样,如果您希望微件高度在同一设备上可调整为 1 个单元格,则需要将 minResizeHeight 设置为最多 50dp,以确保 minResizeHeight 属性的值小于 51dp,因为单元格在横屏模式下至少为 51dp 高。

每个微件都可以在 minResizeWidth/minResizeHeightmaxResizeWidth/maxResizeHeight 属性之间的大小范围内调整大小,这意味着它需要适应它们之间的任何大小范围。

例如,如需设置微件在展示位置上的默认大小,您可以设置以下属性:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

这意味着微件的默认大小为 3x2 个单元格(由 targetCellWidthtargetCellHeight 属性指定),或者为 180×110dp(由搭载 Android 11 或更低版本的设备的 minWidthminHeight 指定)。在后一种情况下,单元格大小可能因设备而异。

此外,如需设置微件支持的大小范围,您可以设置以下属性:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

如上述属性所指定,微件的宽度可从 180dp 调整为 530dp,高度可从 110dp 调整为 450dp。 然后,只要满足以下条件,微件就可以从 3x2 个单元格调整为 5x2 个单元格:

  • 设备具有 5x4 网格。
  • 单元格数与 dp 中的可用大小之间的映射 遵循本页中显示最小尺寸估算 的表格
  • 微件适应该大小范围。

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(180f, 110f) to smallView,
    SizeF(270f, 110f) to mediumView,
    SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

假设微件使用前面代码段中定义的自适应布局。这意味着,指定的布局 R.layout.widget_weather_forecast_small 的大小范围为 180dp (minResizeWidth) x 110dp (minResizeHeight) 到 269x279dp(下一个分界点 - 1)。同样,R.layout.widget_weather_forecast_medium 的大小范围为 270x110dp 到 270x279dp,R.layout.widget_weather_forecast_large 的大小范围为 270x280dp 到 530dp (maxResizeWidth) x 450dp (maxResizeHeight)。

当用户调整微件大小时,其外观会发生变化,以适应每个单元格大小,如以下示例所示。

以最小的 3x2 网格尺寸显示的天气 widget 示例。界面会显示位置名称(东京)、温度 (14°) 和表示多云天气的符号。
图 2. 3x2 R.layout.widget_weather_forecast_small

尺寸为 4x2 的“中等”天气 widget 示例。以这种方式调整 widget 大小会基于之前 widget 大小的所有界面,并添加“多云”标签以及下午 4 点到晚上 7 点的温度预报。
图 3. 4x2 R.layout.widget_weather_forecast_medium

5x2“中等”尺寸的天气 widget 示例。以这种方式调整 widget 大小会产生与之前大小相同的界面,只是拉伸了一个单元格长度,以占用更多横向空间。
图 4. 5x2 R.layout.widget_weather_forecast_medium

5x3“大”尺寸的天气 widget 示例。以这种方式调整 widget 大小会基于之前 widget 大小的所有界面,并在 widget 内添加一个包含周二和周三天气预报的视图。指示晴天或雨天的符号,以及每天的最高气温和最低气温。
图 5. 5x3 R.layout.widget_weather_forecast_large

5x4“大”尺寸的天气 widget 示例。以这种方式调整微件大小会沿用之前微件大小的所有界面,并添加周四和周五(以及它们对应的符号,用于指示天气类型以及每天的最高温度和最低温度)。
图 6. 5x4 R.layout.widget_weather_forecast_large