Accessibility made Easy

Accessibility made Easy

Android Makers 2020

4d2070e800356da57315d427aa2343f4?s=128

François Blavoet

April 21, 2020
Tweet

Transcript

  1. @francoisblavoet Accessibility Made Easy

  2. What Is Accessibility? ▸ Accessibility : making sure that anyone

    can use your application ▸ Who is targeted ?
 - Blind
 - Any kind of visual, auditory and motor disabilities
 - Color blindness
 … ▸ 25 % of the population has a disability
  3. What Is Accessibility?

  4. How?

  5. How? Switch Access Voice Access And many more a11y services

  6. How?

  7. How?

  8. How?

  9. How?

  10. How? And many, many more devices !

  11. How?

  12. How?

  13. How?

  14. Why Implement Accessibility? ▸ It’s the right thing to do

    !
 ▸ Untapped market
 ▸ Legal and contractual obligations
  15. Why Implement Accessibility? Legal and contractual obligations : ▸ By

    contract with other companies ▸ Mandatory in the USA and UK ▸ Mandatory for public service in France ▸ Soon everywhere in Europe as well -> European Accessibility Act
  16. How Does Accessibility Work?

  17. How Does Accessibility Work?

  18. How Does Accessibility Work? class MyAccessibilityDelegate(private var view: MyView) :

    AccessibilityDelegateCompat() { override fun getAccessibilityNodeProvider(host: View)B AccessibilityNodeProviderCompat { return object : AccessibilityNodeProviderCompat() { override fun createAccessibilityNodeInfo(virtualViewId: Int)B AccessibilityNodeInfoCompat? { when (virtualViewId) { myViewId FG { val node = AccessibilityNodeInfoCompat.obtain(view) node.addChild(view, childId) return node } childId FG { val node = AccessibilityNodeInfoCompat.obtain(view, virtualViewId) node.setParent(view) node.addAction(ACTION_SCROLL_FORWARD) node.addAction(ACTION_SCROLL_BACKWARD) node.setBoundsInScreen(bounds) return node } else FG return null } } override fun performAction( virtualViewId: Int, action: Int, arguments: Bundle )B Boolean { return if (virtualViewId FV myViewId) { view.performAccessibilityAction(action, arguments) } else when (action) { ACTION_SCROLL_FORWARD.id FG { navigateToNextView() true } ACTION_SCROLL_BACKWARD.id FG { navigateToRootView() true } else FG false } } } } }
  19. How Does Accessibility Work? class MyAccessibilityDelegate(private var view: MyView) :

    AccessibilityDelegateCompat() { override fun getAccessibilityNodeProvider(host: View)B AccessibilityNodeProviderCompat { return object : AccessibilityNodeProviderCompat() { override fun createAccessibilityNodeInfo(virtualViewId: Int)B AccessibilityNodeInfoCompat? { when (virtualViewId) { myViewId FG { val node = AccessibilityNodeInfoCompat.obtain(view) node.addChild(view, childId) return node } childId FG { val node = AccessibilityNodeInfoCompat.obtain(view, virtualViewId) node.setParent(view) node.addAction(ACTION_SCROLL_FORWARD) node.addAction(ACTION_SCROLL_BACKWARD) node.setBoundsInScreen(bounds) return node } else FG return null } } override fun performAction( virtualViewId: Int, action: Int, arguments: Bundle )B Boolean { return if (virtualViewId FV myViewId) { view.performAccessibilityAction(action, arguments) } else when (action) { ACTION_SCROLL_FORWARD.id FG { navigateToNextView() true } ACTION_SCROLL_BACKWARD.id FG { navigateToRootView() true } else FG false } } } } }
  20. How Does Accessibility Work? class MyAccessibilityDelegate(private var view: MyView) :

    AccessibilityDelegateCompat() { override fun getAccessibilityNodeProvider(host: View)B AccessibilityNodeProviderCompat { return object : AccessibilityNodeProviderCompat() { override fun createAccessibilityNodeInfo(virtualViewId: Int)B AccessibilityNodeInfoCompat? { when (virtualViewId) { myViewId FG { val node = AccessibilityNodeInfoCompat.obtain(view) node.addChild(view, childId) return node } childId FG { val node = AccessibilityNodeInfoCompat.obtain(view, virtualViewId) node.setParent(view) node.addAction(ACTION_SCROLL_FORWARD) node.addAction(ACTION_SCROLL_BACKWARD) node.setBoundsInScreen(bounds) return node } else FG return null } } override fun performAction( virtualViewId: Int, action: Int, arguments: Bundle )B Boolean { return if (virtualViewId FV myViewId) { view.performAccessibilityAction(action, arguments) } else when (action) { ACTION_SCROLL_FORWARD.id FG { navigateToNextView() true } ACTION_SCROLL_BACKWARD.id FG { navigateToRootView() true } else FG false } } } } }
  21. How Does Accessibility Work? class MyAccessibilityDelegate(private var view: MyView) :

    AccessibilityDelegateCompat() { override fun getAccessibilityNodeProvider(host: View)B AccessibilityNodeProviderCompat { return object : AccessibilityNodeProviderCompat() { override fun createAccessibilityNodeInfo(virtualViewId: Int)B AccessibilityNodeInfoCompat? { when (virtualViewId) { myViewId FG { val node = AccessibilityNodeInfoCompat.obtain(view) node.addChild(view, childId) return node } childId FG { val node = AccessibilityNodeInfoCompat.obtain(view, virtualViewId) node.setParent(view) node.addAction(ACTION_SCROLL_FORWARD) node.addAction(ACTION_SCROLL_BACKWARD) node.setBoundsInScreen(bounds) return node } else FG return null } } override fun performAction( virtualViewId: Int, action: Int, arguments: Bundle )B Boolean { return if (virtualViewId FV myViewId) { view.performAccessibilityAction(action, arguments) } else when (action) { ACTION_SCROLL_FORWARD.id FG { navigateToNextView() true } ACTION_SCROLL_BACKWARD.id FG { navigateToRootView() true } else FG false } } } } }
  22. How Does Accessibility Work? <ConstraintLayout android:layout_width="match_parent" android:importantForAccessibility=“yes|no|noHideDescendants” android:layout_height=“wrap_content">

  23. How Does Accessibility Work? editText.setAccessibilityDelegate { override fun onInitializeAccessibilityNodeInfo(host: View,

    info: AccessibilityNodeInfoCompat) val errorMessage = currentErrorMessage if (errorMessage.isNotBlank()) { info.text = "${editText.text}. Error: $errorMessage" } info.hintText = editText.hint } }
  24. Let’s make our apps accessible!

  25. ▸ follow the material spec ▸ try to get close

    to a 4.5:1 contrast ratio TEXT AND IMAGES
  26. ▸ follow the material spec ▸ try to get close

    to a 4.5:1 contrast ratio TEXT AND IMAGES
  27. COLORS

  28. COLORS

  29. TEXT SIZES

  30. TEXT SIZES

  31. TEXT SIZES <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp"Fe

  32. TEXT SIZES <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp"Fe

  33. TEXT SIZES <ConstraintLayout android:layout_width="match_parent" android:layout_height="32sp"> <TextView android:id="@iid/label" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp"Fe

  34. TEXT SIZES <ConstraintLayout android:layout_width="match_parent" android:layout_height="32sp"> <TextView android:id="@iid/label" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp"Fe

  35. ALSO : DISPLAY SIZE

  36. MAKE YOUR TOUCH TARGETS BIG ENOUGH ▸ 48 dp *

    48 dp should be your smallest target
  37. MAKE YOUR TOUCH TARGETS BIG ENOUGH ▸ Sometimes, your view

    hierarchy makes this hard : use a touch delegate val rect = Rect() view.getHitRect(rect) rect.enlargeBy(8.dp(context)) view.parent.touchDelegate = TouchDelegate(rect, view)
  38. CONTENT DESCRIPTION

  39. CONTENT DESCRIPTION

  40. CONTENT DESCRIPTION

  41. CONTENT DESCRIPTION

  42. CONTENT DESCRIPTION fun ImageView.loadImage(model: ImageModel, builder: LoadRequestBuilder.() FG Unit =

    {}) { contentDescription = model.alt GlideApp.load(model) .apply(builder) .into(this) }
  43. CONTENT DESCRIPTION fun ImageView.loadImage(model: ImageModel, builder: LoadRequestBuilder.() FG Unit =

    {}) { contentDescription = model.alt GlideApp.load(model) .apply(builder) .into(this) } imageView.loadImage(myModel)
  44. CONTENT DESCRIPTION

  45. CONTENT DESCRIPTION <ImageView android:id="@iid/banana" android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAccessibility="no" Fe

  46. CONTENT DESCRIPTION

  47. CONTENT DESCRIPTION Anything that is focusable will be focusable for

    a11y setOnClickListener{} = will be focusable as well
  48. CONTENT DESCRIPTION

  49. CONTENT DESCRIPTION view.contentDescription = builder.toString()

  50. CONTENT DESCRIPTION

  51. FOCUS ORDER

  52. FOCUS ORDER <TextView android:id=“@iid/email" android:nextFocusRight="@iid/my_id" android:nextFocusDown="@iid/my_id"Fe <SomeView android:id=“@iid/my_id” android:nextFocusRight=“@iid/my_next_id" android:nextFocusDown="@iid/my_next_id"Fe

  53. FOCUSING VIEWS fun onDisplayed() { view.requestFocus() }

  54. “CHECKABLE” VIEWS

  55. “CHECKABLE” VIEWS inline fun <reified T> T.setupCheckableForAccessibility() where T :

    View, T : Checkable { setAccessibilityNodeInfo { _, info FG info.isCheckable = true info.isChecked = isChecked } }
  56. “CHECKABLE” VIEWS inline fun <reified T> T.setupCheckableForAccessibility() where T :

    View, T : Checkable { setAccessibilityNodeInfo { _, info FG info.isCheckable = true info.isChecked = isChecked info.className = "android.widget.Switch" } }
  57. HOW TO DO ALL THIS?

  58. HOW TO DO ALL THIS? It’s a process!

  59. HOW TO DO ALL THIS? Build tools & reusable components

    : ‣ Atomic design system with a11y handling ‣ Helper methods to automatically handle e.g. focus
  60. REFINEMENTS class LoadingAccessibilityMessenger(…) { fun onEvent(event: LceFFn) { val message

    = when (event) { is Lce.Loading FG messages.loading is Lce.Error FG messages.error is Lce.Data FG messages.loaded } view.announceForAccessibility(message) } }
  61. HOW TO DO ALL THIS? Build tools & reusable components

    : ‣ Atomic design system with a11y handling ‣ Helper methods to automatically handle e.g. focus ‣ Accessibility handguide
  62. HOW TO DO ALL THIS?

  63. Build tools & reusable components : ‣ Atomic design system

    with a11y handling ‣ Helper methods to automatically handle e.g. focus ‣ Accessibility handguide ‣ Onboard new colleagues ‣ Code review checklist ‣ A11y audits HOW TO DO ALL THIS?
  64. HOW TO DO ALL THIS?

  65. HOW TO DO ALL THIS?

  66. ACCESSIBILITY CHECKLIST ▸ make sure to have enough contrast ▸

    don’t rely only on color to signal error / success ▸ support sp fonts without breaking your layouts ▸ make sure your touch targets are at least 48dp x 48dp ▸ provide content description as needed ▸ make sure that the focus handling is correct