在 Android 12 中构建更现代的应用 Widget
yuyutoo 2024-10-12 01:28 14 浏览 0 评论
从 2008 年开始,Widget 就一直是 Android 系统的一个重要组成部分,也是自定义主屏幕的一个重要方面。您可以将 Widget 理解为一个 "一目了然" 的应用视图,让用户在无需从主屏幕打开应用的前提下,就能对应用数据和核心功能一览无余。但是从 Android 推出至今,AppWidget 的 API 基本就没有什么大的变化,从 2012 年到 2021 年更是只有一个 Android 版本包含了对 AppWidget API 的更新。而随着 Android 12 的推出,也带来了 Widget API 一些亟需改进的更新。
本文我们就来介绍一下 Android 12 中带来了哪些关于 Widget API 的更新,以及有哪些好用的工具可以让开发应用 Widget 变得更加出色。
Widget 工作原理
Widget 运行在一个名为 AppWidgetHost 的远端进程中,比如 Home Screen Launcher,也正因如此,它的运行受到了一些限制。我们来看看 Widget 的工作原理。
在前端,应用首先注册 AppWidgetProvider 来定义 Widget 行为,以及注册 AppWidgetProviderInfo 来定义元数据。然后 AndroidManifest 引用这些信息,让操作系统通过 AndroidManifest 读取元数据,例如 Widget 初始的布局和默认尺寸,并提供 Widget 的预览,紧接着,provider 会使用链接账户来更新布局并对 Widget 进行更新。这里需要注意的是,应用于 Widget 的构建次数有限,所以操作系统是通过接收方的广播事件 (包含了更新信息) 对 Widget 进行更新,这也意味着 Widget 是定期接收来自应用的信息进行更新的。
API
Android 12 的推出带来了很多关于 AppWidget API 的更新,本文不会对所有的 API 一一介绍,而是重点介绍几个对 Widget 构建非常有用的 API。
实现圆角
在 Android 12 中许多关键的界面元素都开始采用圆角设计,为了使 AppWidget 与其他系统组件样式之间看起来一致,Android 12 引入了 system_app_widget_background_radius 和 system_app_widget_inner_radius 两个新的系统参数实现圆角,前一个参数是用来设置 Widget 的圆角半径,后一个则是设置 Widget 内视图的圆角半径。要使用这些参数,只需要定义一个设置了系统参数 corner 的可绘制对象即可,如代码所示:
// res/drawable/app_widget_background.xml
<shape android:shape="rectangle">
<corners android:radius="@android:dimen/system_app_widget_background_radius">
…
</shape>
// res/drawable/app_widget_inner_view_background.xml
<shape android:shape="rectangle">
<corners android:radius="@android:dimen/system_app_widget_inner_radius">
…
</shape>
然后将可绘制对象应用于 Widget 的外部容器,这样做可将系统参数提供的圆角半径应用于 Widget 背景中。同样,将内部视图的可绘制对象应用于表示 Widget 内部容器的布局,如代码所示:
// res/layout/widget_layout.xml
<LinearLayout
android:background=”@drawable/app_widget_background”
…>
<LinearLayout
android:background=”@drawable/app_widget_inner_view_background”
…>
</LinearLayout>
</LinearLayout>
从效果中我们可以看到 Widget 当前内部容器的圆角半径要小于外部容器,这就是新参数的使用方法。
动态颜色
正如我们之前在 Google I/O 大会上宣布的那样,从 Android 12 开始,Widget 可以为按钮、背景及其他组件使用设备主题颜色,包括浅色主题和深色主题。这样可使过渡更流畅,而且还能在不同的 Widget 之间保持一致。
我们添加了动态颜色 API,您可直接获取并使用 Pixel 设备系统上提供的主题背景、颜色等参数,从而让 Widget 同主屏幕的样式保持一致:
// res/layout/widget_layout.xml
<LinearLayout
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:background="?android:attr/colorBackground">
<ImageView
android:tint="?android:attr/colorAccent" />
…
</LinearLayout>
您可以看到,当设置了主题属性之后,Widget 直接从系统壁纸中提取了主色,并将其应用于深色和浅色主题背景中。
响应式布局
Android 12 引入了新的 API 来实现响应式布局,可以随着 Widget 的尺寸调整,自动切换到不同的布局。如下图所示,用户可以通过拖动来任意更改 Widget 的尺寸,Widget 也会根据尺寸的不同而动态更新所要显示的内容。
那么如何做到让 Widget 随着尺寸的变化而动态更新显示内容呢,用如下代码举例,我们定义了三个不同的参数,分别包含最小支持宽度和高度,以及在此大小范围内对应的 RemoteView,系统会自动根据实际的尺寸而自动对 Widget 进行调整。
val viewMapping: Map<SizeF, RemoteViews> = mapof(
SizeF(180.0f, 110.0f) to RemoteViews(
context. packageName,
R.layout.widget_small
),
SizeF (270.0f, 110.0f) to RemoteViews(
context.packageName,
R.layout.widget_medium
),
SizeF(270.0f, 280.0f) to RemoteViews(
context.packageName,
R.layout.widget_large
)
)
appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
Android 12 中还提供了新的 targetCellWidth 和 targetCellHeight 属性,这些属性指定了 Widget 置于主屏幕中时默认的较大单元格尺寸。在 Android 12 之前,可以使用 minWidget 和 minHeight 属性,它们指定了以 dp 为单位的默认 Widget 尺寸,我们建议同时指定这两个属性以保持向后兼容。如果您的 Widget 是可调整尺寸的,那么还可以使用 Android 12 提供的 minResizeWidget/Height 和 maxResizeWidget/Height 属性来限制 Widget 的可调整尺寸范围。
<appwidget-provider
android:targetCellWidth="3"
android: targetCellHeight="2"
android:minWidth="140dp"
android:minHeight="110dp"
android:maxResizeWidth="570dp"
android:maxResizeHeight="450dp"
android:minResizeWidth="140dp"
android:minResizeHeight="110dp"
…>
Widget 选择器
Android 12 还改进了 Widget 选择器的使用体验,引入了两个新的属性,第一个属性是 description,它对 Widget 选择器的作用进行了描述说明,通过它可以了解 Widget 的作用;另一个是 previewLayout,它指定了 Widget 选择器中展示的 XML 布局。实际上在 Android 12 之前可以使用 previewImage 属性来指定静态资源达到类似效果,但是 previewLayout 相比较来说更加精确和方便。另外,由于这些预览都是在运行时构建的,因此也可以动态适配设备的主题。
<appwidget-provider
android:description=
"@string/app_widget_weather_description"
android:previewLayout=
"@layout/widget_weather_forecast_small"
…
/>
目前已经介绍了很多 Android 12 引入的新 API,相信不久之后就会看到越来越多的应用采用新 API 构建出更现代的 Widget 使用体验。
Glance
要构建出色的 Widget,除了需要用到目前更现代的 API 之外,我们还需要更现代、更出色的工具来帮助我们,Glance 就是这么一个出色的工具,它也加入到了 Jetpack 大家庭中。Glance 是由 Compose Runtime 提供支持的 API,通过它就可以使用 Compose 风格的语法来创建 AppWidget,这也意味着您可以通过 Glance 以 composable 构建界面,并将其转换为远端视图显示到 Widget 中,同时还能用到前文中提到的 Android 12 的新 API,并尽可能的让其向后兼容。另外,Glance 还会负责一些 Widget 生命周期以及其他一些常见的操作,听上去是不是觉得非常方便。
接下来我们介绍如何使用 Glance 构建 Widget,首先仍需要像之前一样声明 AppWidget,并在 AndroidManifest 中将其链接到接收器,当然,我们在这里使用了 Glance 提供的 GlanceAppWidgetReceiver 和 GlanceAppWidget,Glance 会为您处理大部分的工作,您只需要覆写 MyAppWidget 中的 Content 方法,提供 AppWidget 内容即可。在定义内容时,不再使用 XML 语法,而是使用 Compose 语法,要显示的内容将会被转换为远端视图展示在 AppWidget 中。
class MyAppWidget: GlanceAppWidget() {
@Composable
override fun Content() {
// 在这里创建 AppWidget
Column(
modifier = Modifier.expandHeight().expandWidth(),
verticalAlignment = Alignment.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = “Where to”, modifier = Modifier.padding(12.dp))
userDestinations()
}
}
}
class MyAppWidgetReceiver: GlanceAppWidgetReceiver() {
// 告知 MyAppWidgetReceiver 该使用哪个 GlanceAppWidget
override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
}
有一点需要了解,虽然 Glance 使用 Compose Runtime 和 Compose 的语法,但它仍是一个独立的框架,由于受到在远端进行构建的限制,您不可能重用在 Jetpack Compose UI 中定义的组件。但如果您已对 Jetpack Compose 非常熟悉,那么 Glance 将非常易于理解。
另外,由于 Glance 使用用户事件 API 的方式处理交互,我们处理同用户的交互将变得更加轻松。如果您了解 Widget 的工作原理就会知道 Widget 在不同进程上工作,这使得处理简单的用户事件也变得困难,因为不在同一进程就代表您没有这个 Widget 的所有权,只能通过进程回调来处理各种事件。
Glance 将这些复杂性抽象了出来,您只需通过向需要的 composable 对象定义 clickable modifier 即可让其支持处理用户点击事件,Glance 会将其中的注入行为全部抽象出来,用户点击了 composanle,即可回调所定义的操作。我们还定义了一些常用的操作,例如,如何启动 Activity,只要调用 launchActivity 传递 Activity 目标类即可。
Button(
text = “Home”,
modifier = Modifier.clickable(launchActivity<NavigationActivity>)
)
此外,我们还可以提供自定义操作来执行一些自定义代码,例如,我们可能希望每当用户点击此按钮时就会更新地理位置并刷新 Widget,如下列代码所示,Glance 会在背后为您处理一些需要注入的工作,并通过广播接收器处理此次点击,最终调用您定义的操作代码。但请注意,如果该种操作为网络请求或数据库访问等较为耗时的操作,请使用 WorkManager API。
Button(
text = “My Location”,
modifier = Modifier.clickable(customAction<UpdateLocationAction>)
)
在前文中我们也提到,您可以使用可调整尺寸的 Widget,但是处理不同的响应式布局也并非易事,Glance 就试图通过定义三种不同的 SizeMode 选项从而让这种工作变得稍微轻松一些。
SizeMode.Single 是默认选项,该选项指定了我们在此处定义的 Widget 内容不会因为可用尺寸变化而改变,这意味着我们在 Widget 元数据上定义的最小支持尺寸只会通过 Content 方法被调用一次,如果 Widget 的可用尺寸发生更改,例如用户调整了 Widget 尺寸,则不会刷新内容。如下图所示,使用了 SizeMode.Single 选项的 Widget,无论其尺寸如何变化,其输出的尺寸大小永远不会得到变化,这是因为 Content 方法只被调用了一次,内容在尺寸发生变化时并没有得到刷新。
class MyAppWidget: GlanceAppWidget() {
override val sizeMode = SizeMode.Single
@Composable
override fun Content() {
val size = LocalSize.current
//…
}
}
若在每次尺寸发生变更都对内容进行刷新,则可使用 SizeMode.Exact 选项。此选项会在用户每次调整 Widget 尺寸时,重新创建 Widget 界面并再次调用 Content 方法,并同时提供最大可用尺寸以便让我们能够在空间足够的情况下更改界面,比如添加额外按钮等等。如下图中,Widget 尺寸发生变化时,其内部的输出也会随时发生变化,这是因为每次 Widget 界面都会被重新创建。
class MyAppWidget: GlanceAppWidget() {
override val sizeMode = SizeMode.Exact
@Composable
override fun Content() {
val size = LocalSize.current
//…
}
}
尽管 SizeMode.Exact 选项看似能够完全满足需求,但是每次都需要重新创建界面,可能会导致用户在调整尺寸时界面的转换因为一些性能问题有点不流畅,此时我们就可以通过 SizeMode.Responsive 选项。例如,此处我们将一些尺寸映射到某些特定形状,每当创建或更新 AppWidget 时 Glance 都会调用每个 Size 定义好的的 Content 方法,每次都将映射到特定尺寸并存储在内存中,系统能够在用户调整 Widget 尺寸时,根据可用尺寸选择最合适的尺寸,而无需重新创建界面从而提供更平稳的转换和更出色的性能。正如下图所展示的那样,当 Widget 尺寸发生变更时,只有当其尺寸能够匹配到所预先定义好的尺寸范围中,其内部输出才会发生变化,更应该注意的是,此时并没有重新创建界面。
同样,我们还可以在 Content() 方法中定义更加多元化的样式,让 Widget 在不同的尺寸下展示更独特的内容。
class MyAppWidget: GlanceAppWidget() {
companion object {
private val SMALL_SQUARE = DpSize (100.dp, 160. dp)
private val HORIZONTAL_RECTANGLE = DpSize (250.dp, 100.dp)
private val BIG_SQUARE = DpSize (250.dp, 250.dp)
}
override val sizeMode = SizeMode.Responsive(
SMALL_SQUARE, HORIZONTAL_RECTANGLE, BIG_SQUARE
)
@Composable
override fun Content() {
val size = LocalSize.current
//…
}
}
除了以上提到的内容外,还有例如对 Widget 状态管理的支持,和即开即用的 Material You 主题背景等更多内容,等待着您的探索。
如需了解更多内容,欢迎您查阅 Android 开发者网站: 应用 Widget 概览,我们非常期待您尝试我们提供的新 API,并期待看到您构建出的 Widget 和您的反馈。
欢迎您打开链接:https://go2.gdsub.com/androiddevfb 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!
相关推荐
- 墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人
-
中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...
- 网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕
-
伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...
- 一课译词:来龙去脉(来龙去脉 的意思解释)
-
Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...
- 高考重要考点:range(range高考用法)
-
range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...
- C++20 Ranges:现代范围操作(现代c++白皮书)
-
1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...
- 学习VBA,报表做到飞 第二章 数组 2.4 Filter函数
-
第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...
- VBA学习笔记:数组:数组相关函数—Split,Join
-
Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...
- VBA如何自定义序列,学会这些方法,让你工作更轻松
-
No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...
- Excel VBA入门教程1.3 数组基础(vba数组详解)
-
1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...
- 远程网络调试工具百宝箱-MobaXterm
-
MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...
- AREX:携程新一代自动化回归测试工具的设计与实现
-
一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...
- Windows、Android、IOS、Web自动化工具选择策略
-
Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...
- python自动化工具:pywinauto(python快速上手 自动化)
-
简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...
- 时下最火的 Airtest 如何测试手机 APP?
-
引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...
- 【推荐】7个最强Appium替代工具,移动App自动化测试必备!
-
在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)