很长一段时间来,微件 (widget) 一直是 Android 用户体验的核心组成部分,很多应用通过微件来提升用户黏度。用户乐于使用微件的原因是可以在不打开应用的情况下使用应用功能,且可自定义设备的主屏幕。
Android 12 更新了已有的 Widget API,重塑了微件的设计来契合 "Material You" 设计语言。这些更新可以帮助您使用设备的主题颜色和圆角来构建更加美观的微件,从而提升微件在搜索和摆放时的可发现性和视觉观感。
在这个系列中,我们将带您更新微件来适配 Android 12。在本文中我们将进行一些简单的修改,使您的微件能够在 Android 12 的设备上看起来更加精致,且在较旧版本的设备中提供一致的用户体验。在第二篇文章中,我们将了解新的 API,通过它们可以使微件更加个性化、响应更灵敏并且更具互动性。
视觉变化
对于用户来说,毫无疑问最直观的视觉变化是风格和设计上的改变。更新可视元素,比如颜色和圆角,呈现出的外观会令用户耳目一新。增加这些修改,我们推荐您创建一个自定义的主题。
增加动态颜色
Material You 旨在提供更加个性化的用户体验。在 Android 12 中,动态颜色可以使您的微件与其它微件以及系统保持一致的风格。微件可以使用系统默认的主题 Theme.DeviceDefault.DayNight,并且在微件的 UI 元素中使用主题颜色属性。
对于 SDK 级别低于 31 的设备,您需要创建一个继承自 DeviceDefault 的自定义主题。
- values/themes.xml
- <style name="Theme.AppWidget.AppWidgetContainer"
- parent="@android:style/Theme.DeviceDefault" />
对于 SDK 级别为 31 的设备,使用主题 DeviceDefault.DayNight 来创建自定义主题。
- values-v31/themes.xml
- <style name="Theme.AppWidget.AppWidgetContainer"
- parent="@android:style/Theme.DeviceDefault.DayNight" />
或者,如果您的应用使用了 Material Components,您可以使用 Theme.MaterialComponents.DayNight 作为基础主题,而不是使用 Theme.DeviceDefault。
- layout/widget_checkbox_list_title_region.xml
- ...
- <TextView android:id="@+id/checkbox_list_title"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="8dp"
- android:layout_weight="1"
- android:text="@string/grocery_list"
- android:textColor="?android:attr/textColorPrimary" />
- <ImageButton
- android:layout_width="@dimen/widget_element_min_length"
- android:layout_height="@dimen/widget_element_min_length"
- android:background="?android:attr/selectableItemBackground"
- android:clickable="true"
- android:contentDescription="@string/add_button_grocery_list_content_description"
- android:src="@drawable/ic_add_24"
- android:tint="?android:attr/colorAccent" />
- ...
圆角
从 Android 12 开始,圆角将自动应用于微件。这也意味着圆角会裁剪微件的部分内容。为了避免出现这样的问题,并且提供与其它微件和系统风格一致的外观和用户体验,您可以使用 system_app_widget_background_radius 在微件的背景添加圆角,使用 system_app_widget_inner_radius 在微件中的视图添加圆角。后者的值需要比 system_app_widget_background_radius 小 8dp。
在添加上述修改时,请注意如果您的微件包含靠近角区域的内容,这些内容可能会被裁减掉。要解决该问题,您需要添加足够大的 padding 来避免微件的内容与圆角之间的冲突。
- values/attrs.xml
- <declare-styleable name="AppWidgetAttrs">
- <attr name="appWidgetPadding" format="dimension" />
- <attr name="appWidgetInnerRadius" format="dimension" />
- <attr name="appWidgetRadius" format="dimension" />
- </declare-styleable>
- values/themes.xml
- <style name="Theme.AppWidget.AppWidgetContainerParent"
- parent="@android:style/Theme.DeviceDefault">
- <!-- 微件的外轮廓的圆角半径 -->
- <item name="appWidgetRadius">16dp</item>
- <!-- widget 内部视图边缘的圆角半径。它的值为 8 dp 或者小于 appWidgetRadius -->
- <item name="appWidgetInnerRadius">8dp</item>
- </style>
- <style name="Theme.AppWidget.AppWidgetContainer"
- parent="Theme.AppWidget.AppWidgetContainerParent">
- <!-- 增加 padding 来避免微件的内容与圆角冲突 -->
- <item name="appWidgetPadding">16dp</item>
- </style>
- values-v31/themes.xml
- <style name="Theme.AppWidget.AppWidgetContainerParent"
- parent="@android:style/Theme.DeviceDefault.DayNight">
- <item name="appWidgetRadius">
- @android:dimen/system_app_widget_background_radius</item>
- <item name="appWidgetInnerRadius">
- @android:dimen/system_app_widget_inner_radius</item>
- </style>
- values/styles.xml
- <style name="Widget.AppWidget.AppWidget.Container"
- parent="android:Widget">
- <item name="android:id">@android:id/background</item>
- <item name="android:background">
- ?android:attr/colorBackground</item>
- </style>
如果您的 minTargetSDK 小于 21,那么您需要提供适用于 SDK 版本 21 的 style,因为在可绘制对象上使用 android:attr/colorBackground 需要 SDK 版本至少为 21。
至此您已经创建了主题,现在可以在微件的布局上设置样式了。
- layout/widget_grocery_list.xml
- <LinearLayout
- style="@style/Widget.AppWidget.AppWidget.Container">
- ...
- </LinearLayout>
过渡
当应用通过微件打开时,Android 12 提供了过渡效果。该过渡效果是由系统自动处理的,并且在旧版本的 Android 上不会出现。要启用该效果,您需要在微件布局根元素上指定一个 id,并设置它的值为 android:id/background。
...
如果您的微件使用了 broadcast trampoline,也就是说您的微件在用户点击时创建了 PendingIntent,通过广播或者服务启动 Activity,那么在这种情况下,该过渡动画不会生效。
微件选择器的优化
优化
预览Android 12 包含新的经过改进的微件选择器。与使用静态可绘制资源不同,新的微件选择器使用 XML 布局来动态创建缩放的微件预览。
如果您的微件并不包含动态元素,比如 ListView 或者 GridView,您可以使用微件的布局实现预览。
要实现预览,您需要将默认值直接设置到原始布局上。
- <TextView
- style="@style/Widget.AppWidget.Checkbox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/widget_title_preview" />
- <TextView
- style="@style/Widget.AppWidget.Checkbox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/widget_subject_preview" />
在布局上设置默认值可能会带来少量的延迟,因为占位的值会在实际值之前首先被启用。要避免该问题,您可以为预览创建一个独立的布局文件,并且启用自定义的预览主题。
- <resources>
- <!-- 声明属性-->
- <attr name="widgetTitlePreview" format="string" />
- <attr name="widgetSubjectPreview" format="string" />
- <!-- 声明 style -->
- <style name="Theme.MyApp.Widget"
- parent="@style/Theme.DeviceDefault.DayNight.AppWidget">
- <item name="widgetTitlePreview"></item>
- <item name="widgetSubjectPreview"></item>
- </style>
- <style name="Theme.MyApp.Widget.Preview">
- <item name="widgetTitlePreview">Preview Title</item>
- <item name="widgetSubjectPreview">Preview Subject</item>
- </style>
- </resources>
创建预览主题后,您可以在布局中将它应用到预览元素上。
- layout/my_widget_preview.xml
- <LinearLayout ...>
- <include layout="@layout/widget_header"
- android:theme=”@style/Theme.MyApp.Widget.Preview” /></LinearLayout>
- layout/my_widget_actual.xml
- <LinearLayout ...>
- <include layout="@layout/widget_header"
- android:theme=”@style/Theme.MyApp.Widget” />
- </LinearLayout>
最后,您需要将微件的布局设置为 appwidget-provider 的 previewLayout 属性。
- xml/app_widget_info_checkbox_list.xml
- <appwidget-provider
- android:previewLayout="@layout/widget_grocery_list"
- ...
- />
对于显示多个元素的 ListView、GridView 或者 Stack,是无法直接在布局上设置默认值的。对于这些视图,您可以为微件预览创建另一个布局,并且在布局中设置固定的值。
要实现上述操作,推荐的最佳实践是使用 标签来复用布局的一部分以启用默认值,而无需复制整个布局。您可以将新的布局设置为 appwidget-provider 的 previewLayout 属性。
描述
您也可以设置 description 属性作为描述信息显示在微件选择器上。虽然这是可选项,但是提供描述信息可以帮助用户更好地了解微件的功能。
- app_widget_info_checkbox_list.xml
- <appwidget-provider
- android:description="@string/app_widget_grocery_list_description"
- ...
- />
总结
在本文中,我们为您展示了如何更新微件设计并且在微件选择器中提供更好的用户体验。上述内容可以快速更新您的微件来适配 Android 12,您的用户可以看到非常直观的区别。
但这并不是全部。在下一篇文章中,我们将会了解新的 API,它可以使您的微件更加个性化,响应更灵敏且更具互动性。