向微件选择器添加预览

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

借助生成的微件预览,您可以为微件创建动态的个性化预览,准确反映微件在用户主屏幕上的显示方式。对于 Android 15 及更高版本,这些预览通过推送 API 提供,这意味着您的应用可以在其生命周期的任何时间点提供预览,而无需接收来自微件宿主的明确请求。

如需了解详情,请观看 使用实时动态和微件丰富您的应用 在 YouTube。

添加生成的预览

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

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

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

  • 如果微件具有静态信息或属于快速操作,请在应用首次启动时设置预览。
  • 您可以在应用拥有数据后设置预览;例如,在用户登录或初始设置后。
  • 您可以设置定期任务,以您选择的节奏更新预览。

以下示例加载 XML 微件布局资源并将其设置为预览。如需让 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 布局提供,该布局设置为微件的默认大小。以前,微件预览是静态可绘制资源,在某些情况下,会导致预览在添加到主屏幕后不能准确地反映微件。

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

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

我们建议使用与实际微件相同的布局,并使用真实的默认值或测试值。大多数应用使用相同的 previewLayoutinitialLayout。如需了解如何创建准确的预览布局,请参阅 构建包含动态项的准确预览

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

添加静态微件预览以实现向后兼容性

如需让 Android 11(API 级别 30)或更低版本中的微件选择器能够显示微件的预览,或者作为可缩放预览的回退,请指定 previewImage 属性。

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

如果您未使用 setWidgetPreview 设置生成的预览,此属性也会用作生成预览的回退。

构建包含动态项的准确预览

图 1: 显示没有列表项的微件预览。

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

如果您的微件使用其中一个视图,则通过在 previewLayout 中直接提供实际微件布局来创建可缩放的预览可能会降低体验,因为微件预览不显示任何项。发生这种情况的原因是集合视图数据是在运行时动态设置的,并且看起来与图 1 中显示的图片类似。

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

  • 实际微件布局。
  • 包含虚假项的占位符集合视图。例如,您可以通过提供包含多个虚假列表项的占位符 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 属性指定实际微件布局,并在运行时构建 RemoteViews 时使用实际微件布局。

<appwidget-provider
    previewLayout="@layout/widget_preview"
    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>