提供灵活的 widget 布局

本页面介绍了关于微件大小调整和更高灵活性的优化措施 在 Android 12(API 级别 31)中引入。还详细介绍了 确定 widget 的大小

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

从 Android 12(API 级别 31)开始,您可以提供更精细的尺寸 属性和灵活布局,具体如 以下部分:

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

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

在以前的 Android 版本中,可以获取 使用 OPTION_APPWIDGET_MIN_WIDTHOPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, 和OPTION_APPWIDGET_MAX_HEIGHT extra,然后估算 widget 的大小,但该逻辑并非适用于所有模式 情况。对于以 Android 12 或更高版本为目标平台的微件,我们建议 提供自适应完全匹配 布局

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

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

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

以下 XML 展示了如何使用大小调整属性。

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

提供自适应布局

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

此功能可实现更顺畅的扩缩,并提升整体的系统性能 运行状况,因为系统不必每次都唤醒应用 它会以不同的尺寸显示该 widget。

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

Kotlin

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

    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)
}

Java

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

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

    appWidgetManager.updateAppWidget(id, remoteViews);
}

假设该 widget 具有以下属性:

<appwidget-provider
    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 键。

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

Kotlin

override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        id: Int,
        newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, 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 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 { }

Java

@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 RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

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

确定 widget 的大小

每个 widget 都必须为设备定义 targetCellWidthtargetCellHeight 搭载 Android 12 或更高版本,或者对于所有搭载 Android 12 或更高版本的用户,使用 minWidthminHeight Android 版本 - 指示占用的最小空间量 默认情况。不过,当用户向主屏幕添加微件时,通常 占用的宽度和高度大于您指定的最小宽度和高度。

Android 主屏幕为用户提供了可用空间网格,供他们在其中 放置微件和图标。此网格可能会因设备而异;例如,许多 手机采用 5x4 网格,平板电脑则提供更大的网格。当微件 被拉伸以占据最小数量的单元格 水平和垂直方向上,需要满足其 targetCellWidthtargetCellHeight(在运行的设备上) Android 12 或更高版本,或者 minWidthminHeight 限制条件 搭载 Android 11(API 级别 30)或更低版本的设备。

单元格的宽度和高度以及自动应用外边距的大小 也会因设备而异使用下表大致估算出 在典型的 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 米 (73 米 - 16)x(118 米 - 16) (142 米 - 15)x(66 米 - 15)

使用纵向模式的单元格大小来说明您提供的值 minWidthminResizeWidthmaxResizeWidth 属性。同样, 使用横屏模式的单元格大小来决定您提供的值 minHeightminResizeHeightmaxResizeHeight 属性。

原因在于,在纵向模式下,单元格的宽度通常较小 与横向模式相比,采用横向模式时,单元格高度通常为 相较于纵向模式,尺寸小

例如,如果您希望将微件的宽度调整到 如果是 Google Pixel 4,则需要将 minResizeWidth 设置为不超过 56dp 以确保 minResizeWidth 属性的值较小 大于 57dp,因为纵向模式下单元格的宽度至少为 57dp。 同样,如果您希望微件高度可在 您需要将 minResizeHeight 设置为不超过 50dp,以确保 minResizeHeight 属性的值小于 51dp - 因为一个单元格在横屏模式下的高度至少为 51dp。

每个 widget 均可在 minResizeWidth/minResizeHeightmaxResizeWidth/maxResizeHeight 属性,这意味着它需要适应这些对象之间的任何尺寸范围。

例如,要在展示位置上设置微件的默认尺寸,您可以 设置以下属性:

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

这意味着该微件的默认大小为 3x2 单元格, targetCellWidthtargetCellHeight 属性,即 180×110dp,例如 由 minWidthminHeight 指定(针对运行 Android 11 或更低版本。在后一种情况下,单元格中的大小 因设备而异。

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

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

正如以上属性所指定,微件的宽度为 大小可调整为 180dp 至 530dp,高度可调整为 110dp 至 450dp。 然后,可以调整该微件的大小,使其介于 3x2 到 5x2 的单元格之间,但需满足以下条件: 条件:

Kotlin

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))

Java

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

假设该 widget 使用前面定义的响应式布局 代码段。也就是说, 使用 R.layout.widget_weather_forecast_small 时,最低为 180dp (minResizeWidth) x 110dp (minResizeHeight) 到 269x279dp(下一个临界点 - 1)。同样, R.layout.widget_weather_forecast_medium 的使用范围为 270x110dp 到 270x279dp, 以及从 270x280dp 到 R.layout.widget_weather_forecast_large 530dp (maxResizeWidth) x 450dp (maxResizeHeight)。

当用户调整微件的大小时,其外观会随之改变以适应 单元格,如以下示例所示。

最小 3x2 网格大小的天气 widget 示例。界面会显示
            位置名称(东京)、温度 (14°) 以及指示符
            局部多云天气。
图 2. 3x2R.layout.widget_weather_forecast_small

4x2“medium”中的天气 widget 示例。调整 widget 大小
            这种方法基于之前的 widget 大小的所有界面构建,
            并添加了“以多云为主”标签以及从
            下午 4 点至晚上 7 点。
图 3. 4x2R.layout.widget_weather_forecast_medium

<ph type="x-smartling-placeholder">
</ph> 5x2“medium”中的天气 widget 示例。调整 widget 大小
            这样生成的界面与之前的尺寸相同
            将单元格拉伸一个单元格,以占据更多水平空间。
图 4.5x2R.layout.widget_weather_forecast_medium

5x3“大”尺寸的天气 widget 示例。调整 widget 大小
            这种方法基于之前微件大小的所有界面构建,
            并在该 widget 内添加一个包含天气预报的视图
            。表示晴天或雨天的符号
            以及每日最高和最低温度
图 5. 5x3 R.layout.widget_weather_forecast_large

5x4“大”尺寸的天气 widget 示例。调整 widget 大小
            这种方法基于之前微件大小的所有界面构建,
            并添加星期四和星期五(以及相应的符号
            用于表示天气类型以及最高温度和最低温度
            )。
图 6. 5x4R.layout.widget_weather_forecast_large