Android 12上焕然一新的小组件:美观、便捷和实用(2)

简介: Android 12上焕然一新的小组件:美观、便捷和实用(2)

6. 便捷地配置尺寸

12针对小组件的尺寸配置环节也进行了改进,更加便捷。

6.1 精确的尺寸

在已有的minWidth、minResizeWidth等属性以外,新增了几个属性以更便捷地配置小组件的尺寸。


targetCellWidth和targetCellHeight:占据Launcher上Cell的宽高格数,用以替代minWidth和minHeight。事实上Launcher是以Cell的单位来展示小组件的,所以直接指定Cell数显然更合理

maxResizeWidth和maxResizeHeight: 配置Launcher上允许配置的最大尺寸,弥补minResizeWidth和minResizeHeight的不足

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

1832b220aa754cd18c504acc7686a560.png

6.2 灵活调节尺寸

iOS上添加小组件后尺寸就固定了,不支持调节。而Android 12上小组件在长按后即可灵活调节。

1832b220aa754cd18c504acc7686a560.png

想要支持这个特性只需要给widgetFeatures属性指定reconfigurable值即可。

<appwidget-provider
    android:widgetFeatures="reconfigurable">
</appwidget-provider>

The reconfigurable flag was introduced in Android 9 (API level 28), but it was not widely supported in launchers until Android 12.


事实上这个属性早在Android 9的时候就引入了,但官方说从S开始才全面支持。我在11版本的Pixel Launcher上发现已经可以直接调节尺寸了,不知道官方的意思是不是别的Launcher并不支持。

6.3 采用默认配置

configure属性可以在小组件展示之前启动一个配置画面,供用户选择小组件所需的内容、主题和风格等。

如果想让用户快速看到效果,即不想展示这个画面的话,只要在widgetFeatures里指定新的configuration_optional值即可。

<appwidget-provider
  ...
  android:configure="com.example.appwidget.activity.WidgetConfigureActivity"
  android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

后面改主意了又想替换配置的话,可以长按小组件找到配置的入口。

一是小组件右下方的编辑按钮,二是上方出现的Setup菜单,这在以前的版本上是没有的。

image.gif

7. 高效地控制布局

小组件内容较多的时候,为了展示的完整往往会给它限定Size,这意味着只有Launcher空间足够大小组件才能成功放置。当Launcher空间捉急的时候就尴尬了,用户只能在移除别的小组件和放弃你的小组件之间做个抉择。


免除这种困扰的最佳做法是在不同的Size下采用不同的布局,对展示的内容做出取舍。即Size充足的情况下提供更多丰富的内容,反之只呈现最基本、最常用的信息。

7.1 响应式布局

之前是如何做到这一需求呢?除了预设各种尺寸的小组件的一般思路以外,通过onAppWidgetOptionsChanged回调也可以控制布局的变化,但往往非常繁琐。


而12上借助新增的RemoteViews(Map<SizeF, RemoteViews> map)API可以大大简化实现过程。在小组件放置的时候就将Size和布局的映射关系告知系统,当Size变化了AppWidgetManager将自动响应更新对应的布局。


比如待办事项小组件在Size为3x2的时候额外展示添加按钮,2x2的时候只展示事项列表的相应式布局。

image.png

代码的实现也简单清晰:

private fun updateAppWidgetWithResponsiveLayouts(...) {
    ...
    // 尺寸够宽的情况下Button才显示
    val wideView = RemoteViews(rv)
    wideView.setViewVisibility(button, View.VISIBLE)
    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(100f, 100f) to rv,
        SizeF(200f, 100f) to wideView
    )
    // 将Size和RemoteViews布局的映射关系告知AppWidgetManager
    val remoteViews = RemoteViews(viewMapping)
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}

好处:

  • 免于同一功能提供一堆尺寸小组件的繁琐,减轻选择器的负担
  • 实现简单,自动响应

7.2 精确布局

如今移动设备的尺寸、形态丰富多样,尤其是折叠屏愈加成熟。如果响应式布局仍不能满足更精细的需求,可以在Size变化的回调里,获取目标Size对布局进一步的精确把控。


利用AppWidgetManager新增的OPTION_APPWIDGET_SIZES KEY可以从AppWidgetManager里拿到目标Size。

