本文档详细介绍了如何使用 Android Jetpack 中的模板构建器构造 Slice。
定义 Slice 模板
Slice 是使用 ListBuilder
构造的。利用 ListBuilder,您可以添加显示在列表中的不同类型的行。本部分介绍了每种行类型及其构造方式。
SliceAction
Slice 模板的最基本元素是 SliceAction
。SliceAction
包含一个标签以及一个 PendingIntent
,它是以下某一项:
- 图标按钮
- 默认切换开关
- 自定义切换开关(一个具有开启/关闭状态的可绘制对象)
SliceAction
由模板构建器使用,详见本部分余下篇幅。您可以为 SliceAction
定义一种图片模式,该模式决定了如何为操作呈现图片:
ICON_IMAGE
:超小尺寸,可着色SMALL_IMAGE
:小尺寸,不可着色LARGE_IMAGE
:最大尺寸,不可着色
HeaderBuilder
在大多数情况下,您应使用 HeaderBuilder
为模板设置标头。标头可以支持以下几项:
- 标题
- 副标题
- 摘要副标题
- 主要操作
一些标头配置示例如下图所示。请注意,灰色框表示可能的图标和内边距位置:
在不同界面上呈现的标头
当需要 Slice 时,显示界面会决定如何呈现 Slice。请注意,Slice 在不同托管界面上的呈现方式可能会略有不同。
采用较小的格式时,通常只会显示标头(如果存在)。如果您为标头指定了摘要,则会显示摘要文字,而不是副标题文字。
如果您尚未在模板中指定标头,则通常会显示添加到 ListBuilder
的第一行。
HeaderBuilder 示例 - 包含标头的简单列表 Slice
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
)
}
}
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
Slice 标头也可以显示 SliceAction:
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)
}
}
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、图标或时间戳
- 主要操作
您可以通过多种方式组合行内容,但须遵守以下限制:
- 起始项不能显示在 Slice 的第一行中
- 结束项不能同时包含
SliceAction
对象和Icon
对象 - 一行只能包含一个时间戳
内容行示例如下图所示。请注意,灰色框表示可能的图标和内边距位置:
RowBuilder 示例 - Wi-Fi 切换开关
以下示例演示的行包含一项主要操作和一个默认切换开关。
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)
}
}
}
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 示例 - 附近的餐厅
以下示例演示的网格行包含图片和文字。
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
}
}
}
}
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
构建包含音量滑块的 Slice。如需构造进度行,请使用 addRange()
。
fun createSliceWithRange(sliceUri: Uri): Slice {
return list(context, sliceUri, ListBuilder.INFINITY) {
inputRange {
title = "Ring Volume"
inputAction = volumeChangedPendingIntent
max = 100
value = 30
}
}
}
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()
返回 Slice。耗时的调用可能会导致显示问题,如闪烁和突然调整大小。
如果您的 Slice 内容无法快速加载,您可以使用占位符内容构造 Slice,同时在构建器中注明内容正在加载。一旦内容可供显示,请使用 Slice URI 调用 getContentResolver().notifyChange(sliceUri, null)
。此时会再次调用 SliceProvider.onBindSlice()
,让您可以使用新内容重新构造 Slice。
延迟的内容示例 - 骑车上班
在下面的“骑车上班”行中,上班距离是动态确定的,可能不会立即显示。示例代码演示了在内容加载时使用 null 副标题作为占位符:
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)
}
}
}
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"
);
}
处理 Slice 中停用滚动操作的情况
Slice 模板的呈现界面可能不支持在模板内滚动。在这种情况下,某些内容可能不会显示。
举例来说,假设一个 Slice 中显示了一个 Wi-Fi 网络列表:
如果这个 Wi-Fi 列表较长,且停用了滚动操作,您可以添加查看更多按钮,以确保用户可以看到列表中的所有项目。您可以使用 addSeeMoreAction()
添加此按钮,如下例所示:
fun seeMoreActionSlice(sliceUri: Uri) =
list(context, sliceUri, ListBuilder.INFINITY) {
// [START_EXCLUDE]
// [END_EXCLUDE]
setSeeMoreAction(seeAllNetworksPendingIntent)
// [START_EXCLUDE]
// [END_EXCLUDE]
}
public Slice seeMoreActionSlice(Uri sliceUri) {
if (getContext() == null) {
return null;
}
ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
// [START_EXCLUDE]
listBuilder.addRow(new RowBuilder()
.setTitle("Hello")
.setPrimaryAction(createActivityAction())
);
// [END_EXCLUDE]
listBuilder.setSeeMoreAction(seeAllNetworksPendingIntent);
// [START_EXCLUDE]
// [END_EXCLUDE]
return listBuilder.build();
}
显示效果如下图所示:
点按查看更多会发送 seeAllNetworksPendingIntent
。
或者,如果您希望提供自定义消息或行,不妨考虑添加一个 RowBuilder:
fun seeMoreRowSlice(sliceUri: Uri) =
list(context, sliceUri, ListBuilder.INFINITY) {
// [START_EXCLUDE]
// [END_EXCLUDE]
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"
)
}
}
public Slice seeMoreRowSlice(Uri sliceUri) {
if (getContext() == null) {
return null;
}
ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
// [START_EXCLUDE]
.addRow(new RowBuilder()
.setTitle("Hello")
.setPrimaryAction(createActivityAction())
)
// [END_EXCLUDE]
.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"))
);
// [START_EXCLUDE]
// [END_EXCLUDE]
return listBuilder.build();
}
只有满足以下条件之一,通过此方法添加的行或操作才会显示出来:
- Slice 的呈现界面已停用视图上的滚动操作
- 可用空间无法显示所有的行
组合模板
您可以将多种行类型组合在一起,创建内容丰富的动态 Slice。例如,Slice 可以包含标头行、带有单张图片的网格以及带有两个文字单元格的网格。
下面的 Slice 包含标头行以及带有三个单元格的网格。