Designing for non-touch users means ensuring two things: access and feedback. As always, it's a case of identifying user journeys and making sure they are achievable by your target users.
A totally accessible app should allow non-touchscreen users to access all information and controls that a touchscreen user can access.
The user should be given feedback (aural, visual and/or haptic) as they navigate through your app using non-touch input so they can locate themselves on a screen.
This post will demonstrate how you can start to make your apps accessible to non-touchscreen users.
These users include anyone with Google Android TV or Amazon Fire TV. It also includes any user that interacts with their Android device via keyboards, dpads, trackballs or joysticks.
Android supports the use of bluetooth or USB mice - users can interact with controls by clicking when the mouse cursor is on top of the element they want to interact with.
With dpad users, there is no cursor - the controls themselves gain focus and, ideally, indicate this state of focus to the user somehow.
What is focus?
Focus indicates a View's readiness to consume key events. For example, an EditText that has focus can consume character inputs from a keyboard.
Only one View can be focused at a time - the key inputs must be directed to a single View (which can decide to consume the events or let them fall through to the next View, much like touch events).
Although every View can be made focusable, not all are focusable by default. You can use the
android:focusable property in XML or
View#setFocusable(boolean) API in Java to override the default value.
EditTexts, Buttons and ScrollViews are examples of Views that are focusable by default. TextView, ImageView and LinearLayout, among others, are not. Given what focusability implies (ability to consume key events), can you reason why these are the defaults?
For Views that are focusable, you can use
view.requestFocus() to ask the system to give it focus.
The focused state refers to the state a View is in when it's focused - more typically, it refers to the visual treatment applied to the View when in this state.
Press states are probably more familiar and are certainly more common. Here we want to change the background of our TextView when it's being pressed:
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/background" />
<selector> <item android:state_pressed="true" android:drawable="@color/red" /> <item android:drawable="@color/transparent" /> </selector>
res/drawable/background.xml is a StateListDrawable. The Android framework will read from top-down, and choose the first drawable where all the conditions are true. In this case, it'll choose the color red when the View is in a pressed state, otherwise it'll choose transparent.
For Lollipop and above, we can use a RippleDrawable. In the example below, the default color is transparent. When pressed, the Android framework will animate a ripple using the color red. The mask is used to create bounds for the ripple, so it clips at the edges of the mask - the color itself is not important:
<ripple android:color="@color/red"> <item android:drawable="@color/transparent" /> <item android:id="@android:id/mask" android:drawable="@android:color/white" /> </ripple>
To add a focus state, we just need to add another entry in the StateListDrawable:
<selector> <item android:state_pressed="true" android:drawable="@color/red" /> <item android:state_focused="true" android:drawable="@color/pink" /> <item android:drawable="@color/transparent" /> </selector>
Done! Well, almost. As TextView is not focusable by default, we need to make it so explicitly:
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="true" android:background="@drawable/background" />
We don't need to do anything to
res/drawable-v21/background.xml because the RippleDrawable already supports both focused and pressed states.
Testing focused states
Using Genymotion (or an emulator) is the most convenient way I've found to test non-touch input.
You can also connect a bluetooth keyboard (or USB keyboard with an OTG adapter) to a physical device, or even test directly on an Android TV/Amazon Fire TV.
You will find that Views seem to lose focus when you touch the screen (or click on the screen). This is because focus does not exist in touch mode.
Touch mode is a boolean state of your device - the device is either in touch mode or not.
The device is in touch mode if the last interaction was via the user's touch (or emulated touch if using an emulator or Genymotion). Using the keyboard or a dpad will switch the device to non-touch mode.
You can check if the device is in touch mode by using the
Focusable in Touch mode
I lied, focus can exist in touch mode. There's a great article on touch mode by Romain Guy.
Some Views can be focused while in touch mode - EditText for example. Consider a login screen with input fields for username and password. In touch mode, you can touch on an EditText to give it focus, and then continue typing with the on-screen keyboard: key events will be directed to that EditText and not the other.
New Android developers often think that focusable in touch mode is the solution they need to "fix" the problem of disappearing selection/focus
The TL;DR of focusable in touch mode is that you rarely need it (beyond what the system gives you).
We looked at what focus is, why it's used and how to implement it in simple cases. We covered how to test your app's focused states and touched on (oh ho) touch mode. Finally, introduced the concept of focusable in touch mode and presented a warning not to use it as a quick fix.
In a later post, we'll cover some practical implementations - how to make common Android patterns compatible with non-touch input.