本页介绍了 Android 12(API 级别 31)中引入的 widget 大小调整优化和更大的灵活性。其中还详细介绍了如何确定微件的尺寸。
使用改进的 API 设置微件大小和布局
从 Android 12(API 级别 31)开始,您可以通过执行以下操作(如以下部分所述)提供更精细的尺寸属性和灵活的布局:
在较低版本的 Android 中,可以使用 OPTION_APPWIDGET_MIN_WIDTH
、OPTION_APPWIDGET_MIN_HEIGHT
、OPTION_APPWIDGET_MAX_WIDTH
和 OPTION_APPWIDGET_MAX_HEIGHT
extra 获取微件的大小范围,然后估算微件的大小,但该逻辑并非在所有情况下都适用。对于以 Android 12 或更高版本为目标平台的微件,我们建议您提供自适应布局或精确布局。
指定额外的微件大小调整限制
Android 12 添加了一些 API,可让您确保微件在具有不同屏幕尺寸的不同设备上更可靠地调整大小。
除了现有的 minWidth
、minHeight
、minResizeWidth
和 minResizeHeight
属性之外,还可以使用下面这些新的 appwidget-provider
属性:
targetCellWidth
和targetCellHeight
:根据启动器网格单元定义微件的目标大小。定义后,会使用这些属性,而不是minWidth
或minHeight
。maxResizeWidth
和maxResizeHeight
:定义启动器允许用户调整微件大小的大小上限。
以下 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(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
) 的尺寸。
您的 widget 必须支持 minResizeWidth
× minResizeHeight
到 maxResizeWidth
× maxResizeHeight
的尺寸范围。在此范围内,您可以决定切换布局的截止点。
提供精确布局
如果一小组自适应布局不可行,您可以改为提供根据微件的显示大小量身定制的不同布局。通常,手机有两种大小(竖屏和横屏模式),可折叠设备有四种大小。
如需实现此解决方案,您的应用需要执行以下步骤:
过载
AppWidgetProvider.onAppWidgetOptionsChanged()
,当一组大小发生更改时,就会调用此方法。调用
AppWidgetManager.getAppWidgetOptions()
,这样会返回包含大小的Bundle
。访问
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) { }
确定微件的尺寸
每个微件都必须为搭载 Android 12 或更高版本的设备定义 targetCellWidth
和 targetCellHeight
,或为所有 Android 版本定义 minWidth
和 minHeight
,以指明其默认占用的最小空间量。不过,当用户向其主屏幕添加微件时,微件占用的宽度和高度通常会超过您指定的最小值。
Android 主屏幕为用户提供了一个可用空间网格,供他们放置微件和图标。此网格可能因设备而异;例如,许多手机都提供 5x4 网格,而平板电脑则可提供更大的网格。添加微件后,它将在水平和垂直方向进行拉伸,占用满足其 targetCellWidth
和 targetCellHeight
约束条件所需的最小单元格数(在搭载 Android 12 或更高版本的设备上),或满足其 minWidth
和 minHeight
约束条件所需的最小单元格数(在搭载 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 m | (73n - 16) x (118m - 16) | (142n - 15) x (66m - 15) |
使用纵向模式单元格大小来确定您为 minWidth
、minResizeWidth
和 maxResizeWidth
属性提供的值。同样,请使用横屏模式单元格大小来确定您为 minHeight
、minResizeHeight
和 maxResizeHeight
属性提供的值。
原因在于,单元格的宽度通常在竖屏模式下比横屏模式下小;同样,单元格的高度通常在横屏模式下比竖屏模式下小。
例如,如果您希望在 Google Pixel 4 上将微件宽度调整为最小 1 个单元格,则需要将 minResizeWidth
设置为最多 56dp,以确保 minResizeWidth
属性的值小于 57dp,因为单元格的纵向宽度至少为 57dp。同样,如果您希望在同一设备上的单个单元格中调整微件的高度,则需要将 minResizeHeight
设置为最多 50dp,以确保 minResizeHeight
属性的值小于 51dp,因为单个单元格在横向模式下的高度至少为 51dp。
每个微件都可以在 minResizeWidth
/minResizeHeight
和 maxResizeWidth
/maxResizeHeight
属性之间的尺寸范围内调整大小,这意味着它需要适应这两个属性之间的任何尺寸范围。
例如,如需设置 widget 在放置时的默认大小,您可以设置以下属性:
<appwidget-provider
android:targetCellWidth="3"
android:targetCellHeight="2"
android:minWidth="180dp"
android:minHeight="110dp">
</appwidget-provider>
这意味着,widget 的默认大小为 3x2 个单元格(由 targetCellWidth
和 targetCellHeight
属性指定);对于搭载 Android 11 或更低版本的设备,则为 180×110dp(由 minWidth
和 minHeight
指定)。在后一种情况下,单元格中的大小可能会因设备而异。
此外,如需设置 widget 支持的尺寸范围,您可以设置以下属性:
<appwidget-provider
android:minResizeWidth="180dp"
android:minResizeHeight="110dp"
android:maxResizeWidth="530dp"
android:maxResizeHeight="450dp">
</appwidget-provider>
如前面的属性所指定,该 widget 的宽度可从 180dp 调整到 530dp,其高度可从 110dp 调整到 450dp。然后,只要满足以下条件,该微件就可以从 3x2 单元格调整为 5x2 单元格:
- 设备采用 5x4 网格。
- 单元格数量与可用大小(以 dps 为单位)之间的映射遵循此页面中显示最小尺寸估算值的表格。
- 该微件会根据该尺寸范围进行调整。
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,R.layout.widget_weather_forecast_large
适用于 270x280dp 到 530dp (maxResizeWidth
) x 450dp (maxResizeHeight
)。
当用户调整 widget 的大小时,其外观会发生变化,以适应单元格中的每个大小,如以下示例所示。