您的应用可能具有多步骤用户任务。例如,您的应用可能需要引导用户购买其他内容、设置复杂的配置设置,或者只是确认决定。所有这些任务都需要引导用户完成一个或多个有序步骤或决定。
Leanback androidx 库
提供了用于实现多步骤用户任务的类。本课程介绍如何使用 GuidedStepSupportFragment
类引导用户完成一系列决定,以完成任务。GuidedStepSupportFragment
利用电视界面最佳做法,不仅便于用户了解多步骤任务,而且方便用户在电视设备上导航。
提供步骤详情
GuidedStepSupportFragment
表示一系列步骤中的单个步骤。界面左侧提供了一个包含步骤信息的引导视图。GuidedStepSupportFragment
的右侧视图包含该步骤可能执行的操作或决定的列表。
对于多步骤任务中的每个步骤,请扩展 GuidedStepSupportFragment
并提供有关用户可以执行的步骤和操作的上下文信息。替换 onCreateGuidance()
并返回新的 GuidanceStylist.Guidance
,其中包含步骤标题、说明和图标等上下文信息。
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = getString(R.string.guidedstep_first_title);
String breadcrumb = getString(R.string.guidedstep_first_breadcrumb);
String description = getString(R.string.guidedstep_first_description);
Drawable icon = getActivity().getDrawable(R.drawable.guidedstep_main_icon_1);
return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
}
通过在 Activity 的 onCreate()
方法中调用 GuidedStepSupportFragment.add()
,将 GuidedStepSupportFragment
子类添加到所需的 Activity 中。如果您的 Activity 仅包含 GuidedStepSupportFragment
对象,请使用 GuidedStepSupportFragment.addAsRoot()
而不是 add()
添加第一个 GuidedStepSupportFragment
。使用 addAsRoot()
可确保:如果用户在查看第一个 GuidedStepSupportFragment
时按下电视遥控器上的“返回”按钮,GuidedStepSupportFragment
和父 Activity 都将关闭。
注意:以编程方式添加 GuidedStepSupportFragment
对象,而不是在布局 XML 文件中添加。
创建和处理用户操作
通过替换 onCreateActions()
添加用户操作。在替换过程中,为每个操作项添加新的 GuidedAction
,并提供操作字符串、说明和 ID。使用 GuidedAction.Builder
添加新操作。
@Override
public void onCreateActions(List actions, Bundle savedInstanceState) {
// Add "Continue" user action for this step
actions.add(new GuidedAction.Builder()
.id(CONTINUE)
.title(getString(R.string.guidedstep_continue))
.description(getString(R.string.guidedstep_letsdoit))
.hasNext(true)
.build());
...
操作不仅限于单行选择。以下是您可以创建的其他类型的操作:
- 通过设置
infoOnly(true)
添加信息标签操作。如果将infoOnly
设置为 true,用户便无法选择该操作。如需提供有关用户选择的其他信息,请使用标签操作。 - 通过设置
editable(true)
添加可修改的文本操作。如果editable
为 true,在选择该操作后,用户可以使用遥控器或所连接的键盘输入文本。替换onGuidedActionEditedAndProceed()
以获取用户输入的修改后的文本。您也可以替换onGuidedActionEditCanceled()
以了解用户何时取消输入。 - 通过使用具有通用 ID 值的
checkSetId()
将操作分为一组,添加一组作为可勾选单选按钮的操作。同一列表中具有相同勾选组 ID 的所有操作均被视为已关联。当用户选择该组中的某项操作时,该操作将变为“已勾选”状态,而所有其他操作均变为“未勾选”状态。 - 通过在
onCreateActions()
中使用GuidedDatePickerAction.Builder
而不是GuidedAction.Builder
,添加日期选择器操作。替换onGuidedActionEditedAndProceed()
以获取用户输入的修改后的日期值。 - 添加一项使用子操作的操作,让用户从扩展选项列表中进行选择。
- 添加一项显示在操作列表右侧且可轻松访问的按钮操作。
您还可以通过设置 hasNext(true)
添加可视指示符,以指示选择该操作会导致新的步骤。
如需响应操作,请替换 onGuidedActionClicked()
并处理传入的 GuidedAction
。通过检查 GuidedAction.getId()
确定所选操作。
添加子操作
某些操作可能需要为用户提供一组额外的选项。GuidedAction
可以指定显示为子操作下拉列表的子操作列表。
子操作列表可以包含常规操作或单选按钮操作,但不包含日期选择器或可修改的文本操作。此外,由于系统不支持多个级别的子操作,因为子操作不能拥有自己的子操作集。深层嵌套的操作集会造成糟糕的用户体验。
如需添加子操作,请先创建并填充将充当子操作的 GuidedActions
列表:
List subActions = new ArrayList();
subActions.add(new GuidedAction.Builder()
.id(SUBACTION1)
.title(getString(R.string.guidedstep_subaction1_title))
.description(getString(R.string.guidedstep_subaction1_desc))
.build());
...
在 onCreateActions()
中,创建一个选中后会显示子操作列表的顶级 GuidedAction
:
@Override
public void onCreateActions(List actions, Bundle savedInstanceState) {
...
actions.add(new GuidedAction.Builder()
.id(SUBACTIONS)
.title(getString(R.string.guidedstep_subactions_title))
.description(getString(R.string.guidedstep_subactions_desc))
.subActions(subActions)
.build());
...
}
最后,通过替换 onSubGuidedActionClicked() 响应子操作选择:
@Override
public boolean onSubGuidedActionClicked(GuidedAction action) {
// Check for which action was clicked, and handle as needed
if (action.getId() == SUBACTION1) {
// Subaction 1 selected
}
// Return true to collapse the subactions drop-down list, or
// false to keep the drop-down list expanded.
return true;
}
添加按钮操作
如果引导步骤包含大量操作,用户可能必须滚动浏览列表以访问最常用的操作。使用按钮操作将常用操作与操作列表分开。按钮操作显示在操作列表的右侧,并且易于导航。
按钮操作的创建和处理方式与常规操作一样,但您需要在
onCreateButtonActions()
而不是 onCreateActions()
中创建按钮操作。响应 onGuidedActionClicked()
中的按钮操作。
将按钮操作用于简单操作,例如步骤之间的导航操作。请勿将日期选择器操作或其他可修改操作作为按钮操作。此外,按钮操作不能包含子操作。
将引导步骤分组到引导序列中
GuidedStepSupportFragment
表示单个步骤,但您可能会按顺序将多个步骤包含在内。使用 GuidedStepSupportFragment.add()
将多个 GuidedStepSupportFragment
对象组合在一起,以将序列中的下一个步骤添加到 Fragment 堆栈中。
@Override
public void onGuidedActionClicked(GuidedAction action) {
FragmentManager fm = getFragmentManager();
if (action.getId() == CONTINUE) {
GuidedStepSupportFragment.add(fm, new SecondStepFragment());
}
...
如果用户按下电视遥控器上的“返回”按钮,设备会在 Fragment 堆栈上显示之前的 GuidedStepSupportFragment
。如果您决定提供自己的 GuidedAction
返回上一步,可以通过调用 getFragmentManager().popBackStack()
实现“返回”行为。如果需要让用户返回序列中更早的步骤,请使用 popBackStackToGuidedStepSupportFragment()
返回 Fragment 堆栈中的特定 GuidedStepSupportFragment
。
用户完成序列中的最后一步后,请使用 finishGuidedStepSupportFragments()
从当前堆栈中移除所有 GuidedStepSupportFragment
并返回原始父 Activity。如果使用 addAsRoot()
添加了第一个 GuidedStepSupportFragment
,那么调用 finishGuidedStepSupportFragments()
也将关闭父 Activity。
自定义步骤呈现
GuidedStepSupportFragment
类可以使用自定义主题背景控制呈现的各个方面,例如标题文本格式或步骤过渡动画。自定义主题背景必须从 Theme_Leanback_GuidedStep
继承,并且可以为 GuidanceStylist
和 GuidedActionsStylist
中定义的属性提供替换值。
如需将自定义主题背景应用于 GuidedStepSupportFragment
,请执行以下任一操作:
- 通过将
android:theme
属性设置为 Android 清单中的 Activity 元素,将主题背景应用于父 Activity。设置该属性会将主题背景应用于所有子视图,并且如果父 Activity 仅包含GuidedStepSupportFragment
对象,那么这是应用自定义主题背景最简单的方法。 - 如果您在属于同一整体多步骤任务的不同 Activity 中使用
GuidedStepSupportFragment
对象,并且希望在所有步骤中使用一致的视觉主题背景,请替换GuidedStepSupportFragment.onProvideTheme()
并返回您的自定义主题背景。 - 如果您的 Activity 已使用一个自定义主题背景,并且您不想将
GuidedStepSupportFragment
样式应用于 Activity 中的其他视图,请将LeanbackGuidedStepTheme_guidedStepTheme
属性添加到现有自定义 Activity 主题背景中。该属性指向仅 Activity 中的GuidedStepSupportFragment
对象使用的自定义主题背景。
GuidedStepSupportFragment
类使用特殊的样式类获取和应用主题背景属性。GuidanceStylist
类使用主题背景信息控制左侧导航视图的呈现,而 GuidedActionsStylist
类使用主题背景信息控制右侧操作视图的呈现。
实战
添加GuidedStepSupportActivity
public class GuidedStepSupportActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_guided_step_support);
GuideStep1SupportFragment.addAsRoot(this, new GuideStep1SupportFragment(), R.id.fl_container);
}
}
activity_guided_step_support.xml
GuideStep1SupportFragment.java
public class GuideStep1SupportFragment extends GuidedStepSupportFragment {
private static final String TAG = GuideStep1SupportFragment.class.getSimpleName();
private final static int ACTION_INFO = 1;
private final static int ACTION_INPUT = 2;
private final static int ACTION_DATE_PICKER = 4;
private final static int ACTION_BUTTON_NEXT = 5;
private final static int ACTION_SUB_OPTION = 3;
private final static int SUB_OPT1 = 1;
private final static int SUB_OPT2 = 2;
private final static int SUB_OPT3 = 3;
private final static int SUB_OPT4 = 4;
private final static int SUB_OPT5 = 5;
private final static int SUB_OPT6 = 6;
private final static int[] subOptions = {SUB_OPT1, SUB_OPT2, SUB_OPT3, SUB_OPT4, SUB_OPT5, SUB_OPT6};
private final static String[] subOptionsTitles = {"SUB_OPT1", "SUB_OPT2", "SUB_OPT3", "SUB_OPT4", "SUB_OPT5", "SUB_OPT6"};
private final static String[] subOptionsDescs = {"SUB_OPT_DESC1", "SUB_OPT_DESC2", "SUB_OPT_DESC3", "SUB_OPT_DESC4", "SUB_OPT_DESC5", "SUB_OPT_DESC6"};
@NonNull
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = "guidedstep_first_title";
String breadcrumb = "guidedstep_first_breadcrumb";
String description = "guidedstep_first_description";
Drawable icon = getActivity().getResources().getDrawable(R.mipmap.ic_launcher);
return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
}
@Override
public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ImageView imageView = new ImageView(getContext());
imageView.setImageResource(R.drawable.bg_for_vod);
return imageView;
}
@Override
public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) {
super.onCreateActions(actions, savedInstanceState);
GuidedAction infoAction = new GuidedAction.Builder(getActivity())
.id(ACTION_INFO)
.title("guidedstep_subactions_title")
.description("guidedstep_subactions_desc")
.infoOnly(true)//
.build();
actions.add(infoAction);
GuidedAction editAction = new GuidedAction.Builder(getActivity())
.id(ACTION_INPUT)
.title("age")//标题
.description("please input your age")//描述
.editable(true)//用户可以使用遥控器或所连接的键盘输入文本
.editInputType(InputType.TYPE_CLASS_NUMBER)//设置输入的文本类型
.editTitle("")//弹出输入框后显示的输入文本内容
.editDescription("please input age")//弹出输入框后的提示文本
.build();
actions.add(editAction);
List subActions = new ArrayList<>();
for (int i = 0; i < subOptions.length; i++) {
GuidedAction guidedAction = new GuidedAction.Builder(getActivity())
.id(subOptions[i])
.title(subOptionsTitles[i])
.description(subOptionsDescs[i])
.checkSetId(3)//设置checkSetId实现单选操作,如果不设置不会出现RadioButton的样式
.build();
subActions.add(guidedAction);
}
GuidedAction subOptionAction = new GuidedAction.Builder(getActivity())
.id(ACTION_SUB_OPTION)
.title("choose option")
.description("click here to choose sub option")
.subActions(subActions)
.build();
actions.add(subOptionAction);
//选择日期
GuidedDatePickerAction datePickAction = new GuidedDatePickerAction.Builder(getActivity())
.id(ACTION_DATE_PICKER)
.title("choose date")
.description("click here to choose date")
.build();
actions.add(datePickAction);
}
@Override
public void onCreateButtonActions(@NonNull List actions, Bundle savedInstanceState) {
super.onCreateButtonActions(actions, savedInstanceState);
GuidedAction nextAction = new GuidedAction.Builder(getActivity())
.id(ACTION_BUTTON_NEXT)
.title("Next")
.build();
actions.add(nextAction);
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
super.onGuidedActionClicked(action);
if (action.getId() == ACTION_BUTTON_NEXT) {
Toast.makeText(getActivity(), "ACTION_BUTTON_NEXT", Toast.LENGTH_SHORT).show();
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
add(getFragmentManager(), new GuideStep2SupportFragment(), R.id.fl_container);
}
}
}
@Override
public long onGuidedActionEditedAndProceed(GuidedAction action) {
if (action.getId() == ACTION_INPUT) {
//当用户输入模式下按键盘的"Next"、"Done"和"Enter"时会关闭键盘并回调到该方法
action.setTitle(action.getEditTitle());//用EditTitle替换Title,显示用户输入的内容
} else if (action.getId() == ACTION_DATE_PICKER) {
if (action instanceof GuidedDatePickerAction) {
//用户选择完日期按"Center"键时会回调到该方法
long date = ((GuidedDatePickerAction) action).getDate();
Log.d(TAG, "date:" + date);
Toast.makeText(getContext(), "Date: " + date, Toast.LENGTH_SHORT).show();
}
}
return super.onGuidedActionEditedAndProceed(action);
}
@Override
public void onGuidedActionEditCanceled(GuidedAction action) {
super.onGuidedActionEditCanceled(action);
if (action.getId() == ACTION_INPUT) {
//当输入输入模式下按返回隐藏键盘时会回调到该方法,用EditTitle替换Title,显示用户输入的内容
action.setTitle(action.getEditTitle());
}
}
@Override
public boolean onSubGuidedActionClicked(GuidedAction action) {
//这里增加子按钮被点击的监听事件,根据action的id做出相应的处理
if (action.getId() == SUB_OPT1) {
Toast.makeText(getContext(), subOptionsTitles[SUB_OPT1], Toast.LENGTH_SHORT).show();
} else if (action.getId() == SUB_OPT2) {
Toast.makeText(getContext(), subOptionsTitles[SUB_OPT2], Toast.LENGTH_SHORT).show();
}
return super.onSubGuidedActionClicked(action);
}
}
GuideStep2SupportFragment.java
public class GuideStep2SupportFragment extends GuidedStepSupportFragment {
private final static int ACTION_STEP_1 = 1;
private final static int ACTION_DONE = 2;
@NonNull
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
String title = "guidedstep_second_title";
String breadcrumb = "guidedstep_second_breadcrumb";
String description = "guidedstep_second_description";
Drawable icon = getActivity().getResources().getDrawable(R.mipmap.ic_launcher);
return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
}
@Override
public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ImageView imageView = new ImageView(getContext());
imageView.setImageResource(R.drawable.bg_for_vod);
return imageView;
}
@Override
public void onCreateActions(@NonNull List actions, Bundle savedInstanceState) {
super.onCreateActions(actions, savedInstanceState);
GuidedAction back2Step1Action = new GuidedAction.Builder(getActivity())
.id(ACTION_STEP_1)
.title("Back to Step 1")
.build();
actions.add(back2Step1Action);
GuidedAction doneAction = new GuidedAction.Builder(getActivity())
.id(ACTION_DONE)
.title("Done")
.build();
actions.add(doneAction);
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
super.onGuidedActionClicked(action);
if (action.getId() == ACTION_STEP_1) {
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
fragmentManager.popBackStack();//返回第一步
}
} else if (action.getId() == ACTION_DONE) {
//移除所有GuidedStepSupportFragment,
// 如果是addAsRoot添加第一个GuidedStepSupportFragment,则会关闭Activity;
//否则可以调用Activity的finish方法
finishGuidedStepSupportFragments();
}
}
}
因为点击GuidedAction进入编辑模式,但是按返回键隐藏键盘的同时也会退出编辑模式,导致无法移动光标修改内容。要想正常的添加输入操作,你必须保证键盘具有"左右"按键能够切换游标,如果键盘不具有左右切换游标的功能,不建议在引导步骤中增加输入操作。
相信根据理论知识加上实战的代码,相信您已经知道了如何利用Leanback为你的Android TV应用添加"引导步骤"啦!