// 监听目标尺寸
override fun onAppWidgetOptionsChanged(...) {
    ...
    // Get the new sizes.
    val sizes = newOptions?.getParcelableArrayList<SizeF>(
        AppWidgetManager.OPTION_APPWIDGET_SIZES
    )
    // Do nothing if sizes is not provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    Log.d("Widget", "PedometerAppWidget#onAppWidgetOptionsChanged() size:${sizes}")
    // Get exact layout
    if (BuildCompat.isAtLeastS()) {
        val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
        appWidgetManager?.updateAppWidget(appWidgetId, remoteViews)
    }
}

如下的日志显示Size变化的时候会将目标Size回传。

Widget  : PedometerAppWidget#onAppWidgetOptionsChanged() size:[377.42856x132.0, 214.57143x216.57143]

之后从预设的精细布局里匹配相应的视图。

private fun createRemoteViews(size: SizeF): RemoteViews {
    val smallView: RemoteViews = ...
    val tallView: RemoteViews = ...
    val wideView: RemoteViews = ...
    ...
    return when (size) {
        SizeF(100f, 100f) -> smallView
        SizeF(100f, 200f) -> tallView
        SizeF(200f, 100f) -> wideView
        ...
    }
}

注意:实际上Size列表由Launcher提供,如果3rd Launcher没有适配这一特性的话,回传的Size可能为空

8. 自由地更新视图

RemoteViews作为小组件视图的重要管理类,本次OSV也添加了诸多API,以便更加自由地控制视图的展示。


更改颜色的setColorStateList()

更改边距的setViewLayoutMargin()

更改宽高的setViewLayoutWidth()等

这些新API可以助力我们实很多方便的功能,比如CheckBox选中之后更新文本颜色,思路很简单:


监听小组件的点击事件并传递目标视图

根据CheckBox的状态获得预设的文本颜色

使用setColorStateList()更新

override fun onReceive(context: Context?, intent: Intent?) {
    ...
    // Get target widget.
    val appWidgetManager = AppWidgetManager.getInstance(context)
    val thisAppWidget = ComponentName(context!!.packageName, TodoListAppWidget::class.java.name)
    val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
    // Update widget color parameters dynamically.
    for (appWidgetId in appWidgetIds) {
        val remoteViews = RemoteViews(context.packageName, R.layout.widget_todo_list)
        remoteViews.setColorStateList(
            viewId,
            "setTextColor",
            getColorStateList(context, checked)
        )
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    }
}
private fun getColorStateList(context: Context, checkStatus: Boolean): ColorStateList =
    if (checkStatus) 
        ColorStateList.valueOf(context.getColor(R.color.widget_checked_text_color))
    else 
        ColorStateList.valueOf(context.getColor(R.color.widget_unchecked_text_color))

2Zmh5D.gif

再比如Chart线图太小,看不清楚。可以让它在点击之后放大,再点击之后恢复原样。

// 根据记录的缩放状态获得预设的宽高
// 通过setViewLayoutWidth和setViewLayoutHeight更新宽高
override fun onReceive(context: Context?, intent: Intent?) {
    ...
    val widthScaleSize = if (scaleOutStatus) 200f else 260f
    val heightScaleSize = if (scaleOutStatus) 130f else 160f
    // Update widget layout parameters dynamically.
    for (appWidgetId in appWidgetIds) {
        val remoteViews = RemoteViews(context.packageName, R.layout.widget_pedometer)
        remoteViews.setViewLayoutWidth(viewId, widthScaleSize, TypedValue.COMPLEX_UNIT_DIP)
        remoteViews.setViewLayoutHeight(viewId, heightScaleSize, TypedValue.COMPLEX_UNIT_DIP)
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    }
}

2Zmh5D.gif

9. 流畅的启动效果

12版本上点击Widget启动App的时候可以呈现更流畅的过渡效果,适配也很简单。官方指示只需给小组件的根布局指定android的backgoround id即可。

<LinearLayout
    ...
    android:id="@android:id/background">
</LinearLayout>

实际的动作显示添加这个ID后App启动没有什么变化,个中原因需要继续研究。

12开始对从Broadcast Receiver或Serivce启动Activity做了更严格的限制,但不包括Widget发起的场合。但为了避免视觉上的突兀,这种后台启动的情况下不展示迁移动画。

10. 简化的数据绑定

小组件里展示ListView的需求也很常见,提供数据的话需要声明一个RemoteViewsService 以返回RemoteViewsFactory,比较绕。


