向 widget 选择器添加生成的预览

借助生成的 widget 预览,您可以为 widget 创建动态的个性化预览,准确反映它们在用户主屏幕上的显示方式。它们通过推送 API 提供,这意味着您的应用可以在其生命周期的任何时间点提供预览,而无需接收来自 widget 主机的明确请求。

为了改进应用的 widget 选择器体验,请在搭载 Android 15 及更高版本的设备上提供生成的 widget 预览,在搭载 Android 12 至 Android 14 的设备上提供缩放的 widget 预览(通过指定 previewLayout),并在更早的版本中提供 previewImage

如需了解详情,请观看 YouTube 上的利用实时更新和 widget 丰富您的应用视频。

设置应用以进行生成的 widget 预览

如需在 Android 15 或更高版本的设备上显示生成的微件预览,请先在模块 build.gradle 文件中将 compileSdk 值设置为 35 或更高值,以便能够向微件选择器提供 RemoteViews

然后,应用可以在 GlanceAppWidgetManagerAppWidgetManager 中使用 setWidgetPreview。为防止滥用并缓解系统健康问题,setWidgetPreview 是一个受速率限制的 API。默认限制为每小时大约两次调用。

使用 Jetpack Glance 生成更新后的预览

对于使用 Jetpack Glance 构建的 widget,请执行以下操作:

  1. 替换 GlanceAppWidget.providePreview 函数以提供预览的可组合内容。与在 provideGlance 中一样,加载应用的数据并将其传递给 widget 的内容可组合项,以确保预览显示准确的数据。与 provideGlance 不同,这是一个没有重组或效果的单一组合。

  2. 调用 GlanceAppWidgetManager.setWidgetPreviews 以生成并发布预览。

系统没有提供预览的回调,因此您的应用必须决定何时调用 setWidgetPreviews。更新策略取决于微件的使用场景:

  • 如果 widget 包含静态信息或属于快速操作,请在应用首次启动时设置预览。
  • 您可以在应用拥有数据后(例如,在用户登录或完成初始设置后)设置预览。
  • 您可以设置定期任务,以按所选频率更新预览。

排查生成的预览方面的问题

一个常见问题是,在生成预览后,相对于 widget 的放置大小,预览图片中可能会缺少图片、图标或其他可组合项。此放置大小由 targetCellWidthtargetCellHeight(如果已指定)或应用 widget 提供程序信息文件中的 minWidthminHeight 定义。

这是因为 Android 默认情况下仅渲染在 widget 的最小尺寸下可见的可组合项。换句话说,Android 默认将 previewSizeMode 设置为 SizeMode.Single。它使用应用 widget 提供程序信息 XML 中的 android:minHeightandroid:minWidth 来确定要绘制哪些可组合项。

如需解决此问题,请在 GlanceAppWidget 中替换 previewSizeMode 并将其设置为 SizeMode.Responsive,从而提供一组 DpSize 值。这会告知 Android 需要渲染的所有布局尺寸以进行预览,从而确保所有元素都能正确显示。

针对特定外形规格进行优化。提供 1 或 2 个尺寸,从最小值开始,并遵循 widget 的断点。指定至少一张用于向后兼容的图片。您可以在微件设计指南中找到不同网格尺寸对应的适当最小 DP 值。

生成不含 Jetpack Glance 的更新预览

您可以在不使用 Glance 的情况下使用 RemoteViews。以下示例加载 XML widget 布局资源并将其设置为预览。如果 setWidgetPreview 要在此代码段中显示为方法,则必须将 compileSdk build 设置设为 35 或更高版本。

AppWidgetManager.getInstance(appContext).setWidgetPreview(
    ComponentName(
        appContext,
        ExampleAppWidgetReceiver::class.java
    ),
    AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
    RemoteViews("com.example", R.layout.widget_preview)
)

向微件选择器添加可缩放的微件预览

从 Android 12 开始,微件选择器中显示的微件预览可缩放。您会将其作为 XML 布局提供,该布局设置为微件的默认大小。以前,微件预览是静态可绘制资源,在某些情况下,会导致预览无法准确反映微件添加到主屏幕后的显示效果。

如需实现可缩放的 widget 预览,请改用 appwidget-provider 元素的 previewLayout 属性来提供 XML 布局:

<appwidget-provider
    android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>

我们建议使用与实际 widget 相同的布局,并使用真实的默认值或测试值。大多数应用都使用相同的 previewLayoutinitialLayout。如需有关创建准确的预览版布局的指导,请参阅本页中的以下部分。

我们建议您同时指定 previewLayoutpreviewImage 属性,以便在用户设备不支持 previewLayout 时,您的应用可以回退到使用 previewImagepreviewLayout 属性的优先级高于 previewImage 属性。

微件预览的向后兼容性

为使 Android 11(API 级别 30)或更低版本中的微件选择器能够显示微件的预览,或者作为生成的预览的后备方案,请指定 previewImage 属性。

如果您更改了微件的外观,请更新预览图片。

构建包含动态商品的准确预览

图 1: 未显示任何列表项的 widget 预览。

本部分介绍了在具有集合视图的微件(即使用 ListViewGridViewStackView 的微件)的微件预览中显示多个项的推荐方法。这不适用于生成的 widget 预览。

如果您的 widget 使用了上述某个视图,那么通过直接提供实际 widget 布局来创建可缩放的预览会在 widget 预览不显示任何项时降低体验。这是因为集合视图数据是在运行时动态设置的,并且看起来与图 1 中显示的图片类似。

为了使包含集合视图的 widget 预览在 widget 选择器中正确显示,我们建议维护一个专门用于预览的单独布局文件。此单独的布局文件应包含以下内容:

  • 实际 widget 布局。
  • 包含虚假项的占位集合视图。例如,您可以通过提供包含多个虚假列表项的占位符 LinearLayout 来模拟 ListView

为了说明 ListView 的示例,我们先从一个单独的布局文件开始:

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The number of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo
    // definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

在提供 AppWidgetProviderInfo 元数据的 previewLayout 属性时,指定预览布局文件。您仍然需要为 initialLayout 属性指定实际 widget 布局,并在运行时构建 RemoteViews 时使用实际 widget 布局。

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

复杂列表项

上一部分中的示例提供了虚假列表项,因为列表项是 TextView 对象。如果项是复杂的布局,则提供虚假项可能会更复杂。

假设在 widget_list_item.xml 中定义了一个列表项,该列表项包含两个 TextView 对象:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

如需提供虚假列表项,您可以多次包含该布局,但这会导致每个列表项都相同。如需提供唯一的列表项,请按以下步骤操作:

  1. 为文本值创建一组属性:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. 使用以下属性设置文本:

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. 根据预览需要创建任意数量的样式。重新定义每种样式中的值:

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. 在预览布局中对虚假项应用样式:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" />
    
    </LinearLayout>