Slide 1

Slide 1 text

@francoisblavoet Accessibility Made Easy

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

What Is Accessibility?

Slide 4

Slide 4 text

How?

Slide 5

Slide 5 text

How? Switch Access Voice Access And many more a11y services

Slide 6

Slide 6 text

How?

Slide 7

Slide 7 text

How?

Slide 8

Slide 8 text

How?

Slide 9

Slide 9 text

How?

Slide 10

Slide 10 text

How? And many, many more devices !

Slide 11

Slide 11 text

How?

Slide 12

Slide 12 text

How?

Slide 13

Slide 13 text

How?

Slide 14

Slide 14 text

Why Implement Accessibility? ▸ It’s the right thing to do !
 ▸ Untapped market
 ▸ Legal and contractual obligations

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

How Does Accessibility Work?

Slide 17

Slide 17 text

How Does Accessibility Work?

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

How Does Accessibility Work?

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Let’s make our apps accessible!

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

COLORS

Slide 28

Slide 28 text

COLORS

Slide 29

Slide 29 text

TEXT SIZES

Slide 30

Slide 30 text

TEXT SIZES

Slide 31

Slide 31 text

TEXT SIZES

Slide 32

Slide 32 text

TEXT SIZES

Slide 33

Slide 33 text

TEXT SIZES

Slide 34

Slide 34 text

TEXT SIZES

Slide 35

Slide 35 text

ALSO : DISPLAY SIZE

Slide 36

Slide 36 text

MAKE YOUR TOUCH TARGETS BIG ENOUGH ▸ 48 dp * 48 dp should be your smallest target

Slide 37

Slide 37 text

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)

Slide 38

Slide 38 text

CONTENT DESCRIPTION

Slide 39

Slide 39 text

CONTENT DESCRIPTION

Slide 40

Slide 40 text

CONTENT DESCRIPTION

Slide 41

Slide 41 text

CONTENT DESCRIPTION

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

CONTENT DESCRIPTION

Slide 45

Slide 45 text

CONTENT DESCRIPTION

Slide 46

Slide 46 text

CONTENT DESCRIPTION

Slide 47

Slide 47 text

CONTENT DESCRIPTION Anything that is focusable will be focusable for a11y setOnClickListener{} = will be focusable as well

Slide 48

Slide 48 text

CONTENT DESCRIPTION

Slide 49

Slide 49 text

CONTENT DESCRIPTION view.contentDescription = builder.toString()

Slide 50

Slide 50 text

CONTENT DESCRIPTION

Slide 51

Slide 51 text

FOCUS ORDER

Slide 52

Slide 52 text

FOCUS ORDER

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

“CHECKABLE” VIEWS

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

“CHECKABLE” VIEWS inline fun T.setupCheckableForAccessibility() where T : View, T : Checkable { setAccessibilityNodeInfo { _, info FG info.isCheckable = true info.isChecked = isChecked info.className = "android.widget.Switch" } }

Slide 57

Slide 57 text

HOW TO DO ALL THIS?

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

HOW TO DO ALL THIS? Build tools & reusable components : ‣ Atomic design system with a11y handling ‣ Helper methods to automatically handle e.g. focus

Slide 60

Slide 60 text

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) } }

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

HOW TO DO ALL THIS?

Slide 63

Slide 63 text

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?

Slide 64

Slide 64 text

HOW TO DO ALL THIS?

Slide 65

Slide 65 text

HOW TO DO ALL THIS?

Slide 66

Slide 66 text

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