而12里新增的setRemoteAdapter(int , RemoteCollectionItems) API则可以大大简化这个绑定过程。


比如制作一个即将到来的事件列表小组件,通过这个API便可以高效注入数据。

private fun updateCountDownList(...) {
    ...
    // 创建用于构建Remote集合数据的Builder
    val builder = RemoteViews.RemoteCollectionItems.Builder()
    val menuResources = context.resources.obtainTypedArray(R.array.count_down_list_titles)
    // 往Builder里添加各Item对应的RemoteViews
    for (index in 0 until menuResources.length()) {
        ...
        builder.addItem(index.toLong(), constructRemoteViews(context, resId))
    }
    // 构建Remote集合数据
    // 并通过setRemoteAdapter直接放入到ListView里
    val collectionItems = builder.setHasStableIds(true).build()
    remoteViews.setRemoteAdapter(R.id.count_down_list, collectionItems)
    ...
}
// 创建ListView各Item对应的RemoteViews
private fun constructRemoteViews(...): RemoteViews {
    val remoteViews = RemoteViews(context.packageName, R.layout.item_count_down)
    val itemData = context.resources.getStringArray(stringArrayId)
    // 遍历Item数据行设置对应的文本
    itemData.forEachIndexed { index, value ->
        val viewId = when (index) {
            0 -> R.id.item_title
            1 -> R.id.item_time
            ...
        }
        remoteViews.setTextViewText(viewId, value)
    }
    return remoteViews
}

1832b220aa754cd18c504acc7686a560.png

如果Item的布局不固定不止一种,可以使用setViewTypeCount指定布局类型的数目,告知ListView需要提供的ViewHolder种类。如果不指定也可以,系统将自动识别布局的种类,需要系统额外处理而已。


但要注意:如果指定的数目和实际的不一致会引发异常。


IllegalArgumentException: View type count is set to 2, but the collection contains 3 different layout ids

另外,需要补充一下,支持该API的View必须是AdapterView的子类,比如常见的ListView、GridView等。RecyclerView是不支持的,毕竟小组件里数据量不多,不能使用也没关系

11. 新增API总结

简要罗列一下12针对小组件新增的API,方便大家查阅。

RemoteViews类

1672151250881.png

XML属性

1672151265739.png

结语

通过上面的解读,大家可以感受到Google在小组件的重新设计上耗费了诸多努力,它给这个老旧的功能注入很多新玩法和新花样。

简要回顾一下Android 12里小组件的新特性:

  • 更便捷的小组件选择器
  • 更美观的圆角边框设计
  • 更灵活的小组件预览
  • 更完整的控件支持
  • 更方便的尺寸调节
  • 更精准的布局控制
  • 更自由的视图更新
  • 更简便的列表数据绑定

如此之多的新特性,在助力小组件高效开发的同时,还能给用户呈现更加优秀的使用体验。

跟随Android 12的脚步,快快尝试起来,让现有的小组件重新绽放光彩。

未决事项

  1. 小组件内部视图的圆角尺寸如何适配?
  2. 小组件启动App的流畅过渡效果如何实现,是什么效果?

本文DEMO

https://github.com/ellisonchan/NewAppWidget

参考文档

官方宣传

官方介绍文档

官方Sample

推荐阅读

Android 12上全新的应用启动画面,还不适配一下?

全面复盘Android开发者容易忽视的Backup功能。

为什么推荐使用CameraX?

相关文章
|
3天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
1月前
|
存储 Android开发 开发者
深入理解安卓应用开发的核心组件
【10月更文挑战第8天】探索Android应用开发的精髓,本文带你了解安卓核心组件的奥秘,包括Activity、Service、BroadcastReceiver和ContentProvider。我们将通过代码示例,揭示这些组件如何协同工作,构建出功能强大且响应迅速的应用程序。无论你是初学者还是资深开发者,这篇文章都将为你提供新的视角和深度知识。
|
1月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
74 0
|
6月前
|
Android开发 算法 架构师
android的基础ui组件,这些知识点你会吗
android的基础ui组件,这些知识点你会吗
android的基础ui组件,这些知识点你会吗
|
6月前
|
Android开发 缓存 双11
android的基础ui组件,Android开发社招面试经验
android的基础ui组件,Android开发社招面试经验
android的基础ui组件,Android开发社招面试经验
|
1月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
1月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
44 6
|
2月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
169 5
|
2月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
3月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!