Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Accessibility made Easy

Accessibility made Easy

Android Makers 2020

François Blavoet

April 21, 2020
Tweet

More Decks by François Blavoet

Other Decks in Programming

Transcript

  1. 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
  2. Why Implement Accessibility? ▸ It’s the right thing to do

    !
 ▸ Untapped market
 ▸ Legal and contractual obligations
  3. 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
  4. 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 } } } } }
  5. 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 } } } } }
  6. 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 } } } } }
  7. 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 } } } } }
  8. 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 } }
  9. ▸ follow the material spec ▸ try to get close

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

    to a 4.5:1 contrast ratio TEXT AND IMAGES
  11. MAKE YOUR TOUCH TARGETS BIG ENOUGH ▸ 48 dp *

    48 dp should be your smallest target
  12. 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)
  13. CONTENT DESCRIPTION fun ImageView.loadImage(model: ImageModel, builder: LoadRequestBuilder.() FG Unit =

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

    {}) { contentDescription = model.alt GlideApp.load(model) .apply(builder) .into(this) } imageView.loadImage(myModel)
  15. CONTENT DESCRIPTION Anything that is focusable will be focusable for

    a11y setOnClickListener{} = will be focusable as well
  16. “CHECKABLE” VIEWS inline fun <reified T> T.setupCheckableForAccessibility() where T :

    View, T : Checkable { setAccessibilityNodeInfo { _, info FG info.isCheckable = true info.isChecked = isChecked } }
  17. “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" } }
  18. HOW TO DO ALL THIS? Build tools & reusable components

    : ‣ Atomic design system with a11y handling ‣ Helper methods to automatically handle e.g. focus
  19. 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) } }
  20. 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
  21. 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?
  22. 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