切片模板

本文档详细介绍了如何使用 Android Jetpack 中的模板构建器构造切片

定义您的切片模板

切片是使用 ListBuilder 构造的。利用 ListBuilder,您可以添加不同类型的行并使其显示在一个列表中。本部分介绍了每种行类型及其构造方式。

SliceAction

切片模板的最基本元素是 SliceActionSliceAction 包含一个标签以及一个 PendingIntent,属于以下某一项:

  • 图标按钮
  • 默认切换开关
  • 自定义切换开关(一个具有开启/关闭状态的可绘制对象)

SliceAction 是供模板构建器使用的,详见本部分余下篇幅。SliceAction 可定义图片模式,用于确定为操作呈现图片的方式:

  • ICON_IMAGE:超小尺寸,可着色
  • SMALL_IMAGE:小尺寸,不可着色
  • LARGE_IMAGE:最大尺寸,不可着色

HeaderBuilder

在大多数情况下,您应使用 HeaderBuilder 为模板设置标题。标题可支持以下内容:

  • 大标题
  • 副标题
  • 摘要副标题
  • 主要操作

下面例举了一些标题配置。请注意,灰色框表示可能的图标和内边距位置:

在不同界面上呈现标题

当需要切片时,显示界面会决定如何呈现切片。请注意,不同托管表面的呈现方式可能存在一定差异。

采用较小格式时,通常只会显示标题(如果存在的话)。如果您为标题指定了摘要,则会显示摘要文字,而不是副标题文字。

如果您尚未在模板中指定标题,则通常会显示添加到 ListBuilder 的第一行。

HeaderBuilder 示例 - 包含标题的简单列表切片

Kotlin

    fun createSliceWithHeader(sliceUri: Uri) =
        list(context, sliceUri, ListBuilder.INFINITY) {
            setAccentColor(0xff0F9D) // Specify color for tinting icons
            header {
                title = "Get a ride"
                subtitle = "Ride in 4 min"
                summary = "Work in 1 hour 45 min | Home in 12 min"
            }
            row {
                title = "Home"
                subtitle = "12 miles | 12 min | $9.00"
                addEndItem(
                    IconCompat.createWithResource(context, R.drawable.ic_home),
                    ListBuilder.ICON_IMAGE
                )
            }
        }
    

Java

    public Slice createSliceWithHeader(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }

        // Construct the parent.
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .setAccentColor(0xff0F9D58) // Specify color for tinting icons.
                .setHeader( // Create the header and add to slice.
                        new HeaderBuilder()
                                .setTitle("Get a ride")
                                .setSubtitle("Ride in 4 min.")
                                .setSummary("Work in 1 hour 45 min | Home in 12 min.")
                ).addRow(new RowBuilder() // Add a row.
                        .setPrimaryAction(
                                createActivityAction()) // A slice always needs a SliceAction.
                        .setTitle("Home")
                        .setSubtitle("12 miles | 12 min | $9.00")
                        .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_home),
                                SliceHints.ICON_IMAGE)
                ); // Add more rows if needed...
        return listBuilder.build();
    }
    

标题中的 SliceAction

切片标题也可以显示 SliceAction:

Kotlin

    fun createSliceWithActionInHeader(sliceUri: Uri): Slice {
        // Construct our slice actions.
        val noteAction = SliceAction.create(
            takeNoteIntent,
            IconCompat.createWithResource(context, R.drawable.ic_pencil),
            ICON_IMAGE,
            "Take note"
        )

        val voiceNoteAction = SliceAction.create(
            voiceNoteIntent,
            IconCompat.createWithResource(context, R.drawable.ic_mic),
            ICON_IMAGE,
            "Take voice note"
        )

        val cameraNoteAction = SliceAction.create(
            cameraNoteIntent,
            IconCompat.createWithResource(context, R.drawable.ic_camera),
            ICON_IMAGE,
            "Create photo note"
        )

        // Construct the list.
        return list(context, sliceUri, ListBuilder.INFINITY) {
            setAccentColor(0xfff4b4) // Specify color for tinting icons
            header {
                title = "Create new note"
                subtitle = "Easily done with this note taking app"
            }
            addAction(noteAction)
            addAction(voiceNoteAction)
            addAction(cameraNoteAction)
        }
    }
    

