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

5. Perceivable — Time-Based Media & Cues
Written by Victoria Gonda

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Video, audio, animation and instructions are vital parts of your app’s experience. But for those who live with certain conditions, these media types may not be useful, or even perceivable. In order to build an accessible app, you’ll need to make adjustments to your app’s design to make these kinds of media accessible to all.

In this chapter, you’ll delve deeper into the concept of perceivability, specifically how to make time-based media useful to different people. You’ll also learn best practices for giving your users cues they can use to navigate your app with the help of assistive technologies.

Displaying time-based media

As you might expect, time-based media is anything that, well, takes place over time. The obvious examples are video and audio; they start at a particular time, and then they end later. Animations also fall into this category. There is only one way to consume these media types.

That thought brings you to the prevailing WCAG guideline for this chapter:

Guideline 1.2 Time-based Media Provide alternatives for time-based media.

There are many ways you can provide these types of media. For prerecorded audio, you can have on-screen captions. If you have a video, you can include an audio track or text alternative that contains the same information. When you’re animating an instruction, you can also provide a description in audio or text form.

In some cases, you can make time-based media completely optional, allowing the user to skip it. Be careful with optional settings though — you don’t want to prevent people from accessing content that might be valuable.

Think about the guideline’s success criteria, which specify a heuristic that these elements need equivalent alternatives, for example, text, captions, or other form factors.

Consider this criterion:

Success Criterion 1.2.3 Audio Description or Media Alternative (Prerecorded): An alternative for time-based media or audio description of the prerecorded video content is provided for synchronized media, except when the media is a media alternative for text and is clearly labeled as such.

Level A

Taco Tuesday has some significant issues where time-based media is not accessible, especially in the on-boarding flow. Once again, you’ll improve the app so that you can learn.

Open up the project you used in previous chapters or use the starter project from this chapter’s materials.

Improving the on-boarding flow

Think about the many ways you could design the on-boarding process for Taco Tuesday:

Exploring the on-boarding

If you don’t see the on-boarding flow, go to Settings and select Show on-boarding. Then close and reopen the app. You can do this anytime you want to see on-boarding again.

Show on-boarding in settings.
Vkaq er-jueqnosl ek riqdasnl.

//  val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
//  val showOnboarding = sharedPref.getBoolean("onboarding", true)
//  if (showOnboarding) {
   OnboardingActivity.startActivity(this)
//   finish()
//  }
Screenshots of on-boarding views.
Mfjuakqcehm aj ov-cuavkuyw ceamm.

Removing auto-advance

To make Taco Tuesday’s on-boarding flow more friendly, you’ll remove the auto-advance feature and add controls.

lifecycleScope.launch(Dispatchers.IO) {
 val options = resources.getStringArray(R.array.pop_up_options)
 while (isActive) {
  delay(5000) // 5 seconds

  withContext(Dispatchers.Main) {
   if (binding.onboardingPager.currentItem == NUM_PAGES - 1) {
    MainActivity.startActivity(this@OnboardingActivity)
    this.cancel()
   } else {
    binding.onboardingPager.currentItem++
   }
  }
 }
}

Adding controls

In this section, you’ll implement logic that gives your user a straightforward way to advance to the next page. First, you’ll add the layout for a Next button.

<com.google.android.material.button.MaterialButton
  android:id="@+id/onboarding_next_button"
  style="@style/Widget.MaterialComponents.Button.TextButton"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="@dimen/space_normal"
  android:text="@string/onboarding_next"
  android:textColor="?colorOnPrimary"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/onboarding_next_button"
Next button.
Hutv biwnog.

binding.onboardingNextButton.setOnClickListener {
 if (binding.onboardingPager.currentItem == NUM_PAGES - 1) {
  MainActivity.startActivity(this)
 } else {
  binding.onboardingPager.currentItem =
    binding.onboardingPager.currentItem + 1
 }
}
<com.google.android.material.button.MaterialButton
  android:id="@+id/onboarding_back_button"
  style="@style/Widget.MaterialComponents.Button.TextButton"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:padding="@dimen/space_normal"
  android:text="@string/onboarding_back"
  android:textColor="?colorOnPrimary"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintStart_toStartOf="parent" />
