使用 Glance 创建应用 widget

以下部分介绍如何使用 Glance 创建基本应用微件。

在清单中声明 AppWidget

完成 设置步骤后,在应用中声明 AppWidget 及其 元数据。

  1. GlanceAppWidgetReceiver 扩展 AppWidget 接收器:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. AndroidManifest.xml 文件和关联的元数据文件中注册应用微件的提供程序:

        <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 元数据

接下来,按照创建微件指南中的说明在 @xml/my_app_widget_info 文件中创建和定义应用 微件信息。

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 对象定义了微件的基本特性。在 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>

微件尺寸设置属性

默认的主屏幕根据定义了高度和宽度的单元格的网格在其窗口中放置微件。大多数主屏幕仅允许微件采用网格单元格的整数倍大小,例如水平方向上两个单元格,垂直方向上三个单元格。

借助微件尺寸设置属性,您可以为微件指定默认大小,并提供微件大小的下限和上限。在此上下文中,微件的默认大小是指微件首次添加到主屏幕时采用的大小。

下表介绍了与微件尺寸设置相关的 <appwidget-provider> 属性:

属性和说明
targetCellWidthtargetCellHeight (Android 12)、 minWidthminHeight
  • 从 Android 12 开始, targetCellWidthtargetCellHeight 属性以网格 单元格为单位指定微件的默认大小。在 Android 11 及更低版本中,这些属性会被忽略,如果主屏幕不支持基于网格的布局,这些属性也可能会被忽略。
  • `minWidth` 和 ` minHeight` 属性以 dp 为单位指定微件的默认大小。如果微件的最小宽度或高度值与单元格的尺寸不匹配 则这些值会向上舍入到最接近的单元格大小。
我们建议您同时指定这两组 属性(targetCellWidthtargetCellHeight,以及minWidthminHeight),以便在用户的设备 不支持targetCellWidthtargetCellHeight时,您的应用可以回退到使用 minWidthminHeight。如果支持, targetCellWidthtargetCellHeight 属性 优先于 minWidthminHeight 属性。
minResizeWidthminResizeHeight 指定微件的绝对最小大小。这些值指定了 微件不可读或无法使用的最小大小。使用 这些属性可让用户将微件调整为小于默认微件大小的大小。如果 minResizeWidth 属性大于 minWidth 或未启用水平大小调整,则该属性会被忽略。请参阅 resizeMode。同样,如果 minResizeHeight 属性大于 minHeight 或未启用垂直大小调整,则该属性会被忽略。
maxResizeWidthmaxResizeHeight 指定微件的建议最大大小。如果这些值不是网格单元格尺寸的倍数,则会向上舍入到最接近的单元格大小。如果 maxResizeWidth 属性小于 minWidth 或未启用水平大小调整,则该属性会被忽略。请参阅 resizeMode。同样, 如果 maxResizeHeight 属性小于 minHeight 或未启用垂直大小调整,则该属性会被忽略。 此属性是在 Android 12 中引入的。
resizeMode 指定微件可调整大小的规则。您可以使用此 属性来让主屏幕微件在横轴上可调整大小、在纵轴上可调整大小, 或者在这两个轴上均可调整大小。用户可轻触并按住微件以显示其大小调整手柄, 然后拖动水平或垂直手柄以更改布局网格上的大小。resizeMode 属性的值包括 horizontalverticalnone。如需将微件声明为在水平和垂直方向上均可调整大小,请使用 horizontal|vertical

示例

为了说明上表中的属性如何影响微件尺寸设置,假设有以下规范:

  • 网格单元格的宽度为 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 开始

使用 targetCellWidthtargetCellHeight 属性作为微件的默认大小。

微件的默认大小为 2x2。微件可以调整为 2x1 或 4x3。

Android 11 及更低版本

使用 minWidthminHeight 属性计算微件的默认大小。

默认宽度 = Math.ceil(80 / 30) = 3

默认高度 = Math.ceil(80 / 50) = 2

微件的默认大小为 3x2。微件可以调整为 2x1 或全屏。

其他微件属性

下表介绍了与微件尺寸设置以外的特性相关的 <appwidget-provider> 属性。

