大多数基于卡片的插件都是使用多张卡片构建的,这些卡片代表着插件界面的不同“页面”。为了获得有效的用户体验,您应该在插件中的卡片之间使用简单自然的导航。
最初在 Gmail 插件中,界面的不同卡片之间的转换是通过将卡片推入和弹出单个卡片堆栈(使用 Gmail 显示的堆栈的顶部卡片)来处理的。
Google Workspace 插件引入了首页和非上下文卡片。为了适应内容相关卡片和非上下文卡片,Google Workspace 每个插件都有一个内部卡片堆栈。在主机中打开插件时,会触发相应的 homepageTrigger
在堆栈上创建第一张首页卡片(下图中的深蓝色“首页”卡片)。如果未定义 homepageTrigger
,则系统会创建、显示默认卡片,并将其推送到非上下文堆栈。第一张卡是根卡。
当用户浏览您的插件时,您的插件可以创建其他非上下文的卡片,并将它们推送到堆栈(图中的蓝色“推送的卡片”)中。插件界面会显示堆栈中的顶部卡片,因此将新卡片推送到堆栈会更改显示界面,从堆栈中弹出卡片将返回屏幕来显示之前显示的卡片。
如果您的插件定义了上下文触发器,那么当用户输入该上下文时,触发器就会触发。触发器函数构建上下文卡片,但界面显示会根据新卡片的 DisplayStyle
进行更新:
- 如果
DisplayStyle
为REPLACE
(默认),上下文卡(图中的深橙色“上下文”卡)会替换当前显示的卡。这样可以有效地在非上下文卡堆栈之上启动新的上下文卡堆栈,并且此上下文卡是上下文堆栈的根卡。 - 如果
DisplayStyle
为PEEK
,界面会创建一个显示在插件边栏底部的窥视标题,叠加在当前卡片上。滑出式标题会显示新卡片的标题,并为用户提供按钮控件,以便用户决定是否查看新卡片。如果他们点击 View 按钮,则该卡片会取代当前卡片(如上所述,如REPLACE
所示)。
您可以创建其他上下文卡,并将其推送到堆栈(图中的黄色“推送的卡片”)。更新卡片堆栈会更改插件界面,以显示最顶层的卡片。如果用户离开某个上下文,系统会移除堆栈中的上下文卡片,并将屏幕更新为最关联的非上下文卡片或首页。
如果用户输入的插件并未指定上下文触发器,则系统不会创建任何新卡片,并且仍会显示当前卡片。
下文所述的 Navigation
操作仅对来自同一上下文的卡片执行操作;例如,上下文卡片内的 popToRoot()
仅会弹出所有其他上下文卡片,不会影响首页卡片。
相比之下,
按钮始终可供用户从上下文卡片导航到非上下文卡片。导航方法
您可以通过在卡片堆栈中添加或移除卡片,在卡片之间创建过渡效果。Navigation
类提供了从堆栈推送和弹出卡片的函数。如需构建有效的卡片导航,请将 widget 配置为使用导航操作。您可以同时推送或弹出多张卡片,但无法移除在插件启动时首次推送到堆栈的初始首页卡片。
如需导航到新卡片来响应用户与微件的互动,请按以下步骤操作:
- 创建一个
Action
对象,并将其与您定义的回调函数相关联。 - 调用 widget 的相应 widget 处理程序函数,以便在该 widget 上设置
Action
。 - 实现执行导航的回调函数。此函数被赋予了操作事件对象作为参数,并且必须执行以下操作:
- 创建一个
Navigation
对象来定义卡片更改。一个Navigation
对象可以包含多个导航步骤,这些步骤按照添加到对象的顺序进行。 - 使用
ActionResponseBuilder
类和Navigation
对象构建ActionResponse
对象。 - 返回构建的
ActionResponse
。
- 创建一个
构建导航控件时,您可以使用以下 Navigation
对象函数:
功能 | 说明 |
---|---|
Navigation.pushCard(Card) |
将卡推送到当前堆栈。您需要先完全构建卡。 |
Navigation.popCard() |
从堆栈顶部移除一张卡片。相当于点击插件标题行中的返回箭头。此操作不会移除根卡。 |
Navigation.popToRoot() |
从堆栈中移除除根卡以外的所有卡片。这基本上会重置该卡片堆栈。 |
Navigation.popToNamedCard(String) |
从堆栈中弹出卡,直至到达具有指定名称或堆栈根卡的卡片。您可以使用 CardBuilder.setName(String) 函数为卡片指定名称。 |
Navigation.updateCard(Card) |
就地替换当前卡片,刷新界面中显示的卡片。 |
导航最佳做法
如果用户互动或事件应导致在同一环境中重新渲染卡片,请使用 Navigation.pushCard()
、Navigation.popCard()
和 Navigation.updateCard()
方法替换现有卡片。如果用户互动或事件应导致在不同的上下文中重新渲染卡片,请使用 ActionResponseBuilder.setStateChanged()
强制在这些环境中重新执行插件。
以下是导航示例:
- 如果互动或事件更改了当前卡片的状态(例如,将任务添加到任务列表),请使用
updateCard()
。 - 如果互动或事件提供更多详细信息或提示用户执行进一步操作(例如,点击某一项的标题可查看更多详情,或按下按钮创建新的日历活动),请使用
pushCard()
显示新页面,同时允许用户使用返回按钮退出新页面。 - 如果互动或事件更新了上一张卡片中的状态(例如,从详细信息视图中更新项的标题),请使用
popCard()
、popCard()
、pushCard(previous)
和pushCard(current)
等元素更新上一张卡片和当前卡片。
正在刷新卡片
Google Workspace 插件可让用户通过重新运行清单中注册的 Apps 脚本触发器函数来刷新卡。用户通过插件菜单项触发此刷新:
该操作会自动添加到由 homepageTrigger
或 contextualTrigger
触发器函数生成的卡片中,如插件的清单文件(“上下文”和非上下文卡堆栈的“根”)中所指定。
退回多张卡
首页或上下文触发器函数用于构建并返回单个 Card
对象或一组由应用界面显示的 Card
对象。
如果只有一张卡,它会以根卡的形式添加到非上下文堆栈或上下文堆栈中,并且托管应用界面会显示该卡。
如果返回的数组包含多个已构建的 Card
对象,则宿主应用会显示一张新卡片,其中包含每个卡片的标头列表。当用户点击其中任何一个标题时,界面都会显示相应的卡片。
当用户从列表中选择一张卡时,该卡将被推送到当前堆栈,并且托管应用会显示该卡。
按钮可将用户返回到卡片标题列表。如果您的插件不需要在您创建的卡片之间转换,那么这种“平面”卡片排列方式就没有问题。但在大多数情况下,更好的做法是直接定义卡片转换,并让首页和上下文触发器函数返回单个卡片对象。
示例
下例展示了如何构建多张带有导航按钮的导航卡。通过在特定的上下文内部或之外推送 createNavigationCard()
返回的卡,可以将这些卡添加到上下文堆栈或非上下文堆栈。
/**
* Create the top-level card, with buttons leading to each of three
* 'children' cards, as well as buttons to backtrack and return to the
* root card of the stack.
* @return {Card}
*/
function createNavigationCard() {
// Create a button set with actions to navigate to 3 different
// 'children' cards.
var buttonSet = CardService.newButtonSet();
for(var i = 1; i <= 3; i++) {
buttonSet.addButton(createToCardButton(i));
}
// Build the card with all the buttons (two rows)
var card = CardService.newCardBuilder()
.setHeader(CardService.newCardHeader().setTitle('Navigation'))
.addSection(CardService.newCardSection()
.addWidget(buttonSet)
.addWidget(buildPreviousAndRootButtonSet()));
return card.build();
}
/**
* Create a button that navigates to the specified child card.
* @return {TextButton}
*/
function createToCardButton(id) {
var action = CardService.newAction()
.setFunctionName('gotoChildCard')
.setParameters({'id': id.toString()});
var button = CardService.newTextButton()
.setText('Card ' + id)
.setOnClickAction(action);
return button;
}
/**
* Create a ButtonSet with two buttons: one that backtracks to the
* last card and another that returns to the original (root) card.
* @return {ButtonSet}
*/
function buildPreviousAndRootButtonSet() {
var previousButton = CardService.newTextButton()
.setText('Back')
.setOnClickAction(CardService.newAction()
.setFunctionName('gotoPreviousCard'));
var toRootButton = CardService.newTextButton()
.setText('To Root')
.setOnClickAction(CardService.newAction()
.setFunctionName('gotoRootCard'));
// Return a new ButtonSet containing these two buttons.
return CardService.newButtonSet()
.addButton(previousButton)
.addButton(toRootButton);
}
/**
* Create a child card, with buttons leading to each of the other
* child cards, and then navigate to it.
* @param {Object} e object containing the id of the card to build.
* @return {ActionResponse}
*/
function gotoChildCard(e) {
var id = parseInt(e.parameters.id); // Current card ID
var id2 = (id==3) ? 1 : id + 1; // 2nd card ID
var id3 = (id==1) ? 3 : id - 1; // 3rd card ID
var title = 'CARD ' + id;
// Create buttons that go to the other two child cards.
var buttonSet = CardService.newButtonSet()
.addButton(createToCardButton(id2))
.addButton(createToCardButton(id3));
// Build the child card.
var card = CardService.newCardBuilder()
.setHeader(CardService.newCardHeader().setTitle(title))
.addSection(CardService.newCardSection()
.addWidget(buttonSet)
.addWidget(buildPreviousAndRootButtonSet()))
.build();
// Create a Navigation object to push the card onto the stack.
// Return a built ActionResponse that uses the navigation object.
var nav = CardService.newNavigation().pushCard(card);
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
/**
* Pop a card from the stack.
* @return {ActionResponse}
*/
function gotoPreviousCard() {
var nav = CardService.newNavigation().popCard();
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
/**
* Return to the initial add-on card.
* @return {ActionResponse}
*/
function gotoRootCard() {
var nav = CardService.newNavigation().popToRoot();
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}