Java

    public Slice createSliceWithActionInHeader(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        // Construct our slice actions.
        SliceAction noteAction = SliceAction.create(takeNoteIntent,
                IconCompat.createWithResource(getContext(), R.drawable.ic_pencil),
                ListBuilder.ICON_IMAGE, "Take note");

        SliceAction voiceNoteAction = SliceAction.create(voiceNoteIntent,
                IconCompat.createWithResource(getContext(), R.drawable.ic_mic),
                ListBuilder.ICON_IMAGE,
                "Take voice note");

        SliceAction cameraNoteAction = SliceAction.create(cameraNoteIntent,
                IconCompat.createWithResource(getContext(), R.drawable.ic_camera),
                ListBuilder.ICON_IMAGE,
                "Create photo note");

        // Construct the list.
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .setAccentColor(0xfff4b400) // Specify color for tinting icons
                .setHeader(new HeaderBuilder() // Construct the header.
                        .setTitle("Create new note")
                        .setSubtitle("Easily done with this note taking app")
                )
                .addRow(new RowBuilder()
                        .setTitle("Enter app")
                        .setPrimaryAction(createActivityAction())
                )
                // Add the actions to the ListBuilder.
                .addAction(noteAction)
                .addAction(voiceNoteAction)
                .addAction(cameraNoteAction);
        return listBuilder.build();
    }
    

RowBuilder

您可以使用 RowBuilder 构造一行内容。一行可支持以下任何一项:

  • 大标题
  • 副标题
  • 起始项:SliceAction、图标或时间戳
  • 结束项:SliceAction、图标或时间戳
  • 主要操作

您可以通过多种方式组合行内容,但须遵守以下限制:

  • 起始项不会显示在切片的第一行中
  • 结束项不能同时包含 SliceAction 对象和 Icon 对象
  • 一行只能包含一个时间戳

内容行的示例如下图所示。请注意,灰色框表示可能的图标和内边距位置:

RowBuilder 示例 - WLAN 切换开关

以下示例中的行包含一个主要操作和一个默认切换开关。

Kotlin

    fun createActionWithActionInRow(sliceUri: Uri): Slice {
        // Primary action - open wifi settings.
        val wifiAction = SliceAction.create(
            wifiSettingsPendingIntent,
            IconCompat.createWithResource(context, R.drawable.ic_wifi),
            ICON_IMAGE,
            "Wi-Fi Settings"
        )

        // Toggle action - toggle wifi.
        val toggleAction = SliceAction.createToggle(
            wifiTogglePendingIntent,
            "Toggle Wi-Fi",
            isConnected /* isChecked */
        )

        // Create the parent builder.
        return list(context, wifiUri, ListBuilder.INFINITY) {
            setAccentColor(0xff4285) // Specify color for tinting icons / controls.
            row {
                title = "Wi-Fi"
                primaryAction = wifiAction
                addEndItem(toggleAction)
            }
        }
    }
    

Java

    public Slice createActionWithActionInRow(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        // Primary action - open wifi settings.
        SliceAction primaryAction = SliceAction.create(wifiSettingsPendingIntent,
                IconCompat.createWithResource(getContext(), R.drawable.ic_wifi),
                ListBuilder.ICON_IMAGE,
                "Wi-Fi Settings"
        );

        // Toggle action - toggle wifi.
        SliceAction toggleAction = SliceAction.createToggle(wifiTogglePendingIntent,
                "Toggle Wi-Fi", isConnected /* isChecked */);

        // Create the parent builder.
        ListBuilder listBuilder = new ListBuilder(getContext(), wifiUri, ListBuilder.INFINITY)
                // Specify color for tinting icons / controls.
                .setAccentColor(0xff4285f4)
                // Create and add a row.
                .addRow(new RowBuilder()
                        .setTitle("Wi-Fi")
                        .setPrimaryAction(primaryAction)
                        .addEndItem(toggleAction));
        // Build the slice.
        return listBuilder.build();
    }
    

GridBuilder

您可以使用 GridBuilder 构造内容网格。网格可支持以下图片类型:

  • ICON_IMAGE:超小尺寸,可着色
  • SMALL_IMAGE:小尺寸,不可着色
  • LARGE_IMAGE:最大尺寸,不可着色

网格单元格是使用 CellBuilder 构造的。一个单元格最多支持两行文字和一张图片。单元格不能为空。

网格示例如下图所示:

GridRowBuilder 示例 - 附近的餐厅

