Share
Embed
Transcript
71 slides
-
Accessibility and Android APIs Aung Kyaw Paing (Vincent) Senior Consultant
@ thoughtworks | GDE Thailand aungkyawpaing.dev -
Over 1 billion people are estimated to experience disability. This
corresponds to about 15% of the world's population - World Health Organization (WHO) -
Deafness & Hard-of-hearing Motor Impairments Cognitive disabilities Blindness & Visual
impairment Everyone could experiences one of these in their lives. -
- Won “Innovation in Accessibility” award for the first time
in Game awards - Even people without common disabilities start using these features! The Last of Us -
- Screen reader - Describe content out aloud - Interact
with gestures Talkback -
- Integration with braille display - Used together with Talkback
- Interact using keys on display BrailleBack -
Switch Access - Switches instead of taps - Scan items
and highlight - User can interact with switches -
Voice Access - Control hand-free - Show labels and grids
- Interact device with voice commands -
Size your buttons - Recommended size 48dp - Use padding
to expand touchable area -
Think twice about colors - Do not use color as
only means of communication -
- Do not use color as only means of communication
Think twice about colors -
Favorite/Like/Love Search Open Navigation Drawer Label images - Provide a
screen readable label of your image and actions - Translated for different languages -
<ImageView android:id="@+id/event_banner" android:contentDescription="20% discount sale event" /> <menu> <item android:id="@+id/action_settings"
android:title="@string/action_settings" android:contentDescription="Settings" /> </menu> -
<ImageView android:id="@+id/event_banner" android:contentDescription="20% discount sale event" /> <menu> <item android:id="@+id/action_settings"
android:title="@string/action_settings" android:contentDescription="Settings" /> </menu> view.contentDescription = "Describe me!" -
Icon( imageVector = Icons.Filled.KeyboardArrowLeft, contentDescription = "Next" ) Image( bitmap
= ImageBitmap.imageResource(id = R.id.arrow_next), contentDescription = "" ) -
Icon( imageVector = Icons.Filled.KeyboardArrowLeft, contentDescription = "Next" ) Image( bitmap
= ImageBitmap.imageResource(id = R.id.arrow_next), contentDescription = "" ) Text( text = "Hello There", modifier = Modifier.semantics { contentDescription = "General Kenobi" } ) -
Provide users a way to add the content description for
user-generated contents -
- Skip unnecessary elements for screen reader to navigate faster
Skip elements ???????? -
<ImageView android:id="@+id/event_banner" android:contentDescription="@null" /> <!-- Only for SDK 16 and
above --> <ImageView android:id="@+id/event_banner" android:importantForAccessibility="no" /> -
ViewCompat.addAccessibilityAction( itemView, getText(R.id.delete) ) { _, _ -> deleteMail() true
} “Double tap to delete” -
fun InboxRow() { Row( modifier = Modifier .semantics { onClick(label
= "Delete") { deleteMail() return@onClick true } } ) { // Render Contents } } -
fun InboxRow() { Row( modifier = Modifier .semantics { onClick(label
= "Delete") { deleteMail() return@onClick true } } ) { // Render Contents } } -
fun InboxRow() { Row( modifier = Modifier .semantics { onClick(label
= "Delete") { deleteMail() return@onClick true } } ) { // Render Contents } } -
Replace accessibility actions Sometimes a click is not just a
click “Double Tap to Press” -
@Composable fun ClickableRow( text: String, onClick: () -> Unit )
{ Row( modifier = Modifier .clickable() { onClick() } ) { //Render Text and Arrow Right Icon } } “Double tap to click” -
@Composable fun ClickableRow( text: String, onClick: () -> Unit )
{ Row( modifier = Modifier .clickable(onClickLabel = "Open $text") { onClick() } ) { //Render Text and Arrow Right Icon } } “Double tap to open $text” -
Merge components - Merge views to make screen reader navigate
with fewer taps - Recommend to use for Lists + “Checkbox Checked” “With Soup” “Checkbox With Soup - Checked” -
<ConstraintLayout android:id="@+id/project_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/project_title" ... android:focusable="false" android:text="droidconsg" />
<TextView android:id="@+id/build_time" android:focusable="false" android:text="3 Hours Ago" /> </ConstraintLayout> -
<ConstraintLayout android:id="@+id/project_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/project_title" ... android:focusable="false" android:text="droidconsg" />
<TextView android:id="@+id/build_time" android:focusable="false" android:text="3 Hours Ago" /> </ConstraintLayout> -
<ConstraintLayout android:id="@+id/project_container" ... android:screenReaderFocusable="true" android:contentDescription="droiconsg built 3 hours ago"> <TextView
android:id="@+id/project_title" ... android:focusable="false" android:text="droidconsg" /> <TextView android:id="@+id/build_time" android:focusable="false" android:text="3 Hours Ago" /> </ConstraintLayout> -
fun ProjectCard() { Column(modifier = Modifier.Semantic( mergeDescendants = true )
{ //Other accessibility stuffs }) { Text("droidconsg") Text("3 Hours Ago") } } -
fun ProjectCard() { Column(modifier = Modifier.Semantic( mergeDescendants = true )
{ onClick(label = "Do Something") { //On Click actions } }) { BookmarkButton() } } -
fun ProjectCard() { Column(modifier = Modifier.Semantic( mergeDescendants = true )
{ onClick(label = "Do Something") { //On Click actions } }) { BookmarkButton(modifier = Modifier.clearAndSetSemantics { }) } } -
Describe the state - Telling screen reader to alert user
for state change on interaction Paused Playing Image ref: https://medium.com/google-dev eloper-experts/state-description s-on-android-b2029283871f -
playPauseButton.setOnClickListener { if (isPlaying) { ViewCompat.setStateDescription(playPauseButton, getString(R.string.paused)) } else {
ViewCompat.setStateDescription(playPauseButton, getString(R.string.playing)) } } -
playPauseButton.setOnClickListener { if (isPlaying) { ViewCompat.setStateDescription(playPauseButton, getString(R.string.paused)) } else {
ViewCompat.setStateDescription(playPauseButton, getString(R.string.playing)) } } -
@Composable fun MusicPlayerControls { PlayPauseButton(modifier = Modifier.semantics { stateDescription =
if (isPlaying) "Paused" else "Playing" }) } -
@Composable fun MusicPlayerControls { PlayPauseButton(modifier = Modifier.semantics { stateDescription =
if (isPlaying) "Paused" else "Playing" }) } -
Timeout - Do not use constant timeouts - Instead, use
recommended timeout from Accessibility manager -
val accessibilityManager = LocalAccessibilityManager.current accessibilityManager?.calculateRecommendedTimeoutMillis( originalTimeoutMillis = DEFAULT_TIMEOUT, containsIcons =
true, containsText = false, containsControls = false ) -
val accessibilityManager = LocalAccessibilityManager.current accessibilityManager?.calculateRecommendedTimeoutMillis( originalTimeoutMillis = DEFAULT_TIMEOUT, containsIcons =
true, containsText = false, containsControls = false ) -
val accessibilityManager = LocalAccessibilityManager.current accessibilityManager?.calculateRecommendedTimeoutMillis( originalTimeoutMillis = DEFAULT_TIMEOUT, containsIcons =
true, containsText = false, containsControls = false ) -
val accessibilityManager = LocalAccessibilityManager.current accessibilityManager?.calculateRecommendedTimeoutMillis( originalTimeoutMillis = DEFAULT_TIMEOUT, containsIcons =
true, containsText = false, containsControls = false ) -
Anti-patterns - Do not reinvent the wheel - Use material
or androidx components as much as possible - Do not abuse accessibility announcements val event = AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) event.text.add("You got a new notification!") accessibilityManager.sendAccessibilityEvent(event) -
Anti-patterns - Do not reinvent the wheel - Use material
or androidx components as much as possible - Do not abuse accessibility announcements val event = AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) event.text.add("You got a new notification!") accessibilityManager.sendAccessibilityEvent(event) -
Check List - Adjust button sizes, text sizes and color
contrast - Label images - Skip unnecessary elements - Provide accessibility actions - Replace with meaningful actions - Adjust timeout - Merge elements -
Automated Testing - Espresso provides a one-line code to test
accessibility - Runs a list of test every time you interact with the view in a test class EspressoTest { init { AccessibilityChecks.enable() } } -
Automated Testing There were 2 accessibility errors: AppCompatImageButton{id=2131165210, ...}: View
is missing speakable text needed for a screen reader, AppCompatImageButton{id=2131165210,...}: View falls below the minimum recommended size ... ... -
Compose UI Test composeTestRule .onNodeWithContentDescription("Content Description") .assertIsDisplayed() val matcher =
hasStateDescription("state value") composeTestRule.onNode(matcher).assertIsDisplayed() -
Accessibility Scanner - Use accessibility scanner to check for best
practices https://bit.ly/3Sp7SKz -
“Accessibility isn’t something that you check the list and say
it’s done. It’s an user experience so you have to test it manually by yourself to know it’s a good experience”