开发者控制台

为Fire TV添加触控

为Fire TV添加触控

除了将Amazon Fire TV应用设置为与遥控器和方向键配合使用外,现在还可以设置触控交互。鉴于Fire TV正在向汽车领域的应用扩展,这个备选方案的重要性与日俱增。本教程将为您介绍如何修改旨在用于遥控器和方向键的Fire TV应用,添加触控功能,以及如何提供基于触控的良好用户体验。有关更多信息,请参阅Automotive UX指南

建议下载我们的示例应用(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,其中包含了可以播放的所有电影和内容。
黑色的menuLayout(左)和灰色的rowsLayout(右)
黑色的menuLayout(左)和灰色的rowsLayout(右)

您的应用可能具有更复杂的视图嵌套,但这代表了动态媒体/电视应用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设置为afterDescendantsRecyclerView本身会自动为RecyclerView内部的项目提供焦点。在本例中,它将焦点放在RecyclerView菜单中定义的类别上。

同样,将其应用于右侧布局上的每个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"/>

请注意,rowRecyclerView4没有nextFocusDown目标。这是因为这是最后一个RecyclerView,没有更多的RecyclerView可供浏览。

到目前为止,就有了一个可以完全靠使用方向键来导航的UI。现在,我们可以通过修改RecyclerView的内容来让界面支持触控功能。

了解如何实现即使视图为动态生成,也可以完全使用方向键来导航的UI
了解如何实现即使视图为动态生成,也可以完全使用方向键来导航的UI

步骤2: 使用OnClickListener添加触控功能

为Android电视应用添加触控功能实际上相当容易,因为Android在构建时考虑到了触控交互。要将其添加到应用UI中,可以使用可实现方向键交互和触控交互这两者的标准Android组件。

最佳实践是使用OnClickListener在视图上实现点击或触控操作。OnClickListener会触发名为onClick()的方法,让您可以执行任何所需的操作。

视图大小对于方向键导航并不重要,但对于触控很重要。启用触控功能时,要记得使用更大的视图和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视图都可以点击,并且在点击或接收焦点后会有不同的背景颜色(点击后为蓝色,接收焦点后为橙色)。

可以看到通过遥控器正确触发`onClickListeners`
可以看到通过遥控器正确触发`onClickListeners`

步骤3: 管理方向键和触控之间的视图焦点

在方向键交互和触控交互之间建立一致性非常重要。这意味着如果使用遥控器或触控功能,要确保方向导航以一致的方式工作。

请记住,Android在构建时考虑了触控功能,这意味着管理UI视图的底层架构基本可以支持触控功能。

默认情况下,Android中的大多数视图都是可见并可设定焦点的。视图继承了一个名为focusable的参数,默认情况下,该参数设置为“auto”,意味着由平台决定视图是否应该可设定焦点。默认情况下,ButtonsTextViewEditText等视图可设定焦点,因为它们是主要UI组件。默认情况下,布局和布局填充器(LayoutInflater)很少可设定焦点,因为它们通常定义UI结构。

要让应用完全支持触控,请确保最重要的视图可设定焦点,并且当客户使用触控功能时,这些视图可以接收焦点。这需要为每个视图编辑两个参数:focusablefocusableInTouchMode

在示例应用中,我们创建了两个新布局来填充“Categories”RecyclerView和“rows”RecyclerView内部的每个项目。这些布局文件为menuLayout.xmlcardLayout.xml

在左侧,可以看到带黑色背景的“Categories” RecyclerView,以及带灰色背景的“Rows” RecyclerView
在左侧,可以看到带黑色背景的“Categories” RecyclerView,以及带灰色背景的“Rows” RecyclerView

为了实现方向键的触控和聚焦,将focusablefocusableInTouchMode 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也来自示例应用,可定义右侧电影行的单独内容。每个卡片都包含ImageViewTextView

<?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。鼠标可以模拟触控交互。请按照以下步骤操作:

  1. 导航到“Settings(设置)”>“Controllers and Bluetooth Devices(控制器和蓝牙设备)”>“Other Bluetooth Devices(其他蓝牙设备)”
  2. 按照屏幕上的说明连接蓝牙鼠标
  3. 连接鼠标后,返回应用。鼠标可以控制屏幕上的光标来模拟触控,以及点击和手势
  4. 测试布局的所有交互部分

最佳实践

完成上述步骤后,应用UI中最重要的组件会支持触控功能。以下额外提供了一些最佳实践,可确保在触控导航和方向键导航这两方面都能提供良好的用户体验:

  • 确保需要交互的视图分配有OnClickListener,并且可以接收焦点。
  • 触控交互不仅仅是点击项目。请确保尽可能使用ScrollViewRecyclerView来引入通过手势滚动浏览的方式。
  • 次要应用活动(如详情页面或播放UI)也需要支持触控功能。请使用上述模式让它们支持触控功能。
  • 某些第三方组件可能会在用户可访问的区域之外创建布局项目(复选框、按钮等),或者可能无法触发方向键。在此类情况下,请考虑使用ScrollView组件等策略来确保用户可以访问它们。

下载示例应用

如果之前没有下载,可以使用我们的GitHub示例应用更快地启动和运行: Fire TV Sample App – Touch and D-pad。它包含了您在上面看到的所有代码,是为实现触控而设置Fire TV应用的绝佳着手点。如果您有任何疑问,可随时联系我们

资源