binding.onboardingBackButton.setOnClickListener {
 binding.onboardingPager.currentItem =
   binding.onboardingPager.currentItem - 1
}
binding.onboardingPager.registerOnPageChangeCallback(object :
  ViewPager2.OnPageChangeCallback() {
 override fun onPageSelected(position: Int) {
  binding.onboardingNextButton.text =
    if (position == NUM_PAGES - 1) {
     getString(R.string.onboarding_done)
    } else {
     getString(R.string.onboarding_next)
    }
  binding.onboardingBackButton.visibility =
    if (position == 0) {
     View.GONE
    } else {
     View.VISIBLE
    }
   }
 }
)
Screenshots of all button states.
Fzteomdnasg iw org rudpos gdagev.

Giving cues

Another important part of on-boarding is what you’re saying. How do you make sure your instructions are meaningful? For example, if you’re describing a button’s color, what does that mean for a person who doesn’t perceive color? This brings you to the second criterion you’ll explore in this chapter:

Grayscale buttons.
Qxinpkafo gictukp.

Improving cues in on-boarding

There are a number of things you’ll do to improve the onboarding flow’s cues. You’ll start by making the button descriptions more clear and friendly to those who rely on screen readers.

Clarifying the instructions

The instructions are defined in strings.xml, and each entry is prepended with onboarding_ for ease when searching.

Icon buttons on the detail screen.
Amef towluhk ir jpi bipuow yxboav.

Adding text to the button

No button should be without some kind of description, so you’ll add some text to this button to make its purpose clear.

com.google.android.material.button.MaterialButton
android:src="@drawable/ic_baseline_thumb_up_24"
android:text="@string/shared_try_it"
app:icon="@drawable/ic_baseline_thumb_up_24"
Try it button.
Rcb op zuqnuj.

Modifying the button state

This button changes state depending on if the recipe is currently saved or not. This means you need logic to update the text and icon, depending on its state.

recipeDetailTryDiscardButton.setImageDrawable(
 ResourcesCompat.getDrawable(resources,
  R.drawable.ic_baseline_thumb_down_24,
  requireContext().theme))
recipeDetailTryDiscardButton.text =
  getString(R.string.shared_discard)
recipeDetailTryDiscardButton.icon =
  ResourcesCompat.getDrawable(resources,
    R.drawable.ic_baseline_thumb_down_24,
    requireContext().theme)
recipeDetailTryDiscardButton.setImageDrawable(
 ResourcesCompat.getDrawable(resources,
  R.drawable.ic_baseline_thumb_up_24,
  requireContext().theme))
recipeDetailTryDiscardButton.text =
  getString(R.string.shared_try_it)
recipeDetailTryDiscardButton.icon =
  ResourcesCompat.getDrawable(resources,
   R.drawable.ic_baseline_thumb_up_24,
   requireContext().theme)
Updated buttons on the detail screen.
Eztegat vikjorh uq vse vuboiq cgkaut.

Adding state-specific instructions

Now you can add instructions for this button to your on-boarding. You’ll need to add a new page just for these instructions.

<string name="onboarding_details">Manage your recipes using the \"Try it\" and \"Discard\" buttons on the detail view.</string>
<string name="onboarding_details_description">Thumbs-up icon</string>
OnboardingItem(
  R.drawable.onboarding_details,
  R.string.onboarding_details,
  R.string.onboarding_details_description
),
private val NUM_PAGES: Int
 get() = pages.size
Screenshot of new on-boarding page.
Zygeimfbeb at duk ih-vaovrapv loqi.

Key points

  • Time-based media such as video, audio and animations, must be accompanied by alternatives.
  • Alternatives to visual media can be text or audio, and alternatives to audio can be equivalent visuals.
  • Users must be able to control media that’s important for them to understand. You need to provide a way for them to go back and revisit something
  • Sensory characteristics such as shape, color, size, visual location, orientation or sound should not be the only ways you give instructions.

Where to go from here?

Time-based media is a broad topic, in part, because there are many ways to use time-based media in an app.

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now