plot.setBorderStyle(Plot.BorderStyle.ROUNDED, 12f, 12f)
plot.isDrawingCacheEnabled = true
// Reflect chart’s bitmap to widget.
val bmp = plot.drawingCache
val remoteViews = RemoteViews(context.packageName, R.layout.widget_pedometer)
remoteViews.setBitmap(R.id.bar_chart, “setImageBitmap”, bmp)
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}
不用特别适配,直接运行到12上,就能有圆角效果。
但布局需要遵从如下两点建议:
- 四周的边角不要放置内容,防止被切掉
- 背景不要采用透明的、空的视图或布局,避免系统无法探测边界去进行裁切
事实上,系统预设了如下dimension以设置默认的圆角表现。
system_app_widget_background_radius
: 小组件背景的圆角尺寸,默认16dp,上限28dpsystem_app_widget_inner_radius
: 小组件内部视图的圆角尺寸 ,默认8dp,上限20dpsystem_app_widget_internal_padding
:内部视图的padding值,默认16dp
看下官方的对于内外圆角尺寸的示意图。
注意:
- 这些dimension可以被ROM厂商或3rd Launcher修改,不一定能保证一致性的尺寸
- 官方没有说明小组件的内部视图如何才能应用上内部圆角尺寸,DEMO确实也没有适配上,不知道是ROM的问题还是App的问题,有待后续的进一步研究
当然12以前的系统想要支持圆角设计也很简单:自定义radius的attribute,应用在shape drawable上,手动将drawable应用到background。具体可参考官方Sample:
3. 动态的色彩效果
给小组件添加暗黑主题支持即可自动适配动态色彩。
4. 改进的小组件预览
12针对小组件选择时的预览界面进行了改进,方便展示更加精准的预览效果。
4.1 动态预览
之前只能使用previewImage
属性展示一张预览图,功能迭代的过程中忘记更新它的话,可能导致预览和实际效果发生偏差。
12新引入了previewLayout
属性用以配置小组件的实际布局,使得用户能够在小组件的选择器里看到更加接近实际效果的视图,而不再是一层不变的静态图片。
这样一来在保证效果一致的同时免去了额外维护预览图的麻烦。
<appwidget-provider
android:previewImage=“@drawable/app_widget_pedometer_preview_2”
android:previewLayout=“@layout/widget_pedometer”
左边是步数小组件一开始的设计图,右边是最后的实际效果。
如果忘记说服UI重新作图的话,在11上的预览图会和实际效果有较大偏差。而12上不用在乎设计图是否更新,借助新的API即可直接预览实际效果,所见即所得。
一般来说previewLayout
属性最好指定小组件的实际布局。但如果预览的测试数据和实际的默认值有冲突的话,可以指定专用的预览布局,只需要确保布局的一致。
4.2 添加预览说明
description
属性则可以在小组件预览的下方展示额外的说明,便于用户更好地了解其功能定位。
需要提醒的是description
属性并非12新增,但12之前的选择器不支持展示这个说明。
5. 支持新的交互控件
之前的小组件不支持CheckBox
等控件,从12开始全面支持CheckBox
、Switch
和RadioButton
三种状态控件。
下面是采用这三种控件的简单效果。
再做个简单的待办事项以更好地说明状态小组件的使用。
// 小组件件布局里指定CheckBox控件即可
<LinearLayout …
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”
android:theme=“@style/Theme.AppWidget.AppWidgetContainer”>
…
如果将同样的代码运行到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
文本内容不确定的话,可以通过代码动态地控制文本,同时还可以监听用户的选择事件。
比如我们要展示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()
}
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”>
6.2 灵活调节尺寸
iOS上添加小组件后尺寸就固定了,不支持调节。而Android 12上小组件在长按后即可灵活调节。
想要支持这个特性只需要给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菜单,这在以前的版本上是没有的。
7. 高效地控制布局
小组件内容较多的时候,为了展示的完整往往会给它限定Size,这意味着只有Launcher空间足够大小组件才能成功放置。当Launcher空间捉急的时候就尴尬了,用户只能在移除别的小组件和放弃你的小组件之间做个抉择。
免除这种困扰的最佳做法是在不同的Size下采用不同的布局,对展示的内容做出取舍。即Size充足的情况下提供更多丰富的内容,反之只呈现最基本、最常用的信息。
7.1 响应式布局
之前是如何做到这一需求呢?除了预设各种尺寸的小组件的一般思路以外,通过onAppWidgetOptionsChanged
回调也可以控制布局的变化,但往往非常繁琐。
而12上借助新增的RemoteViews(Map<SizeF, RemoteViews> map)
API可以大大简化实现过程。在小组件放置的时候就将Size和布局的映射关系告知系统,当Size变化了AppWidgetManager
将自动响应更新对应的布局。
比如待办事项小组件在Size为3x2的时候额外展示添加按钮,2x2的时候只展示事项列表的相应式布局。
代码的实现也简单清晰:
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选中之后更新文本颜色,思路很简单:
- 监听小组件的点击事件并传递目标视图
- 根据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)
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
【Android高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-kYaKlF5g-1714266363118)]
【Android高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!