Home Android & Kotlin Books Android Accessibility by Tutorials

7
Operable — Navigating the Screen Written by Victoria Gonda

If you’ve worked through this book chapter by chapter, then you’ve learned a lot about what comprises a perceivable app. You might be surprised to learn that it’s not enough for your app to be perceivable. It also needs to be operable, which is defined by the WCAG as:

2. Operable: User interface components and navigation must be operable.

This definition means that users should be able to perform actions and navigate your app, whether they use fingers, voice, screen readers or something else. Every user should have the same choices for actions and views they can reach.

In this chapter, you’ll focus on making Taco Tuesday navigable with accessibility services, and therefore more operable.

Traversing using a keyboard

There are multiple ways to navigate an Android device with a keyboard. For example, in Chapter 3, “Tools and Testing”, you learned about using TalkBack as a keyboard. You can also connect a keyboard to most mobile devices and navigate with keystrokes. For testing, you can create emulators that make use of your computer’s keyboard.

To navigate with a keyboard, you use the Tab and Arrow keys. When you’re testing how well keyboard navigation works, you want to make sure that everything is reachable, elements are navigated in a logical order, and that you don’t get trapped in one part of the screen.

WCAG’s guideline for keyboards is straightforward:

Guideline 2.1 Keyboard Accessible: Make all functionality available from a keyboard.

If you build on native components, then keyboard navigation should work pretty well. You won’t need to change a lot, and you can focus on other operability issues and fine-tuning the experience.

Adjusting navigation order

If you find that you need to change some element’s ordering to improve keyboard navigation, you can use a couple of XML layout attributes. Here’s the first:

android:nextFocusForward="@+id/editText1"
android:nextFocusUp="@+id/editText1"
android:nextFocusDown="@+id/editText2"
android:nextFocusLeft="@+id/editText3"
android:nextFocusRight="@+id/editText4"

Navigating your app

This chapter focuses on how to allow users who use accessibility tools to navigate your app. WCAG’s guideline is logical but broad:

Distinguishing list items

As you know from earlier in the book, content descriptions must be unique so that a user knows where they are on the screen and which item an action might affect.

Multiple items have the same description
Hivwenxu ivahf yeqa vri qepi sipdnuyxaek

Improving content descriptions

Open TryItRecipesRecyclerViewAdapter.kt. In ViewHolder, find bind(). Here is where you’ll add all your content descriptions.

binding.itemRecipeMade.contentDescription =
 itemView.context.getString(
 R.string.try_it_description_made_recipe, recipe.name)

binding.itemRecipeDetails.contentDescription =
 itemView.context.getString(
 R.string.try_it_description_details_recipe, recipe.name)
TalkBack output with unique descriptions
PijcYecf eevmac rosn uxabio deylrapbeegl

Keeping list item focus

Focus items have similar requirements. They need to flow in a logical order, and it must be apparent to the user where they are on the screen. It’s also acceptable, if not advisable, to skip duplicated content. Here’s the WCAG success criterion for focus ordering:

TalkBack focus before and after checking box
DelbWorg fewal micowu enp ajpik xpenbopk sam

Resolving the list item focus bug

First, disable the item animator:

itemAnimator = null
init {
 setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
 return getItem(position).id
}
TalkBack focus before and after checking box
JokvKajx koboq pedico ufw arwek ykukqapj keq

Managing links

Links are common in apps, especially when displaying user-generated content. Because of this, there are criteria for addressing links. Here’s one of them:

Exploring links in Taco Tuesday

Take a look at a detailed view for a recipe. There’s a bit of informational text below the description that says Recipe from TacoFancy.

Recipe from TacoFancy
Gumere dweh CaboKaysw

TalkBack links menu
XagvQajc rofvy zuli

Improving the experience around links

One option is to find the links in the text and then extract and display the link details. You see this in many apps. For example, Twitter shows link previews.

Tweet with link preview
Kpeuq bekd cegm zpilour

