Developer Console

Add touch to Fire TV

Besides setting up your Amazon Fire TV app to work with the remote control and D-pad, you can now set up touch interaction as well. This is an increasingly important option now that Fire TV is expanding for automobile use. In this tutorial, you will learn how to modify your Fire TV app already designed for remote and D-pad, to add touch as well as how to provide a good touch-based UX. See Automotive UX Guidelines for more information.

We recommend you download our sample app (Fire TV Sample App – Touch and D-pad), which integrates all of the code below and allows you to quickly see it in action. It’s a good starting point when creating or editing your app.

Prerequisite: Map UI navigation to the remote D-pad using directional navigation

Android-based applications for Fire TV should follow clear platform-level patterns for remote-based navigation. Since Fire OS is built on top of Android, it follows the same layout and design patterns as Android apps.

To map the app UI navigation automatically to the remote D-pad, and specify what order the Android Views should be in for the customer, we need to use Android’s Directional Navigation (see Android’s documentation here). this is a general best practice when implementing Android app layouts, and impacts how Touch behavour connects to D-pad behavour.

Directional Navigation requires you to specify the previous and next view to be selected for each “focusable” view. This allows the system to automatically map the focus to the next view when a user presses the navigation buttons on their remote D-pad (Up, Down, Left, Right).

You can do this by adding the following to your XML layout files:

android:nextFocusDown, android:nextFocusRight, android:nextFocusLeft, android:nextFocusUp

Afterwards, add the ID of the view you want the app to select next based on the order. For example:

<TextView android:id="@+id/Item1"
          android:nextFocusRight="@+id/Item2"/>

The previous code allows the TextView (Item1) to move to the view (Item2) when the customer presses the “Right” button on their D-pad. Follow this format for all navigation directions.

Step 1: Apply Directional Navigation to Dynamic TV UI

Before applying touch to our Fire TV app, we need to create consistent directional navigation between the D-pad and touch navigation.

While this may be simple for basic app interfaces, Media and Entertainment TV App interfaces can be quite complex. These often display dynamic content, requiring runtime generation.

This is why most developers use Android Views to hold dynamic content. A good example is the RecyclerView. The RecyclerView component allows dynamic content to be parsed from an adapter. RecyclerView is efficient and it implements one of the standard Android patterns: the ViewHolder.

The content of a RecyclerView is dynamic, so make sure the navigation between each RecyclerView is generated correctly.

Here is the UI for the sample application mentioned above. It simulates the standard implementation of a TV interface and has two main UI components:

  • A LinearLayout called “menuLayout,” containing a RecyclerView called “recyclerViewMenu,” which itself contains the left-side menu with all the categories.
  • A second LinearLayout called “rowsLayout,” containing other RecyclerViews, which contain all the movies and content that can be played.
The menuLayout in black (left), and the rowsLayout in grey (right)
The menuLayout in black (left), and the rowsLayout in grey (right)

Your app may have more complex nesting for its views, but this represents the skeleton of a dynamic media/TV app UI.

Important: Scrolling will be enabled if you build your layout using scrollable components, such as ScrollView.

Let’s define how the directional navigation works in your layout. First, move from your categories menu to the content rows. To do that, set nextFocusRight to the first row RecyclerView for our LinearLayout:

<LinearLayout   
    android:id="@+id/menuLayout"
    [...]
    android:nextFocusRight="@id/rowRecyclerView1">

Now once the user clicks on the right button, it will automatically move the focus to the first RecyclerView on the right.

Next set up how the navigation between the RecyclerView items work. Since the views of a RecyclerView are created dynamically at runtime, it’s not practical to manually set the navigation direction on each individual view. It can’t be done using the XML layout anyway. Instead, use the descendantFocusability tag on the RecyclerView:

<androidx.recyclerview.widget.RecyclerView   
    android:id="@+id/recyclerViewMenu"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="afterDescendants" />

By setting descendantFocusability to afterDescendants once the views are dynamically generated, the RecyclerView itself provides focus to the items inside the RecyclerView automatically. In this case, it gives the focus to the categories defined in the RecyclerView menu.

Apply this to every RecyclerView on your right side layout as well. Then define the directional navigation between each RecyclerView. For simplicity, here we defined 4 rows through 4 dedicated RecyclerViews.

Your RecyclerViews should look like this:

<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"/>

Notice how rowRecyclerView4 doesn't have the nextFocusDown target. That's because it's the last RecyclerView, and there are no more RecyclerViews to navigate to.

Once this far, you have a fully navigable UI using the D-pad. Now we can touch-enable our interface by modifying the content of our RecyclerViews.

See how the UI is fully navigable using the remote even though the views are generated dynamically
See how the UI is fully navigable using the remote even though the views are generated dynamically

Step 2: Add touch using OnClickListener

