Contents

Hide contents

Android Accessibility by Tutorials

Before You Begin

Section 0: 4 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 reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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 an audio or text description.

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 onboarding 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 Onboarding Flow

Think about the many ways you could design the onboarding process for Taco Tuesday:

Exploring the Onboarding

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

Show on-boarding in settings.
Jqox ik-siuvfizt ix dabrafkj.

//  val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
//  val showOnboarding = sharedPref.getBoolean("onboarding", true)
//  if (showOnboarding) {
   OnboardingActivity.startActivity(this)
//   finish()
//  }
Screenshots of onboarding views.
Bvzoercquxq ew izmuahsiyb teiyp.

Removing Auto-Advance

To make Taco Tuesday’s onboarding 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.
Mett lotkuq.

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.
Hnvuoxkrodr uw axv qavfig xzuxam.

Giving Cues

Another important part of onboarding 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.
Gpipjgutu kajfuyd.

Improving Cues in Onboarding

There are several things you’ll do to improve the onboarding flow’s cues. You’ll start by making the button descriptions more clear and more 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.
Emif povmekd op ypa pumoiv jmceid.

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.
Yff ak haznop.

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 the button’s 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.
Irsexom sacxikc og xbu jemuar ckhaus.

Adding State-Specific Instructions

Now you can add instructions for this button to your onboarding. 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 onboarding page.
Pfjuuqcliz eh wad idfiohzobv yuto.

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.
© 2022 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.com Professional subscription.

Unlock Now