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.
- Prerequisite: Map UI navigation to the remote D-pad using directional navigation
- Step 1: Apply Directional Navigation to Dynamic TV UI
- Step 2: Add touch using OnClickListener
- Step 3: Manage the view focus between D-pad and touch
- Step 4: Test touch on Fire TV
- Best practices
- Download the sample app
- Resources
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 aRecyclerView
called “recyclerViewMenu,” which itself contains the left-side menu with all the categories. - A second
LinearLayout
called “rowsLayout,” containing otherRecyclerViews
, which contain all the movies and content that can be played.
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 RecyclerView
s.
RecyclerViews
defines the relationships between items. Since Android takes care of defining relationships for us at the framework level, we don’t have to define the relationships manually.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
.
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.
OnClickListener
on top of your custom implementation. This ensures D-pad clicks and touch both execute the 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).
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
.
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.
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:
- Navigate to Settings > Controllers and Bluetooth Devices> Other Bluetooth devices
- Follow the on-screen instructions to connect your Bluetooth mouse
- 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
- 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
andRecyclerViews
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
- Automotive UX Guidelines
- Automotive Developer Reference
- Official Android documentation for directional navigation
- Fire TV Sample App – Touch and D-pad on GitHub
Last updated: Jan 13, 2022