如果正在为亚马逊Fire TV构建应用,使用遥控器实现应用用户界面的导航和交互是不错的做法。这可以通过多种方式实现,但关键概念是将应用内的移动和交互控制映射到方向键和Fire TV遥控器上的按钮。关于用户体验最佳实践的Fire TV文档说明了应用导航和输入的基本方面。
随着Fire TV向汽车领域的扩展,现在除了方向键导航之外将触摸交互添加到Fire TV应用也非常重要。客户既可以使用遥控器,也可用触摸方式操控这些设备。
在本教程中,我们将了解如何修改为方向键设计的基于Android的Fire TV应用,以添加触摸交互,以及如何提供良好的基于触摸的用户体验。
教程将涵盖5个关键步骤:
示例应用 - GitHub存储库:
在跟随本教程步骤操作时,您还可以从GitHub下载或克隆Fire TV Sample App – Touch and D-Pad(Fire TV示例应用 - 触摸板和方向键),网址为:https://github.com/amzn/firetv-sample-touch-app
这个示例应用是本教程的配套应用,因为它集成了下面显示的所有代码,并可让您快速查看它的实际运用。然后,您也可以在编辑或创建应用时将其用作初始参考。
步骤1:了解使用方向键的用户界面导航和焦点
基于Android的Fire TV应用在基于遥控器的导航方面应遵循清晰的平台级模式。由于Fire OS以Android为基础构建,因此它遵循与标准Android应用相同的布局和设计模式。
为了将应用用户界面导航自动映射到遥控器方向键,并指定客户应按何种顺序导航不同的Android视图,我们需要使用Android的方向导航(请参阅此处的Android文档)。这是一种最佳实践,如果您的Android应用不遵循此模式,则应用以下更改非常重要,因为这会影响触摸行为与方向键行为的连接方式。
方向导航要求我们为每个“可设定焦点的”视图指定需要选择的下一个或上一个视图。这样,用户按下遥控器方向键上的导航按钮(上、下、左、右)后,系统可以自动映射将会聚焦的下一个视图。
这是通过在布局XML文件视图中添加以下标记来实现的:
android:nextFocusDown, android:nextFocusRight, android:nextFocusLeft, android:nextFocusUp
接着,添加我们希望应用根据顺序接下来选择的视图ID。
例如:
<TextView android:id="@+id/Item1"
android:nextFocusRight="@+id/Item2"/>
步骤2:对电视应用布局应用方向导航
在我们将触摸交互应用于Fire TV应用之前,我们需要确保在方向键和触摸导航之间创建一致的方向导航体验。
这对于基本应用界面来说非常简单,但媒体和娱乐电视应用界面可能非常复杂,因为它们通常需要显示动态内容。因此,大多数视图可能是在运行时生成的。
为了实现这一点,大多数开发者使用可以轻松保存动态内容的Android视图。对此,RecyclerView就是一个很好的例子。可使用RecyclerView组件从适配器解析动态内容。RecyclerView非常高效,它采用标准的Android模式之一:ViewHolder。
然而,由于RecyclerView的内容是动态的,我们需要确保在RecyclerView内部生成的视图之间的导航正确无误。
为了演示这一点,我们创建了一个简单的应用,模拟电视接口的标准实现。此应用有两个主要的用户界面组件:
在左侧你可以看到黑色的menuLayout,在右侧您可以看到灰色的rowsLayout。
虽然出于本教程的目的,这显得过于简化,因为您应用的视图可能有更复杂的嵌套,但这代表了动态媒体/电视应用用户界面的框架。
您现在要做的是定义方向导航在此布局上的工作方式。
我们要确保的第一件事是,我们可以实际从类别菜单移动到内容行。因此,我们要做的是将nextFocusRight设置为LinearLayout的第一行RecyclerView:
<LinearLayout
android:id="@+id/menuLayout"
[...]
android:nextFocusRight="@id/rowRecyclerView1">
这样一来,用户点击向右按钮后,它会自动将焦点移动到右侧第一个RecyclerView。
我们需要做的另一件事是设置RecyclerView本身项目之间导航的工作方式。由于RecyclerView的视图是在运行时动态创建的,因此手动设置每个视图的导航方向不可行;无论如何,使用XML布局都无法做到这点。为了做到这点,我们需要在RecyclerView上使用一个非常明确的标签,名为descendantFocusability:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants" />
通过将descendantFocusability设置为afterDescendants,可以确保一旦动态生成了视图,RecyclerView本身会自动为RecyclerView内部的项目提供焦点(在本例中,它为RecyclerView菜单中定义的目录提供焦点)。
注意 - 将此应用于我们所有的RecyclerViews非常重要,因为这会自动定义项目之间的关系。这样的优点在于,我们不必手动定义每个项目之间的关系,因为Android会自动将其作为框架级别的功能来处理。
我们还需要将此应用于右侧布局中的所有RecyclerView。我们需要定义每个RecyclerView之间的方向导航(为了简单起见,在此示例中,我们通过4个专用的RecyclerView定义了4行)
最后,我们的RecyclerView应如下所示:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/rowRecyclerView2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/recyclerView3" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/rowRecyclerView4" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"/>
请注意最后一个RecyclerView,即rowRecyclerView4,它没有下个向下聚焦目标。原因是没有其他可导航到其上的RecyclerView。
一旦我们完成了这一步,我们就有了一个使用方向键的完全可导航用户界面。现在,我们可以通过修改RecyclerView的内容来看看如何让我们的界面支持触摸功能。
您会看到如何完全使用方向键来导航的用户界面,即使视图为动态生成也可以如此。
步骤3:为点击添加触摸交互:OnClickListeners
下一步是添加用户点击项目时的触摸交互。对我们而言幸运的是,Android是面向触摸操控而设计的。要在我们的应用用户界面中添加点击操作,我们可以使用标准的Android组件,这些组件可让我们能够涵盖方向键交互和触摸交互。
在视图上实现点击操作或触摸操作的最佳和最简单方法是使用标准的Android OnClickListener。OnClickListener允许在视图上执行触摸点击和方向键按钮点击。OnClickListener触发一个名为onClick()的方法,您可以在其中执行任何所需的操作。
注意 - 如果您已经以任何其他方式在您的或基于方向键的用户界面上实现了点击操作,那么您可能需要在您的任何自定义实现之上添加OnClickListener。这样可以确保方向键点击和触摸点击都能执行所需的操作。
虽然在方向键风格的导航中,视图的大小并不重要,但它对触摸交互影响很大。因此,最好在我们的用户界面中使用更大的视图和区域,以提供良好的用户体验。
在我们创建的简单应用中,我们会将OnClickListener应用于布局本身,而不是布局内部的视图。 要实现这点,也可以扩展内部视图来填充整个布局区域,并将OnClickListener应用于TextView或ImageView等单个视图,但将其应用于整个布局是一个简单的解决方案,可以实现我们的目标,完全不需要更改用户界面的外观。
视图是动态的,由RecyclerViews创建,因此我们需要将单独的点击侦听器应用于每个RecyclerView的已创建元素。我们实现这点的方法是,修改RecyclerView适配器的代码,获得对RecyclerView每个单独项目布局的引用,并在适配器的onBindViewHolder()方法中应用onClickListener:
public class MenuItemsAdapter extends RecyclerView.Adapter<MenuItemsAdapter.ViewHolder> {
private String[] localDataSet;
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
private final ConstraintLayout menuConstraintLayout;
public ViewHolder(View view) {
super(view);
// Define click listener for the ViewHolder's View
textView = (TextView) view.findViewById(R.id.textView);
menuConstraintLayout = view.findViewById(R.id.menuconstraintLayout);
}
public TextView getTextView() {
return textView;
}
public ConstraintLayout getMenuConstraintLayout() {
return menuConstraintLayout;
}
}
/**
* Initialize the dataset of the Adapter.
*
* @param dataSet String[] containing the data to populate views to be used
* by RecyclerView.
*/
public MenuItemsAdapter(String[] dataSet) {
localDataSet = dataSet;
}
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view, which defines the UI of the list item
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.menulayout, viewGroup, false);
return new ViewHolder(view);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
viewHolder.getTextView().setText(localDataSet[position]);
viewHolder.getMenuConstraintLayout().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//In this sample app we are just logging the Click,
//but here you could for example open a new Activity or select
//a specific Category
Log.e("Click ", "Clicked "+localDataSet[position]);
}
});
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return localDataSet.length;
}
}
为了显示某个项目是否被聚焦或点击,很重要的一点是使用包含不同状态的背景和绘图,特定视图可以通过这些状态对自身进行标识。
这很容易实现,只需使用一个选择器绘图,该绘图可以包含多种状态,比如已聚焦和已按下(已点击)。
Menuselectordrawable.xml(用作菜单布局的背景)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@android:color/holo_blue_bright" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@android:color/holo_orange_light" /> <!-- focused -->
<item android:state_hovered="true"
android:drawable="@android:color/holo_green_light" /> <!-- hovered -->
<item android:drawable="@android:color/background_dark" /> <!-- default -->
</selector>
在menulayout.xml中
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/menuconstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/menuselectordrawable"
android:focusable="true"
android:focusableInTouchMode="true">
此时,所有用户界面视图都可以点击,并且在点击(蓝色)或聚焦(橙色)后会显示不同的背景颜色。
可以看到通过遥控器正确触发onClickListeners。
步骤4: 管理方向键和触摸交互之间的视图焦点
下一步是在方向键和触摸交互之间建立一致的体验。这意味着,不管我们使用遥控器还是触摸方式进行交互,要确保方向导航以一致的方式工作。
正如我们上面提到的,Android在构建时就考虑了触摸屏和触摸交互。 因此管理我们的应用用户界面和视图的底层基本上已经支持触摸。
默认情况下,Android中的大多数视图都是可见并可设定焦点的。实际上,视图继承了一个名为focusable的参数,默认情况下,该参数设置为“auto”,意味着由平台本身决定该视图是否应可设定焦点。按钮、文本视图、编辑文本等视图默认情况下是可设定焦点的,因为它们是主要的用户界面组件,而布局和布局扩展元素通常默认情况下不可自动设定焦点,因为它们通常仅用于定义用户界面结构。
为了使我们的应用完全支持触摸,我们需要确保应用中最重要的视图是可设定焦点的,而且还需要确保当客户使用触摸时,可以聚焦它们。
因此,我们将在视图中编辑两个参数:focusable和focusableInTouchMode。
回到我们的示例应用,我们创建了两个新的独立布局,用于在Categories(类别)RecyclerView和“rows”(行)RecyclerView中填充各个项目。
我们需要确保:
实现这点的方法是将focusable和focusableInTouchMode设置为true。
menuLayout.xml(定义左侧Categories的单个内容,其中仅包含一个TextView)
<androidx.constraintlayout.widget.ConstraintLayout
[...]
android:id="@+id/menuconstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/menuselectordrawable"
android:focusable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
[...] />
cardLayout.xml(定义右侧Movies(电影)行的各项内容。每个卡片都包含ImageView和TextView。)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
[...]
android:id="@+id/cardconstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/selectordrawable"
android:focusable="true"
android:focusableInTouchMode="true"
>
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="100dp"
[...] />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
[...] />
</androidx.constraintlayout.widget.ConstraintLayout>
通过这样操作,我们确保当用户触摸用户界面或用方向键控制器导航时,在这两种情况下,都会聚焦正确的用户界面元素。请参考下面的短片,观看相关演示。
在此短片中,您可以看到如何使用触摸交互正确点击和聚焦视图。
步骤5: 其他最佳实践并在Fire TV上测试触摸功能
完成上述步骤后,我们将成功让应用用户界面的最重要组件支持触摸。
以下额外提供了一些简单步骤,可确保在触摸导航和方向键导航这两方面都能提供良好的用户体验:
如何在没有触摸屏的Fire TV设备上测试触摸功能?
最简单的解决方案是将无线鼠标连接到Fire TV。在Android上用鼠标模拟触摸交互。您可通过以下步骤进行该操作:
下载并测试针对触摸和方向键的Fire TV示例应用
请记得下载Fire TV Sample App – Touch and D-pad,网址为https://github.com/amzn/firetv-sample-touch-app。 该应用包括您在上面看到的所有代码,是优化基于Android的Fire TV应用的一个绝佳着手点。
结论
本教程为您提供了关于如何让Fire TV应用支持触摸的初步实用概述。
有关这些内容的更多详情,请查看我们的文档。