使用 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>

微件尺寸设置属性

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

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

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

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

使用 targetCellWidth 和 targetCellHeight 属性作为微件的默认大小。

默认情况下,微件的大小为 2x2。微件可以调整为最小 2x1 或最大 4x3。

Android 11 及更低版本:

使用 minWidth 和 minHeight 属性计算微件的默认大小。

默认宽度 = 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。请参阅允许用户配置应用微件指南。
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_optional 和 reconfigurable。

定义 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. 在 GlanceAppWidgetReceiver 的 glanceAppWidget 中实例化它:

    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,该 intent 位于 <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>