Adding touch to Android apps for TV is actually quite easy, as Android was built with touch interaction in mind. To add it to your application UI, you can use standard Android components which allow interactions for both D-pad and touch.

As a best practice, implement click or touch actions on a view using an OnClickListener. OnClickListener triggers a method called onClick(), allowing you to execute any desired operation.

With D-pad navigation, the size of the view isn’t important, but it is for touch. Remember to use larger views and UI components to provide a great user experience when enabling touch.

In this simple application, we will apply the OnClickListener to the layout itself, and not to the views inside the layout. That way, we don’t have to change the look and feel of the UI.

Views are dynamically created by the RecyclerViews, so we need to apply individual OnClickListeners to each element of each RecyclerView. To do this, modify the code for the RecyclerView adaptors to get a reference to the layout of each individual item of the RecyclerView, and apply the onClickListener in the onBindViewHolder() method of the adapter:

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;
    }
}

You can see if an item has received the focus or been clicked by using backgrounds and drawables that contain the different states of a view. Use a drawable item in a selector element, which can contain multiple states like focused and pressed (clicked).

Use menuselectordrawable.xml for background for your menu layout

<?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>

In the 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">

At this point, all your UI views are clickable and have a different background color when clicked (blue), or when they receive the focus (orange).

You can see the `onClickListeners` being correctly triggered via the remote control
You can see the `onClickListeners` being correctly triggered via the remote control

Step 3: Manage the view focus between D-pad and touch

It’s important to build consistency between D-pad and touch interactions. This means making sure your directional navigation works consistently if you’re using a remote or touch.

Remember, Android was built with touch in mind, which means the underlying layer managing the Views of your UI are mostly already touch-enabled.

Most Views in Android are visible and focusable by default. Views inherit a parameter called focusable, which by default is set to “auto,” meaning that it’s up to the platform to determine if the View should be focusable or not. Views like Buttons, TextView, and EditText are focusable by default since they are the primary UI components. Layouts and layout inflators (LayoutInflater) are seldom focusable by default, since they most often define the UI structure.

To fully touch-enable your app, make sure the most important views are focusable, and that they receive the focus when a customer uses touch. This requires editing two parameters for each View: focusable and focusableInTouchMode.

In our sample app, we created two new layouts to populate each item inside the “Categories” RecyclerView, and the “rows” RecyclerView. These layout files are menuLayout.xml, and cardLayout.xml.

On the left you can see the “Categories“ <code>RecyclerView</code> with a black background, and the “Rows“ <code>RecyclerView</code> with a gray background
On the left you can see the “Categories“ RecyclerView with a black background, and the “Rows“ RecyclerView with a gray background

In order to allow touch and focus for the D-pad, set both the focusable and focusableInTouchMode XML attributes to “true”.

menuLayout.xml from the sample app defines individual categories on the left, and only contains a 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, also from the sample app, defines individual content for the movie rows on the right. Each card contains an ImageView and a 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>

Now, if the user touches the UI or navigates using a D-pad controller, the correct UI element will receive the focus. See the animation below for a demonstration.

Views can now be selected using touch
Views can now be selected using touch

Step 4: Test touch on Fire TV

If you have a touchscreen available for testing, great. Check each part of the screen to make sure it is touch enabled and can switch between a D-pad and touch.

How to test touch on Fire TV devices without a touchscreen

The easiest solution is to connect a Bluetooth mouse to your Fire TV. The mouse will simulate touch interaction. Follow these steps:

  1. Navigate to Settings > Controllers and Bluetooth Devices> Other Bluetooth devices
  2. Follow the on-screen instructions to connect your Bluetooth mouse
  3. After connecting the mouse, go back to your app. Your mouse can control the cursor on the screen to simulate touch, along with clicks and gestures
  4. Test all interactive parts of your layout

Best practices

If you have completed the steps above, the most important components of your app UI will be touch-enabled. Here are some additional best practices to ensure you provide a great user experience for both touch and D-pad navigation:

  • Ensure that Views which need interaction have an OnClickListener assigned to them and can receive the focus.
  • Touch interaction is more than just clicking on items. Make sure to include ways to scroll through gestures by using ScrollViews and RecyclerViews where possible.
  • Secondary application activities such as detail pages or playback UIs also need to be touch enabled. Use the patterns described above to enable them.
  • Some 3rd-party components may create layout items (checkboxes, buttons, etc.) outside of areas the user can access, or they may not trigger the D-Pad. In such instances, consider strategies such as using a ScrollView component to ensure the user can access them.

Download the sample app

If you didn’t download it earlier, you can get things up and running faster with our GitHub sample app: Fire TV Sample App – Touch and D-pad. It includes all the code you’ve seen above and is a great starting point for setting up your Fire TV app for touch. If you have questions, feel free to Contact Us.

Resources


Last updated: Jan 13, 2022