以下示例中的网格行包含图片和文本。

Kotlin

    fun createSliceWithGridRow(sliceUri: Uri): Slice {
        // Create the parent builder.
        return list(context, sliceUri, ListBuilder.INFINITY) {
            header {
                title = "Famous restaurants"
                primaryAction = SliceAction.create(
                    pendingIntent, icon, ListBuilder.ICON_IMAGE, "Famous restaurants"
                )
            }
            gridRow {
                cell {
                    addImage(image1, LARGE_IMAGE)
                    addTitleText("Top Restaurant")
                    addText("0.3 mil")
                    contentIntent = intent1
                }
                cell {
                    addImage(image2, LARGE_IMAGE)
                    addTitleText("Fast and Casual")
                    addText("0.5 mil")
                    contentIntent = intent2
                }
                cell {
                    addImage(image3, LARGE_IMAGE)
                    addTitleText("Casual Diner")
                    addText("0.9 mi")
                    contentIntent = intent3
                }
                cell {
                    addImage(image4, LARGE_IMAGE)
                    addTitleText("Ramen Spot")
                    addText("1.2 mi")
                    contentIntent = intent4
                }
            }
        }
    }
    

Java

    public Slice createSliceWithGridRow(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        // Create the parent builder.
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .setHeader(
                        // Create the header.
                        new HeaderBuilder()
                                .setTitle("Famous restaurants")
                                .setPrimaryAction(SliceAction
                                        .create(pendingIntent, icon, ListBuilder.ICON_IMAGE,
                                                "Famous restaurants"))
                )
                // Add a grid row to the list.
                .addGridRow(new GridRowBuilder()
                        // Add cells to the grid row.
                        .addCell(new CellBuilder()
                                .addImage(image1, ListBuilder.LARGE_IMAGE)
                                .addTitleText("Top Restaurant")
                                .addText("0.3 mil")
                                .setContentIntent(intent1)
                        ).addCell(new CellBuilder()
                                .addImage(image2, ListBuilder.LARGE_IMAGE)
                                .addTitleText("Fast and Casual")
                                .addText("0.5 mil")
                                .setContentIntent(intent2)
                        )
                        .addCell(new CellBuilder()
                                .addImage(image3, ListBuilder.LARGE_IMAGE)
                                .addTitleText("Casual Diner")
                                .addText("0.9 mi")
                                .setContentIntent(intent3))
                        .addCell(new CellBuilder()
                                .addImage(image4, ListBuilder.LARGE_IMAGE)
                                .addTitleText("Ramen Spot")
                                .addText("1.2 mi")
                                .setContentIntent(intent4))
                        // Every slice needs a primary action.
                        .setPrimaryAction(createActivityAction())
                );
        return listBuilder.build();
    }
    

RangeBuilder

使用 RangeBuilder,您可以创建包含进度条或输入范围(例如滑块)的行。

进度和滑块示例如下图所示:

RangeBuilder 示例 - 滑块

以下示例演示了如何使用 InputRangeBuilder 构建包含音量滑块的切片。要构造进度行,请使用 addRange()

Kotlin

    fun createSliceWithRange(sliceUri: Uri): Slice {
        return list(context, sliceUri, ListBuilder.INFINITY) {
            inputRange {
                title = "Ring Volume"
                inputAction = volumeChangedPendingIntent
                max = 100
                value = 30
            }
        }
    }
    

Java

    public Slice createSliceWithRange(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        // Construct the parent.
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                .addRow(new RowBuilder() // Every slice needs a row.
                        .setTitle("Enter app")
                         // Every slice needs a primary action.
                        .setPrimaryAction(createActivityAction())
                )
                .addInputRange(new InputRangeBuilder() // Create the input row.
                        .setTitle("Ring Volume")
                        .setInputAction(volumeChangedPendingIntent)
                        .setMax(100)
                        .setValue(30)
                );
        return listBuilder.build();
    }
    

内容延迟

您应该尽快从 SliceProvider#onBindSlice 返回切片。耗时的调用可能会导致显示问题,例如闪烁和突然调整大小。

如果您的切片内容无法快速加载,您可以构造包含 null 或占位符内容的切片,同时构建器中注明内容正在加载。一旦内容可供显示,使用您的切片 URI 调用 getContentResolver().notifyChange(sliceUri, null)。此时会再次调用 SliceProvider#onBindSlice,让您可以使用新内容重新构建切片。

