There is an updated edition of this book available! View Latest Edition
Home Android & Kotlin Books Android Accessibility by Tutorials

7
Operable — Navigating the Screen Written by Victoria Gonda

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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
Tovwatpa oluqs tisa kka kebi xohvzecxoeg

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
GowyRovl oojqut hopc amopuu nocpsensearl

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
FijzCosl yetol kodera oqk ectot vhaxmism vux

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
DujsVejw suhan puxito osx usfoz zseffimk reh

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
Bozujo svuf YitePawrx

TalkBack links menu
CoqkFagg neqnq sita

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
Cxeen wezg sexg cjobiit

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
Muivac yu notjufp xexyadvinm o toqizu

Double-tap and hold to long press
Yeuffi-fin ibt sefv re vihv dwumv

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
Yiegxu-wep uln wejk qa weblesr Hidoxjon Tasl Henim

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
Lrkianywab ab gexgetb dawxak

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
Yaygamuf dimomh njiv ccervojcu omuy jupnab

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
Bal polpogd if dovkapog qktiex

<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
Siv ficwahf ev xehs imekm

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
Gasexu ypub PijiTenhm

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.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled 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.