Implementing TTsSpan

You’ll use TtsSpan as a custom Span for your markdown links.

.usePlugin(object : AbstractMarkwonPlugin() {
 override fun configureTheme(builder: MarkwonTheme.Builder) {
 builder.linkColor(ContextCompat.getColor(requireContext(),
  R.color.colorPrimary))
 }
})
.usePlugin(object : AbstractMarkwonPlugin() {
 override fun configureSpansFactory(
  builder: MarkwonSpansFactory.Builder
 ) {
 super.configureSpansFactory(
  builder.setFactory(Link::class.java,
   object : LinkSpanFactory() {
    override fun getSpans(
     configuration: MarkwonConfiguration,
     props: RenderProps
    ): Any? {

    }
   })
 )
 }
})
val href = CoreProps.LINK_DESTINATION.require(props)
val uri = Uri.parse(href)
return arrayOf<Any>(

)
LinkSpan(configuration.theme(), href,
 configuration.linkResolver()),
ForegroundColorSpan(ContextCompat.getColor(requireContext(),
 R.color.colorPrimary)),
TtsSpan.ElectronicBuilder()
 .setPort(uri.port)
 .setDomain(uri.host)
 .setPath(uri.path)
 .setQueryString(uri.query)
 .build()

Handling gestures

Support for gestures gives an app a layer of polish — allowing your users to swipe or pinch to perform actions can bring delight. Unfortunately, you can create accessibility issues when a gesture is the only way to perform a particular action.

Adding long press to discard

The first option is a long press. While still not very discoverable, this option preserves your pristine UI.

// 1
binding.itemRecipeTitle.setOnLongClickListener {
 // 2
 MaterialAlertDialogBuilder(it.context)
  .setTitle(R.string.try_it_discard_confirm_title)
  .setMessage(it.context.getString(
  R.string.try_it_discard_confirm_message, recipe.name))
  // 3
  .setPositiveButton(
  R.string.try_it_discard_confirm_discard) { _, _ ->
   onDiscardRecipe(recipe)
  }
  // 4
  .setNegativeButton(
  R.string.try_it_discard_confirm_cancel) { _, _ -> }
  .show()
 true
}
Dialog to confirm discarding a recipe
Xiogot go boytilg sullumqaqs u riqive

Double-tap and hold to long press
Veettu-car atc himm yu raqq vqivs

class DeleteRecipeAccessibilityDelegate(
 private val recipeName: String
) : AccessibilityDelegateCompat() {

}
override fun onInitializeAccessibilityNodeInfo(
 host: View,
 info: AccessibilityNodeInfoCompat
) {
 // 1
 super.onInitializeAccessibilityNodeInfo(host, info)
 // 2
 val longClick =
  AccessibilityNodeInfoCompat.AccessibilityActionCompat(
   AccessibilityNodeInfo.ACTION_LONG_CLICK,
   host.context.getString(
    R.string.try_it_description_discard_recipe,
    recipeName))
 // 3
 info.addAction(longClick)
}
ViewCompat.setAccessibilityDelegate(binding.itemRecipeTitle,
 DeleteRecipeAccessibilityDelegate(recipe.name))
Double-tap and hold to discard Moroccan Lamb Tacos
Weurji-kab ujr gunj mi hezqicp Sirujfup Hack Zacuk

Adding a discard button

The other option you’ll implement is adding a one-tap discard action to the list item. For this exercise, you’ll add one adjacent to the button to view a full recipe.

<ImageButton
 android:id="@+id/item_recipe_discard"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 app:iconGravity="end"
 android:contentDescription="@string/shared_discard"
 android:src="@drawable/ic_baseline_thumb_down_24"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toStartOf="@+id/item_recipe_details"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toBottomOf="@id/item_recipe_rating"
 />
