以下部分介绍了如何使用 Glance 创建基本应用 widget。
在清单中声明 AppWidget
完成设置步骤后,在应用中声明 AppWidget 及其元数据。
从
GlanceAppWidgetReceiver扩展AppWidget接收器:class MyAppWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget") }
在
AndroidManifest.xml文件和关联的元数据文件中注册应用 widget 的提供程序:<receiver android:name=".glance.MyReceiver" android:exported="true"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_app_widget_info" /> </receiver>
添加 AppWidgetProviderInfo 元数据
接下来,按照创建 widget 指南在 @xml/my_app_widget_info 文件中创建和定义应用 widget 信息。
Glance 的唯一区别在于没有 initialLayout XML,但您必须定义一个。您可以使用库中提供的预定义加载布局:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>
声明 AppWidgetProviderInfo XML
AppWidgetProviderInfo 对象定义了 widget 的基本特性。在 XML 元数据资源文件 (res/xml/my_app_widget_info.xml) 的 <appwidget-provider> 元素中定义 AppWidgetProviderInfo:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:maxResizeWidth="250dp"
android:maxResizeHeight="120dp"
android:updatePeriodMillis="86400000"
android:description="@string/example_appwidget_description"
android:previewLayout="@layout/example_appwidget_preview"
android:initialLayout="@layout/glance_default_loading_layout"
android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>
微件尺寸设置属性
默认主屏幕会根据具有指定高度和宽度的单元格网格,在其窗口中放置 widget。大多数主屏幕仅允许微件采用网格单元的整数倍大小,例如水平方向两个单元格,垂直方向三个单元格。
借助 widget 大小属性,您可以为 widget 指定默认大小,并提供 widget 大小的下限和上限。在此上下文中,微件的默认大小是指微件首次添加到主屏幕时所采用的大小。
下表介绍了与微件大小调整相关的 <appwidget-provider> 属性:
| 属性和说明 | |
|---|---|
targetCellWidth 和 targetCellHeight(Android 12)、minWidth 和 minHeight |
targetCellWidth 和 targetCellHeight,以及 minWidth 和 minHeight),以便在用户的设备不支持 targetCellWidth 和 targetCellHeight 时,您的应用可以回退到使用 minWidth 和 minHeight。如果受支持,targetCellWidth 和 targetCellHeight 属性优先于 minWidth 和 minHeight 属性。
|
minResizeWidth 和 minResizeHeight |
指定 widget 的绝对最小尺寸。这些值指定了微件在多大尺寸下会变得难以辨认或无法使用。使用这些属性可让用户将微件的大小调整为小于默认微件大小。如果 minResizeWidth 属性大于 minWidth,或者未启用水平调整大小,系统会忽略该属性。请参阅 resizeMode。同样,如果 minResizeHeight 属性大于 minHeight,或者未启用垂直大小调整,系统会忽略该属性。 |
maxResizeWidth 和 maxResizeHeight |
指定 widget 的建议最大尺寸。如果这些值不是网格单元尺寸的倍数,则会向上舍入到最接近的单元大小。如果 maxResizeWidth 属性小于 minWidth 或未启用水平大小调整,系统会忽略该属性。请参阅 resizeMode。同样,如果 maxResizeHeight 属性小于 minHeight,或者未启用垂直大小调整,则系统会忽略该属性。
在 Android 12 中引入。 |
resizeMode |
指定可用于调整 widget 大小的规则。您可以使用此属性使主屏幕 widget 可在水平方向、垂直方向或两个方向上调整大小。用户轻触并按住微件即可显示其大小调整手柄,然后拖动水平或垂直手柄即可在布局网格上更改其大小。resizeMode 属性的值包括 horizontal、vertical 和 none。如需将 widget 声明为可沿水平和垂直方向调整大小,请使用 horizontal|vertical。 |
示例
为了说明上表中的属性如何影响 widget 大小,我们假设以下规范:
- 网格单元格宽 30 dp,高 50 dp。
- 系统会提供以下属性规范:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp"
android:minHeight="80dp"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:maxResizeWidth="120dp"
android:maxResizeHeight="120dp"
android:resizeMode="horizontal|vertical" />
从 Android 12 开始:
使用 targetCellWidth 和 targetCellHeight 属性作为 widget 的默认大小。
该微件的大小默认为 2x2。该微件的大小可调整为 2x1 到 4x3 之间的任意尺寸。
Android 11 及更低版本:
使用 minWidth 和 minHeight 属性计算 widget 的默认大小。
默认宽度 = Math.ceil(80 / 30) = 3
默认高度 = Math.ceil(80 / 50) = 2
该微件的默认大小为 3x2。该微件的大小可以缩小到 2x1,也可以放大到全屏。
其他 widget 属性
下表介绍了与微件大小调整以外的特性相关的 <appwidget-provider> 属性。
| 属性和说明 | |
|---|---|
updatePeriodMillis |
定义 widget 框架通过调用 onUpdate() 回调方法来请求 GlanceAppWidgetReceiver 进行更新的频率。我们建议您尽可能降低更新频率(不超过每小时一次),以节省电池电量。
如需了解详情,请参阅 Glance 状态管理中的何时更新 widget 部分。 |
initialLayout |
指向布局资源,该资源定义了在 Glance 界面组合渲染之前微件的加载布局。您可以使用库中提供的预定义加载布局:@layout/glance_default_loading_layout。 |
configure |
定义在用户添加 widget 时启动的配置 activity。请参阅允许用户配置应用 widget 指南。 |
description |
指定微件选择器要为您的微件显示的说明。在 Android 12 中引入。 |
previewLayout (Android 12) 和 previewImage(Android 11 及更低版本) |
|
autoAdvanceViewId |
指定由 widget 的托管应用自动推进的 widget 子视图的视图 ID。 |
widgetCategory |
声明您的 widget 是否可以显示在主屏幕 (home_screen)、锁定屏幕 (keyguard) 或两者上。对于 Android 5.0 及更高版本,只有 home_screen 有效。 |
widgetFeatures |
声明该 widget 支持的功能。例如,如果微件的配置是可选的,请同时指定 configuration_optional 和 reconfigurable。 |
定义 GlanceAppWidget
创建一个新类,用于扩展
GlanceAppWidget并替换provideGlance方法。您可以通过此方法加载呈现 widget 所需的数据:class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { // In this method, load data needed to render the AppWidget. // Use `withContext` to switch to another thread for long running // operations. provideContent { // create your AppWidget here Text("Hello World") } } }
在
GlanceAppWidgetReceiver的glanceAppWidget中将其实例化:class MyAppWidgetReceiver : GlanceAppWidgetReceiver() { // Let MyAppWidgetReceiver know which GlanceAppWidget to use override val glanceAppWidget: GlanceAppWidget = MyAppWidget() }
您现在已使用 Glance 配置了 AppWidget。
管理不同尺寸范围的小组件图片内存
当微件显示位图或 content:// 图片 URI 时,请执行以下操作,使序列化 RemoteViews 图片总内存保持在系统限制(6 * width *
height 字节,在 Android 17 及更高版本中通过 IllegalArgumentException 强制执行)内:
- 在
provideContent之前解码共享位图:如果您的 widget 使用SizeMode.Responsive或SizeMode.Exact,Glance 会针对每个DpSize存储桶运行一次provideContentlambda。在provideContent内解码Bitmap会为每个存储桶分配一个单独的Bitmap实例,从而使RemoteViews.mBitmapCache中的内存用量成倍增加。在provideContent之前,在provideGlance中对降采样位图(对于不透明图片,为Bitmap.Config.RGB_565)进行一次解码,以便所有存储桶共享一个Bitmap引用。 使用匹配的
ImageProvider导入:Jetpack Glance 在两个软件包中提供了两个不同的ImageProvider重载:- 在传递内存中的
ImageProvider(bitmap: Bitmap)或可绘制资源ImageProvider(resId: Int)时,请使用import androidx.glance.ImageProvider。 - 传递 URI
ImageProvider(uri: Uri)时,请使用import androidx.glance.appwidget.ImageProvider。通过FileProviderURI 提供缓存的文件时,请向每个已安装的Intent.CATEGORY_HOME启动器软件包授予Intent.FLAG_GRANT_READ_URI_PERMISSION,以便主机可以呈现图片。
- 在传递内存中的
使用 GlanceAppWidgetReceiver 类处理 widget 广播
GlanceAppWidgetReceiver 通过扩展底层 AppWidgetProvider 来协调 widget 广播和平台状态更新。当微件更新、删除、启用或停用时,它会接收平台事件,并将其转换为 Compose 生命周期请求。
在清单中声明 widget
在 AndroidManifest.xml 文件中将 GlanceAppWidgetReceiver 类子类声明为广播接收器:
<receiver android:name="MyReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>
<receiver> 元素需要 android:name 属性,该属性用于指定接收器类。接收器必须接受 <intent-filter> 中的 ACTION_APPWIDGET_UPDATE 广播操作。
<meta-data> 元素必须将其名称标识为 android.appwidget.provider,并且 android:resource 属性必须指向您的 AppWidgetProviderInfo XML 元数据资源 (@xml/my_app_widget_info)。
实现 GlanceAppWidgetReceiver 类
在 Glance 中,您需要扩展 GlanceAppWidgetReceiver,而不是直接扩展 AppWidgetProvider。通过将接收器关联到 GlanceAppWidget 实例来实现此功能。GlanceAppWidgetReceiver 中提供的主要回调的运作方式如下:
onUpdate():由 Glance 自动替换,以执行组合更新。如果您手动替换onUpdate,则必须调用super.onUpdate,以便 Glance 能够成功启动组合线程。onAppWidgetOptionsChanged():在首次放置 widget 或调整其大小时调用。Glance 会在后台读取选项 bundle 项,以便您的布局根据运行时尺寸进行无缝调整。onDeleted(Context, IntArray):每当用户删除特定 widget 实例时调用。onEnabled(Context):在成功创建 widget 的第一个实例时触发。非常适合运行全球迁移。onDisabled(Context):在提供程序的最后一个活跃实例被移除时调用。onReceive(Context, Intent):在特定回调方法之前拦截每个平台广播。您必须确保编写的任何自定义接收器逻辑都会调用super.onReceive(context, intent),并且绝不能自行调用goAsync,因为 Glance 会自动异步路由工作。
接收微件广播 intent
在后台,GlanceAppWidgetReceiver 会过滤并处理以下基础平台 widget 广播 intent:
ACTION_APPWIDGET_UPDATEACTION_APPWIDGET_DELETEDACTION_APPWIDGET_ENABLEDACTION_APPWIDGET_DISABLEDACTION_APPWIDGET_OPTIONS_CHANGED
创建界面
以下代码段演示了如何创建界面:
/* Import Glance Composables In the event there is a name clash with the Compose classes of the same name, you may rename the imports per https://kotlinlang.org/docs/packages.html#imports using the `as` keyword. import androidx.glance.Button import androidx.glance.layout.Column import androidx.glance.layout.Row import androidx.glance.text.Text */ class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { // Load data needed to render the AppWidget. // Use `withContext` to switch to another thread for long running // operations. provideContent { // create your AppWidget here MyContent() } } @Composable private fun MyContent() { Column( modifier = GlanceModifier.fillMaxSize(), verticalAlignment = Alignment.Top, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) Row(horizontalAlignment = Alignment.CenterHorizontally) { Button( text = "Home", onClick = actionStartActivity<MyActivity>() ) Button( text = "Work", onClick = actionStartActivity<MyActivity>() ) } } } }
上述代码示例会执行以下操作:
- 在顶层
Column中,各项会垂直放置,一个接一个。 Column会扩大其尺寸以匹配可用空间(通过GlanceModifier),并将其内容与顶部对齐 (verticalAlignment) 且水平居中 (horizontalAlignment)。Column的内容使用 lambda 定义。顺序很重要。
您可以更改对齐值或应用不同的修饰符值(例如内边距),以更改组件的位置和大小。如需查看每个类的组件、参数和可用修饰符的完整列表,请参阅参考文档。
实现圆角
Android 12 引入了系统参数,用于动态自定义应用 widget 的圆角半径:
system_app_widget_background_radius:指定 widget 背景容器的圆角半径(不得大于 28 dp)。- 内半径:为防止内容被剪裁,请根据系统背景轮廓计算内部内容的比例半径:
systemRadiusValue - widgetPadding
在 Glance 中,您可以使用 GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius) 在组合中动态应用圆角半径大小属性。
为了在搭载 Android 11(API 级别 30)或更低版本的设备上实现向后兼容性,请实现自定义属性和自定义主题资源回退:
/values/attrs.xml<resources> <attr name="backgroundRadius" format="dimension" /> </resources>/values/styles.xml<resources> <style name="MyWidgetTheme"> <item name="backgroundRadius">@dimen/my_background_radius_dimen</item> </style> </resources>/values-31/styles.xml<resources> <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item> </style> </resources>/drawable/my_widget_background.xml<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="?attr/backgroundRadius" /> </shape>