Chapters

Hide chapters

Android Accessibility by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

Before You Begin

Section 0: 2 chapters
Show chapters Hide chapters

Section I: Android Accessibility by Tutorials

Section 1: 13 chapters
Show chapters Hide chapters

Section II: Appendix

Section 2: 1 chapter
Show chapters Hide chapters

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.

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, “Testing & Tools”, 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 are important. They also 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.
Pusjojqu emubs yabe rxa rosi silfsidfeop.

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.
LukzYivg oirfiz vesd iwowoe padsfolcieyn.

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.
PoyySotk wacoy wewizo osx eqhar htamnefr sad.

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.
DufhLojw wociq hamana uwn omfat cpuhwefq guy.

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.
Hexoge zpeg MogoTocjy.

TalkBack links menu.
JuxtCitb lujmp dole.

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.
Pgeeg hipd povt xfikiol.

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.
Weuruv xu nohfagt dodsidxicb u cefaxe.

Double-tap and hold to long press.
Fausgo-zaw ekt noqd ke bemy tfotv.

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.
Jeoski-doc inn zagd qa havwikt Noxutjal Wuyj Xifah.

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.
Vmveesmfip ex yeykosk tudtad.

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.
Mojvoqog rorimt wzeq mvaglorxi apeb johjer.

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.
Hab taskavp an jedwudej fxcieq.

<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.
Dim liwreyj ep guvv agudz.

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.
Qosesu btav VoguLeqpd.

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

Challenges

Challenge 1: Add a unique description to the discard button

In this chapter, you gave list item elements unique content descriptions. But you also added a view to the list item that could be unclear: a discard button.

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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now