本文翻译自developer.android.com/about/versi…
Android 12通过改进现有widget API,来优化用户和开发人员的体验。本文介绍Android 12中widget的新特性并介绍应用如何才可以兼容Android 12。
确保你的widget与Android 12兼容
Android 12中所有的widget都是统一显示成圆角的。所以任何App在Android 12上运行时,如果使用其widget Launcher会默认将其背景裁剪成圆角。
以下场景,widget可能就会出现显示不全或者异常:
- widget角落中存在显示内容,这可能会被系统裁剪掉,导致显示不全
- widget背景无法裁剪,如果使用了透明背景,空的视图或者布局,或任何不容易裁剪的特殊背景。系统可能无法正确识别要使用的背景。
为了让App的widget正常显示,首先我们需要适配圆角。
警告: 圆角的尺寸可能会因设备而异,因为设备制造商(最大16dp)和第三方发射器都可以控制角半径的大小。我们建议刷新窗口小部件,以帮助避免产生不满意的结果。
示例代码
关于新特性的示例代码 sample list widget。
适配圆角
Android 12加入了系统参数来设置widget的圆角半径:
- system_app_widget_background_radius: widget背景的圆角半径,默认不会大于28dp。
- system_app_widget_inner_radius:widget内部view的圆角半径,这个默认比背景圆角小8dp来进行对齐
- widget的圆角。
- widget的内部视图圆角。
警告: 圆角的尺寸可能会因设备而异,因为设备制造商(最大16dp)和第三方Launcher都可以控制角半径的大小。
向后兼容
为了不影响Android 12之前的版本,并且向后兼容后续Android版本,请将圆角的资源配置放在values-31的目录中
动态颜色适配
在Android 12中,widget可以使用当前设备的主题色来设置button、背景或者其他组件。这样有利于在不同界面间切换实现平滑过渡。
在以下示例中,设备主题颜色为“棕褐色”,从而使配色和小部件背景适应。您可以使用系统的默认主题(@android:style/Theme.DeviceDefault.DayNight)及其颜色属性来实现。一些常用的颜色属性是:
- ?android:attr/colorAccent
- ?android:attr/colorBackground
- ?android:attr/textColorPrimary 和 ?android:attr/textColorSecondary
亮色主题
暗色主题
向后兼容
同上适配圆角,为了前后兼容,请将Android 12中的主题资源配置改动,统一放在values-31中。
简化定制widget流程
如果指定一个特殊配置Activity,并配置appwidget-proivder中,当用户添加此widget之后,App Widget会立刻启动这个Activity。
增加重新配置选项
用户长按widget之后,会出现一个重新配置的按钮,如果点击,可以进行更改配置。
- 重新配置按钮
如果需要支持此功能,需要在appwidget-provider的widgetFeatures属性中声明reconfigurable:
<appwidget-provider
...
android:configure="com.myapp.WidgetConfigActivity"
android:widgetFeatures="reconfigurable">
</appwidget-provider>
复制代码
注意:该reconfigurable标志是在Android 9(API级别28)中引入的,但直到Android 12才在启动器中得到广泛支持。
使用默认配置
使用默认配置如果希望widget使用默认配置,那么只需要在widgetFeatures声明configuration_optional和reconfigurable标志来跳过配置步骤。
例如时钟widget可以绕过初始配置并默认显示设备时区。
<appwidget-provider
...
android:configure="com.myapp.WidgetConfigActivity"
android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>
复制代码
向后兼容
应用可以在早期版本的Android中使用configuration_optional和reconfigurable标志。但是,这些标志不会有任何效果,并且系统仍会启动配置Activity。
新的控件支持
Android 12使用以下现有组件添加了对状态行为的新支持:
- CheckBox
- Switch
- RadioButton
实现方式:
// 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 12以前版本的设备(在默认res/layout文件夹中)。
使用API来调整widget大小和布局
从Android 12开始,可以执行以下操作,实现更精细的尺寸属性和更灵活的布局:
- 指定其窗口大小限制
- 提供响应式布局或者精确布局
指定widget大小限制
Android 12添加了新的API,可以确保在屏幕尺寸不同的不同设备上更可靠地调整窗口小部件的尺寸。
除了现有的minWidth, minHeight, minResizeWidth,和minResizeHeight 属性,使用下面的新appwidget-provider属性:
- targetCellWidth 和targetCellHeight:根据Launcher网格单元定义小部件的目标大小。如果定义,则使用这些属性代替minWidth或minHeight。
- maxResizeWidth 和maxResizeHeight:定义Launcher允许用户调整窗口小部件大小的最大大小。
以下XML描述了如何使用大小设置属性:
<appwidget-provider
...
android:targetCellWidth="3"
android:targetCellHeight="2"
android:maxResizeWidth="250dp"
android:maxResizeHeight="110dp">
</appwidget-provider>
复制代码
提供响应式布局
如果widget的布局需要随其大小变化而变化,建议创建不同尺寸的布局。(如果不可行的话,另外一种适配就是在运行时根据widget的具体大小提供精确布局)。
实现此功能可以更好的兼容系统,系统没有必要在每次因为不同大小显示的widget唤醒App。
下面是示例代码如何提供一个布局列表:
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)
}
复制代码
提供精确布局
如果小部分的响应式布局不可用,我们可以改成与widget大小适配的布局,也就是精确布局。通常在普通手机上有两种尺寸(横竖屏),在折叠屏上有四种布局。
为了实现此方案,App需要执行以下步骤:
- 重载 AppWidgetProvider#onAppWidgetOptionsChanged(…), 此方法会在尺寸变化的时候调用
- 调用 getAppWidgetManager#getAppWidgetOptions(…),此方法会返回一个包含尺寸的Bundle
- 从Bundle中获取 AppWidgetManager.OPTION_APPWIDGET_SIZES
注意:Launcher提供尺寸列表,如果当前Launcher不支持此字段,返回值可能是null
下面的代码示例演示如何提供精确的布局:
// 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)
}
复制代码
向后兼容
在此之前,App可以通过 OPTION_APPWIDGET_MIN_WIDTH,OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH,和 OPTION_APPWIDGET_MAX_HEIGHT获取widget的尺寸,也可以通过估算得到,但是此逻辑并不能覆盖所有的场景。基于Android 12,建议使用响应式或者精确式布局。
改善App Widget选择器体验
Android 12可以通过添加动态widget预览和描述来改善App Widget选择器体验。
添加widget预览到选择器中
在Android 12上,widget可以提供一个可缩放的预览在选择器中,而原本的XML只给widget提供大小。在此之前,widget的预览是静态的图片资源,在某些情况下,可能存在预览和实际放在桌面上的效果不一样。
要实现这种缩放的预览,需要在appwidget-provider中使用previewLayout属性:
<appwidget-provider
...
android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>
复制代码
理想情况下,预览布局与实际widget的布局是一模一样的。
向后兼容
如果在Anroid 12之前的版本上,使用预览,请继续设置previewImage。另外如果修改了widget的外观,请同时更新预览。
为widget增加描述
在Android 12中,可以为widget提供说明。
在appwidget-provider中使用description即可为widget提供描述:
<appwidget-provider
...
android:description="@string/my_widget_description">
</appwidget-provider>
复制代码
注意:此处没有字符限制,但是由于设备的空间影响,描述尽量简洁明了。
向后兼容
在Android 12之前的版本,App可以使用widgetDescription,但是这个并不会在选择器中显示。
使用更平滑的过渡
在Android 12中,当用户从widget启动app时,Launcher可提供更平滑的过渡。
要启用此改进的过渡,请使用@android:id/background或 android.R.id.background标识您的背景元素:
// Top level layout of the widget.
<LinearLayout
...
android:id="@android:id/background">
</LinearLayout>
复制代码
警告:为了避免广播崩溃。Android 12中,应用依然通过广播或者服务启动,如果它通过widget的点击使用PendingIntent启动的。但是,新的动画效果并不会使用在广播或者服务启动,因为用户体验很差
向后兼容
App可以使用@android:id/background在以往的版本中,但是没有任何效果。
使用简化的RemoteViews集合
Android 12提供了 setRemoteAdapter(int viewId,RemoteViews.RemoteCollectionItems items) 方法,这个可以让app填充ListView的时候直接通过结合就可以实现。在以前,当使用ListView,我们需要先实现定义一个RemoteViewsService,来获取RemoteViewsFactory。
如果集合中存在多个类型的布局,我们可以使用setViewTypeCount指定集合可以包含最大唯一布局数。
这里是示例如何实现简化RemoteViews集合:
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()
)
复制代码
在运行时修改RemoteViews
Android 12中增加了几种在运行时修改RemotesViews属性的方法。详细的方法可以查阅RemoteViews API。
下面是示例新增的几个方法:
// 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)
复制代码