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

android:id=“@+id/checkbox_first”
style=“@style/Widget.AppWidget.Checkbox”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“@string/todo_list_sample_1”
Tools:text=“@string/todo_list_tool” />

12-widget

如果将同样的代码运行到11上,则会显示加载失败。

日志

AppWidgetHostView: Error inflating AppWidget AppWidgetProviderInfo(UserHandle{0}/ComponentInfo{com.example.splash/com.example.splash.widget.TodoListAppWidget}): android.view.InflateException: Binary XML file line #13 in com.example.splash:layout/widget_todo_list: Binary XML file line #13 in com.example.splash:layout/widget_todo_list: Error inflating class android.widget.CheckBox

12以前的小部件不支持展示CheckBox等控件

文本内容不确定的话,可以通过代码动态地控制文本,同时还可以监听用户的选择事件。

比如我们要展示Android开发者如今要学习的三座大山,选中的时候弹出Toast。

private fun updateAppWidget(…) {
val viewId1 = R.id.checkbox_first
val pendingIntent = PendingIntent.getBroadcast(…)

val rv = RemoteViews(context.packageName, R.layout.widget_todo_list)
rv.apply {
// 设置文本
setTextViewText(viewId1, context.resources.getString(R.string.todo_list_android))

// 设置CheckBox的默认选中状态
setCompoundButtonChecked(viewId1, true)

// 监听相应的CheckBox的选中事件
setOnCheckedChangeResponse(
viewId1,
RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent)
)
}
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}

override fun onReceive(context: Context?, intent: Intent?) {

val checked = intent.extras?.getBoolean(RemoteViews.EXTRA_CHECKED, false) ?: false
val viewId = intent.extras?.getInt(EXTRA_VIEW_ID) ?: -1

Toast.makeText(
context,
“ViewId : $viewId’s checked status is now : $checked”,
Toast.LENGTH_SHORT
).show()
}

12-widget

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

CheckBox精准布局预览

6.2 灵活调节尺寸

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

CheckBox长按效果

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


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

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

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

12-widget

7. 高效地控制布局

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

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

7.1 响应式布局

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

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

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

12-widget

代码的实现也简单清晰:

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(
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选中之后更新文本颜色,思路很简单:

  1. 监听小组件的点击事件并传递目标视图
  2. 根据CheckBox的状态获得预设的文本颜色
  3. 使用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))

12-widget

再比如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)
}
}

12-widget

9. 流畅的启动效果

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

<LinearLayout

android:id=“@android:id/background”>

实际的动作显示添加这个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
}

CheckBox精准布局预览

如果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类

方法作用
RemoteViews(Map<SizeF, RemoteViews>)根据响应式布局映射表创建目标RemoteViews
addStableView()向RemoteViews动态添加子View,类似ViewGroup#addView()
setCompoundButtonChecked()针对CheckBox或Switch控件更新选中状态
setRadioGroupChecked()针对RadioButton控件更新选中状态
setRemoteAdapter(int , RemoteCollectionItems)直接将数据填充进小组件的ListView
setColorStateList()动态更新小组件视图的颜色
setViewLayoutMargin()动态更新小组件视图的边距
setViewLayoutWidth()、setViewLayoutHeight()动态更新小组件视图的宽高
setOnCheckedChangeResponse()监听CheckBox等三种状态小组件的状态变化

XML属性

属性作用
description配置小组件在选择器里的补充描述
previewLayout配置小组件的预览布局
reconfigurable指定小组件的尺寸支持直接调节
configuration_optional指定小组件的内容可以采用默认设计,无需启动配置画面
targetCellWidth、targetCellHeight限定小组件所占的Launcher单元格
maxResizeWidth、maxResizeHeight配置小组件所能支持的最大高宽尺寸

结语

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

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

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

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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

[外链图片转存中…(img-zAQdgtMw-1712501394841)]

[外链图片转存中…(img-TTD2fUfJ-1712501394841)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值