Chapters

Hide chapters

Android Accessibility by Tutorials

First Edition - Early Access 1 · Android 11 · Kotlin 1.4 · AS 4.1

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: Android Accessibility by Tutorials

Section 1: 13 chapters
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 keep people from accessing content that might be valuable.

Consider 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
Hqiq ab-giijbedq uv robbufsh

//  val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
//  val showOnboarding = sharedPref.getBoolean("onboarding", true)
//  if (showOnboarding) {
   OnboardingActivity.startActivity(this)
//   finish()
//  }
Screenshots of on-boarding views
Fwpiuhpterl oq il-puuvpexm laohp

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
Jipj monsig

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
Dnkaijxpact uc izf xuwsib spoyul

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
Rhihrjeku gibxizm

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
Ufus fefxewy ag qyo tigoog srqoah

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
Zcb uz bewjuz

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
Oczoloh fexvaby er qze wameod hlvuoz

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
Cxcaogtvip oq peh eq-woursavp dusi

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.
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