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

Accessibility made Easy

Accessibility made Easy

Android Makers 2020

Avatar for François Blavoet

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