属性和说明
updatePeriodMillis 定义微件框架通过调用 onUpdate() 回调方法从 GlanceAppWidgetReceiver 请求更新的频率。我们建议尽可能降低更新频率(不超过每小时一次),以节省电池电量。 如需了解详情,请参阅 Glance 状态管理中的何时更新微件部分。
initialLayout 指向用于定义 Glance 界面组合呈现之前微件的加载布局的布局资源。您可以使用库中提供的预定义加载布局:@layout/glance_default_loading_layout
configure 定义用户添加微件时启动的配置 activity。请参阅实现配置 activity 指南。
description 指定要由微件选择器为您的微件显示的说明。此属性是在 Android 12 中引入的。
previewLayout (Android 12) 和 previewImage (Android 11 及更低版本)
  • 从 Android 12 开始, previewLayout 属性指定了可缩放的预览,您以设置为微件默认大小的 XML 布局的形式提供该预览。理想情况下, 此属性指向与您的设计布局匹配的静态 XML 映射。
  • 在 Android 11 或更低版本中,previewImage 属性指定了微件外观的静态图片可绘制对象屏幕截图,该屏幕截图会显示在微件选择器中。
我们建议您同时指定这两个属性,以便您的应用在旧版平台上优雅地回退。对于较新的平台(Android 15 及更高版本),您可以使用 `GlanceAppWidget.providePreview` 在 Kotlin 中定义实时生成的预览。请参阅生成的预览指南
autoAdvanceViewId 指定由微件的宿主自动推进的微件子视图的视图 ID。
widgetCategory 声明您的微件是否可以显示在主屏幕 (home_screen)、锁定屏幕 (keyguard) 或两者上。对于 Android 5.0 及更高版本,只有 home_screen 有效。
widgetFeatures 声明微件支持的功能。例如,如果您的微件的配置是可选的,请同时指定 configuration_optionalreconfigurable

定义 GlanceAppWidget

  1. 创建一个从 GlanceAppWidget 扩展的新类,并替换 provideGlance 方法。您可以在此方法中加载呈现微件所需的数据:

    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")
            }
        }
    }

  2. GlanceAppWidgetReceiverglanceAppWidget 中实例化它:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

现在,您已使用 Glance 配置了 AppWidget

使用 GlanceAppWidgetReceiver 类处理微件广播

GlanceAppWidgetReceiver 通过扩展底层 AppWidgetProvider 来协调微件广播和平台状态 更新。当您的微件更新、删除、启用或停用时,它会接收平台事件,并将其转换为 Compose 生命周期请求。

在清单中声明微件

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 属性,该属性用于指定 接收器类。接收器必须接受 ACTION_APPWIDGET_UPDATE 广播操作,在 <intent-filter> 内。

<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():在首次放置或调整微件大小时调用。Glance 会在后台读取选项软件包项,以便您的布局根据运行时尺寸无缝调整。
  • onDeleted(Context, IntArray):每当用户删除特定微件实例时调用。
  • onEnabled(Context):在成功创建微件的第一个实例时触发。非常适合运行全局迁移。
  • onDisabled(Context):在移除提供程序的最后一个活跃实例时调用。
  • onReceive(Context, Intent):在特定回调方法之前拦截每个平台广播。您必须确保您编写的任何自定义接收器逻辑 都会调用 super.onReceive(context, intent),并且绝不能自行调用 goAsync,因为 Glance 会自动异步路由工作 。

接收微件广播 intent

在后台,GlanceAppWidgetReceiver 会过滤和处理以下基础平台微件广播 intent:

创建界面

以下代码段演示了如何创建界面:

/* 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 定义。顺序很重要。
    • Column 中的第一项是具有 12.dp 内边距的 Text 组件。
    • 第二项是 Row,其中的各项水平放置 ,彼此相邻,并且两个 Buttons 在水平方向上居中 (horizontalAlignment)。最终显示取决于可用 空间。下图是其外观示例:
destination_widget
图 1.界面示例。

您可以更改对齐值或应用不同的修饰符值(例如内边距)来更改组件的放置位置和大小。如需查看每个类的组件、参数和可用 修饰符的完整列表,请参阅参考 文档

实现圆角

Android 12 引入了系统参数,用于动态自定义应用微件的圆角半径:

  • system_app_widget_background_radius:指定微件背景容器的圆角半径(不超过 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>