app:layout_constraintStart_toEndOf="@id/item_recipe_discard"
app:layout_constraintStart_toStartOf="parent"
binding.itemRecipeDiscard.setOnClickListener {
 onDiscardRecipe(recipe)
}
Screenshot of discard button
Hndaobxnuy ot xafdepq hejnex

Considering touch targets

Have you ever run across a button or link that’s hard to tap unless you zoom in? This is an example of a touch target issue. WCAG says this about touch targets:

Consider making this clickable item larger
Nokmupat segeys lzan jfetwatvo anun miptaz

Fixing touch targets

You’ll fix these up while making them look a bit nicer by making these targets into MaterialButtons.

android:src="@drawable/ic_baseline_thumb_down_24"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_margin="@dimen/space_normal"
android:text="No thanks"
app:icon="@drawable/ic_baseline_thumb_down_24"
app:iconPadding="@dimen/drawable_padding"
<com.google.android.material.button.MaterialButton
 android:id="@+id/discover_button_discard"
 style="@style/Widget.MaterialComponents.Button.TextButton"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_margin="@dimen/space_normal"
 android:text="No thanks"
 app:icon="@drawable/ic_baseline_thumb_down_24"
 app:iconPadding="@dimen/drawable_padding"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toStartOf="@id/discover_button_try"
 app:layout_constraintHorizontal_chainStyle="packed"
 app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
 android:id="@+id/discover_button_try"
 style="@style/Widget.MaterialComponents.Button.TextButton"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_margin="@dimen/space_normal"
 android:text="Save for later"
 app:icon="@drawable/ic_baseline_thumb_up_24"
 app:iconPadding="@dimen/drawable_padding"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toEndOf="@id/discover_button_discard"
 />
New buttons on discover screen
Quz cibbald os qubkabav wnwiaj

<com.google.android.material.button.MaterialButton
 android:id="@+id/item_recipe_discard"
 style="@style/Widget.MaterialComponents.Button.TextButton"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 app:iconGravity="end"
 android:contentDescription="@string/shared_discard"
 android:textColor="?colorOnPrimary"
 app:icon="@drawable/ic_baseline_thumb_down_24"
 app:iconTint="?colorOnPrimary"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toStartOf="@+id/item_recipe_details"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toBottomOf="@id/item_recipe_rating"
 />

<com.google.android.material.button.MaterialButton
 android:id="@+id/item_recipe_details"
 style="@style/Widget.MaterialComponents.Button.TextButton"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/shared_details"
 android:textColor="?colorOnPrimary"
 app:icon="@drawable/ic_baseline_view_24"
 app:iconTint="?colorOnPrimary"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toEndOf="@id/item_recipe_discard"
 app:layout_constraintTop_toBottomOf="@id/item_recipe_rating"
 />
New buttons on list items
Kip cutkibk ij xesx ohazy

Targeting links

While inline links are exempt from touch target size guidance, there’s a specific case for following them to make a link more clickable: anytime you have a sentence with a single link. For example, “By tapping ‘Continue’ you agree to our Privacy Policy”, where Privacy Policy is the linked text.

Recipe from TacoFancy
Xubiqe nmeh RipiDocmg

Recipe from TacoFancy
recipeDetailCreditText.setOnClickListener {
 startActivity(Intent(
 Intent.ACTION_VIEW,
 Uri.parse("https://github.com/sinker/tacofancy")))
}
recipeDetailCreditText.movementMethod =
 LinkMovementMethod.getInstance()

Key points

  • Operability is a crucial part of achieving accessibility.
  • Accessibility tools help you identify operability issues within an app’s navigation.
  • All elements of a given view must be reachable via a keyboard interface.
  • Content descriptions for different list items should be unique so that the user can differentiate between similar items.
  • Focus ordering should flow in a logical pattern.
  • When performing operations on list items, make sure a screen reader can keep focus on the correct element.
  • Make link text clear and descriptive, and use TtsSpan when applicable.
  • Actions triggered by gestures should also be reachable via a single-tap.
  • Touch targets should be at least 48dp by 48dp.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.