内容延迟示例 - 骑车上班

在下面的“骑车上班”行中,上班距离是动态确定的,可能不会立即显示。示例代码演示了在内容加载时使用 null 副标题作为占位符:

Kotlin

    fun createSliceShowingLoading(sliceUri: Uri): Slice {
        // We’re waiting to load the time to work so indicate that on the slice by
        // setting the subtitle with the overloaded method and indicate true.
        return list(context, sliceUri, ListBuilder.INFINITY) {
            row {
                title = "Ride to work"
                setSubtitle(null, true)
                addEndItem(IconCompat.createWithResource(context, R.drawable.ic_work), ICON_IMAGE)
            }
        }
    }
    

Java

    public Slice createSliceShowingLoading(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        // Construct the parent.
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                // Construct the row.
                .addRow(new RowBuilder()
                        .setPrimaryAction(createActivityAction())
                        .setTitle("Ride to work")
                        // We’re waiting to load the time to work so indicate that on the slice by
                        // setting the subtitle with the overloaded method and indicate true.
                        .setSubtitle(null, true)
                        .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_work),
                                ListBuilder.ICON_IMAGE)
                );
        return listBuilder.build();
    }

    private SliceAction createActivityAction() {
        return SliceAction.create(
                PendingIntent.getActivity(
                        getContext(),
                        0,
                        new Intent(getContext(), MainActivity.class),
                        0
                ),
                IconCompat.createWithResource(getContext(), R.drawable.ic_home),
                ListBuilder.ICON_IMAGE,
                "Enter app"
        );
    }
    

处理切片中停用滚动操作的情况

切片模板的呈现界面可能不支持在模板内滚动。在这种情况下,某些内容可能不会显示。

举例来说,假设一个切片中显示了一个 WLAN 网络列表:

如果这个 WLAN 列表较长,且停用了滚动操作,您可以添加查看更多按钮,以确保用户可以看到列表中的所有项目。您可以使用 addSeeMoreAction() 添加此按钮,如下例所示:

Kotlin

    fun seeMoreActionSlice(sliceUri: Uri) =
        list(context, sliceUri, ListBuilder.INFINITY) {
            // ...
            setSeeMoreAction(seeAllNetworksPendingIntent)
            // ...
        }
    

Java

    public Slice seeMoreActionSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
        // ...
        listBuilder.setSeeMoreAction(seeAllNetworksPendingIntent);
        // ...
        return listBuilder.build();
    }
    

显示效果如下图所示:

点按查看更多会发送 seeAllNetworksPendingIntent

如果您要提供自定义消息或行,也可以考虑添加一个 RowBuilder:

Kotlin

    fun seeMoreRowSlice(sliceUri: Uri) =
        list(context, sliceUri, ListBuilder.INFINITY) {
            // ...
            seeMoreRow {
                title = "See all available networks"
                addEndItem(
                    IconCompat.createWithResource(context, R.drawable.ic_right_caret), ICON_IMAGE
                )
                primaryAction = SliceAction.create(
                    seeAllNetworksPendingIntent,
                    IconCompat.createWithResource(context, R.drawable.ic_wifi),
                    ListBuilder.ICON_IMAGE,
                    "Wi-Fi Networks"
                )
            }
        }
    

Java

    public Slice seeMoreRowSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
                // ...
                .setSeeMoreRow(new RowBuilder()
                        .setTitle("See all available networks")
                        .addEndItem(IconCompat
                                        .createWithResource(getContext(), R.drawable
                                                .ic_right_caret),
                                ListBuilder.ICON_IMAGE)
                        .setPrimaryAction(SliceAction.create(seeAllNetworksPendingIntent,
                                IconCompat.createWithResource(getContext(), R.drawable.ic_wifi),
                                ListBuilder.ICON_IMAGE,
                                "Wi-Fi Networks"))
                );
        // ...
        return listBuilder.build();
    }
    

只有满足以下条件之一,通过此方法添加的行或操作才会显示出来:

  • 切片的呈现界面已停用视图上的滚动操作
  • 可用空间无法显示所有的行

组合模板

要创建丰富的动态切片,您可以组合使用多个行类型。例如,切片可以包含标题行、带有单张图片的网格以及带有两个文字单元格的网格。

下面的切片包含标题行以及带有三个单元格的网格。