为Fire TV添加触控
除了将Amazon Fire TV应用设置为与遥控器和方向键配合使用外,现在还可以设置触控交互。鉴于Fire TV正在向汽车领域的应用扩展,这个备选方案的重要性与日俱增。本教程将为您介绍如何修改旨在用于遥控器和方向键的Fire TV应用,添加触控功能,以及如何提供基于触控的良好用户体验。有关更多信息,请参阅Automotive UX指南。
- 先决条件: 使用方向导航将UI导航映射到遥控器方向键
- 步骤1: 在动态电视UI中应用方向导航
- 步骤2: 使用OnClickListener添加触控功能
- 步骤3: 管理方向键和触控之间的视图焦点
- 步骤4: 在Fire TV上测试触控功能
- 最佳实践
- 下载示例应用
- 资源
建议下载我们的示例应用(Fire TV Sample App – Touch and D-pad),它集成了下面所有代码,您可以快速查看运行效果。这是创建或编辑应用的良好着手点。
先决条件: 使用方向导航将UI导航映射到遥控器方向键
基于Android的Fire TV应用在基于遥控器的导航方面应遵循清晰的平台级模式。由于Fire OS以Android为基础构建,因此它遵循与Android应用相同的布局和设计模式。
要将应用UI导航自动映射到遥控器方向键,并为客户指定Android视图的顺序,我们需要使用Android的方向导航(请参阅此处的Android文档),这是实施Android应用布局时的一般最佳实践,并且会影响触控行为与方向键行为的关联方式。
方向导航要求为每个“可设定焦点”视图指定要选择的上一个和下一个视图。这样,用户按下遥控器方向键上的导航按钮(上、下、左、右)后,系统可以自动将焦点映射到下一个视图。
可以通过在XML布局文件中添加以下属性来实现这点:
android:nextFocusDown、android:nextFocusRight、android:nextFocusLeft、android:nextFocusUp
然后,添加您希望应用根据顺序接下来选择的视图ID。例如:
<TextView android:id="@+id/Item1"
android:nextFocusRight="@+id/Item2"/>
前一段代码可在客户按下方向键上的“向右”按钮后,让TextView (Item1
)移动到视图(Item2
)。所有导航方向均采用此格式。
步骤1: 在动态电视UI中应用方向导航
为Fire TV应用触控功能前,需要在方向键和触控导航之间建立一致的方向导航。
虽然这对于基本应用界面来说可能很简单,但媒体和娱乐电视应用界面可能会相当复杂。它们通常会显示动态内容,需要运行时生成。
这就是大多数开发者使用Android视图来容纳动态内容的原因。RecyclerView
就是一个很好的例子。可使用RecyclerView
组件从适配器解析动态内容。RecyclerView
非常高效,它采用标准的Android模式之一:ViewHolder
。
RecyclerView
的内容是动态的,因此请确保正确生成各个RecyclerView
之间的导航。
下面是上文提到的示例应用的UI。它模拟了电视界面的标准实现,有两个主要的UI组件:
- 一个
LinearLayout
名为“menuLayout”,包含一个名为“recyclerViewMenu”的RecyclerView
,其本身包含了囊括所有类别的左侧菜单。 - 第二个
LinearLayout
名为“rowsLayout”,包含其他RecyclerView
,其中包含了可以播放的所有电影和内容。
您的应用可能具有更复杂的视图嵌套,但这代表了动态媒体/电视应用UI的框架。
重要须知: 如果使用可滚动组件(例如ScrollView
)构建布局,则会启用滚动。
我们来定义方向导航在布局中的工作方式。首先,从类别菜单移到内容行。为此,请将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
菜单中定义的类别上。
同样,将其应用于右侧布局上的每个RecyclerView
。然后定义各个RecyclerView
之间的方向导航。为简化流程,在这里使用4个专用RecyclerView
定义了4行。
RecyclerView
可定义项目之间的关系。由于Android负责在框架层面上为我们定义关系,我们不必手动定义关系。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"/>
请注意,rowRecyclerView4没有nextFocusDown
目标。这是因为这是最后一个RecyclerView
,没有更多的RecyclerView
可供浏览。
到目前为止,就有了一个可以完全靠使用方向键来导航的UI。现在,我们可以通过修改RecyclerView
的内容来让界面支持触控功能。
步骤2: 使用OnClickListener添加触控功能
为Android电视应用添加触控功能实际上相当容易,因为Android在构建时考虑到了触控交互。要将其添加到应用UI中,可以使用可实现方向键交互和触控交互这两者的标准Android组件。
最佳实践是使用OnClickListener
在视图上实现点击或触控操作。OnClickListener
会触发名为onClick()
的方法,让您可以执行任何所需的操作。
OnClickListener
。这样可以确保方向键点击和触控都能执行所需的操作。视图大小对于方向键导航并不重要,但对于触控很重要。启用触控功能时,要记得使用更大的视图和UI组件,以便提供良好的用户体验。
在这个简单的应用中,我们会将OnClickListener
应用于布局本身,而不是布局内部的视图。这样,我们就不必更改UI的界面外观。
视图是由RecyclerView
动态创建,因此需要为每个RecyclerView
的每个元素应用单独的OnClickListener
。为此,请修改RecyclerView
适配器的代码,以获取对RecyclerView
每个单独项的布局的引用,并在适配器的onBindViewHolder()
方法中应用onClickListener
:
public class MenuItemsAdapter extends RecyclerView.Adapter < MenuItemsAdapter.ViewHolder > {
private String[] localDataSet;
/**
* 提供对正在使用的视图类型的引用
*(自定义ViewHolder)。
*/
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
private final ConstraintLayout menuConstraintLayout;
public ViewHolder(View view) {
super(view);
// 为ViewHolder的视图定义点击侦听器
textView = (TextView) view.findViewById(R.id.textView);
menuConstraintLayout = view.findViewById(R.id.menuconstraintLayout);
}
public TextView getTextView() {
return textView;
}
public ConstraintLayout getMenuConstraintLayout() {
return menuConstraintLayout;
}
}
/**
* 初始化适配器的数据集。
*
* @param dataSet String[]包含用于填充RecyclerView要使用的视图
* 的数据。
*/
public MenuItemsAdapter(String[] dataSet) {
localDataSet = dataSet;
}
// 创建新视图(通过布局管理器调用)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// 创建一个新视图,该视图定义了列表项的UI
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.menulayout, viewGroup, false);
return new ViewHolder(view);
}
// 替换视图的内容(通过布局管理器调用)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
// 在此位置从数据集中获取元素并替换
// 具有该元素的视图的内容
viewHolder.getTextView().setText(localDataSet[position]);
viewHolder.getMenuConstraintLayout().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在这个示例应用中,我们只是记录点击,
// 但在这里,你可以*,*例如,打开一个新的活动或选择
// 特定类别
Log.e("Click ", "Clicked " + localDataSet[position]);
}
});
}
// 返回数据集的大小(通过布局管理器调用)
@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">
此时,所有UI视图都可以点击,并且在点击或接收焦点后会有不同的背景颜色(点击后为蓝色,接收焦点后为橙色)。
步骤3: 管理方向键和触控之间的视图焦点
在方向键交互和触控交互之间建立一致性非常重要。这意味着如果使用遥控器或触控功能,要确保方向导航以一致的方式工作。
请记住,Android在构建时考虑了触控功能,这意味着管理UI视图的底层架构基本可以支持触控功能。
默认情况下,Android中的大多数视图都是可见并可设定焦点的。视图继承了一个名为focusable的参数,默认情况下,该参数设置为“auto”,意味着由平台决定视图是否应该可设定焦点。默认情况下,Buttons
、TextView
和EditText
等视图可设定焦点,因为它们是主要UI组件。默认情况下,布局和布局填充器(LayoutInflater
)很少可设定焦点,因为它们通常定义UI结构。
要让应用完全支持触控,请确保最重要的视图可设定焦点,并且当客户使用触控功能时,这些视图可以接收焦点。这需要为每个视图编辑两个参数:focusable
和focusableInTouchMode
。
在示例应用中,我们创建了两个新布局来填充“Categories”RecyclerView
和“rows”RecyclerView
内部的每个项目。这些布局文件为menuLayout.xml
和cardLayout.xml
。
为了实现方向键的触控和聚焦,将focusable
和focusableInTouchMode
XML属性都设置为“true”。
来自示例应用的menuLayout.xml
可定义左侧单独类别,并且仅包含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
也来自示例应用,可定义右侧电影行的单独内容。每个卡片都包含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>
现在,如果用户触控UI或使用方向键控制器导航,则正确的UI元素会接收焦点。相关演示请参见下面的动画。
步骤4: 在Fire TV上测试触控功能
如果拥有可用于测试的触摸屏,会对您大有帮助。检查屏幕的每个部分,确保其已支持触控,并且可在方向键和触控之间切换。
如何在没有触摸屏的Fire TV设备上测试触控功能
最简单的解决方案是将蓝牙鼠标连接到Fire TV。鼠标可以模拟触控交互。请按照以下步骤操作:
- 导航到“Settings(设置)”>“Controllers and Bluetooth Devices(控制器和蓝牙设备)”>“Other Bluetooth Devices(其他蓝牙设备)”
- 按照屏幕上的说明连接蓝牙鼠标
- 连接鼠标后,返回应用。鼠标可以控制屏幕上的光标来模拟触控,以及点击和手势
- 测试布局的所有交互部分
最佳实践
完成上述步骤后,应用UI中最重要的组件会支持触控功能。以下额外提供了一些最佳实践,可确保在触控导航和方向键导航这两方面都能提供良好的用户体验:
- 确保需要交互的视图分配有
OnClickListener
,并且可以接收焦点。 - 触控交互不仅仅是点击项目。请确保尽可能使用
ScrollView
和RecyclerView
来引入通过手势滚动浏览的方式。 - 次要应用活动(如详情页面或播放UI)也需要支持触控功能。请使用上述模式让它们支持触控功能。
- 某些第三方组件可能会在用户可访问的区域之外创建布局项目(复选框、按钮等),或者可能无法触发方向键。在此类情况下,请考虑使用
ScrollView
组件等策略来确保用户可以访问它们。
下载示例应用
如果之前没有下载,可以使用我们的GitHub示例应用更快地启动和运行: Fire TV Sample App – Touch and D-pad。它包含了您在上面看到的所有代码,是为实现触控而设置Fire TV应用的绝佳着手点。如果您有任何疑问,可随时联系我们。