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

Navigation Focus Support in 2024: Support Keybo...

Tiphaine
September 12, 2024

Navigation Focus Support in 2024: Support Keyboard Navigation with Compose!

DroidKaigi 2024 - 2024/9/12
English version of the slides
https://2024.droidkaigi.jp/en/timetable/694196/

Video:
https://www.youtube.com/watch?v=poJg1A2xL-o

Tiphaine

September 12, 2024
Tweet

More Decks by Tiphaine

Other Decks in Technology

Transcript

  1. 2 Tiphaine • Android engineer @ DeNA (Pococha) • DroidKaigi

    volunteer staff • Mobile Dev Japan co-organizer
  2. 3 Slides & Video: It's 2021, let's support accessibility (Japanese

    only) Better late than never ✌🤠✌ Today is a sequel to this talk
  3. 6

  4. 7

  5. 9 • Alternative way to interact with a smartphone without

    using the touch screen • Move through the UI one element at a time Focus Navigation Focus Elements Ref: Google Play Store
  6. Focus Focus 10 • Current position in the UI •

    Can only interact with this element • Element will have some visual changes when focused Ref: Google Play Store
  7. Focus 11 • Accessibility Focus ≠ Navigation Focus • Navigation

    Focus is sometimes called “Keyboard Navigation” • Both focuses are Focus Navigation variants 😵💫 Focus Types
  8. Don’t touch the screen 👋 👀 Don’t see the screen

    13 Navigation Focus Accessibility Focus
  9. Keyboard, Switch Access, D-pad (arrow keys)… Screen readers like TalkBack

    16 focusable attribute importantForAccessibility and focusable attributes Only actionable elements All elements Navigation Focus Accessibility Focus
  10. 17 • Hardware keyboard (tablets, Chromebooks) • On-screen keyboard (soft

    input keyboard) Image: Sergi Kabrera Devices for Nav Focus
  11. 18 • TV remote controller • Smartwatches rotating bezel •

    Game controller • Switch Access Image: Erik Mclean Devices for Nav Focus
  12. 19 Changeable indicator Only one default indicator Keyboard, Switch Access,

    D-pad (arrow keys)… Screen readers like TalkBack focusable attribute importantForAccessibility and focusable attributes Only actionable elements All elements Navigation Focus Accessibility Focus
  13. 22 For more info on Accessibility Focus, check out my

    2021 talk 💁 Slides & Video: It's 2021, let's support accessibility (Japanese only)
  14. 24 • Using a D-pad (directional pad) or arrow keys

    • Two-dimensional navigation • Focus goes up, down, left, or right Directional Navigation
  15. 25 • Using “tab” key or Switch Access • One-dimensional

    navigation (forward or backward) • Focus follows the order in which elements appear in the layout Tab Navigation 1 2 3
  16. 30 • Make actionable elements focusable (if you don’t use

    OnClickListener) // Also focusable with // Accessibility Focus android:focusable="true" Handle click actions
  17. myView.setOnLongClickListener { // Do something true } // Set this

    for any listener 
 // except setOnClickListener⚠ myView.isFocusable = true 32
  18. 34 Focusable, but nothing happens 😢 true } myView.isFocusable =

    true view.performClick() myView.setOnTouchListener { view, event -> // Do something with the event
  19. 35 • Nav Focus can’t trigger OnTouchListener 😣 • Need

    a different way to detect input events 👉 Listen to KeyEvents instead Handle gesture-based actions
  20. myView.setOnKeyListener { view, keyCode, event -> 36 } // Choose

    which one(s) you want to support KeyEvent.KEYCODE_ENTER, ... -> { // Do something true } else -> false when (keyCode) { Ref: Android Developers }
  21. myView.setOnKeyListener { view, keyCode, event -> } when (keyCode) {

    } 37 Don’t forget to parse duplicate events ⚠ when (keyCode) { ... } // The same event will come several times if (event.action != KeyEvent.ACTION_UP) { return@setOnKeyListener false }
  22. override fun onKeyUp(keyCode: Int, event: KeyEvent?)
 : Boolean { }

    return when (keyCode) { KeyEvent.KEYCODE_ENTER -> performClick() else -> ... } 38 Use this instead for custom Views 🚀
  23. 40 • Skip non-interactive elements • Wait for an action

    to initiate changes // Skip this View android:focusable=“false” Meet the user’s expectations
  24. 41 • Make sure the focus can always be removed

    • Verify the Esc key allows to move away • Double check your WebViews, scrolls and drop-down lists Prevent keyboard traps
  25. 43 • Keep the flow logical and consistent // Tab

    navigation
 android:nextFocusForward=“@id/...” // Directional navigation // Can also set up, down and left android:nextFocusRight=“@id/...” Maintain the expected flow
  26. 44 Facilitate navigation Ref: Android Developers // Available from API

    26+ android:keyboardNavigationCluster • Group elements with clusters 1 2 3 4 10 11 12 Main content Bottom nav bar Top tabs
  27. 45 • Request the focus when the start is obvious

    // Available from API 26+ // Won’t show on-screen keyboard <EditText ... android:focusedByDefault="true"/> Facilitate navigation
  28. 48 • Ensure the focus is visible • Customize the

    indicator color and/or shape when necessary Don’t let the user get lost Ref: Google Play Store app in 2021
  29. Customize the indicator: whole app 49 <!-- themes.xml --> <style

    name=“AppTheme" ...> ... <item name="colorControlHighlight">...</item> </style> Customize the indicator
  30. 50 <!-- outline.xml --> <shape ... android:shape="rectangle"> ... <stroke android:width="4dp"

    android:color=“@color/black” /> </shape> Customize the indicator: single View ᶃ Customize the indicator: single View Customize the indicator
  31. <!-- focus_selector.xml --> <selector ...> </selector> 51 <item android:drawable=“@drawable/outline” 


    android:state_focused="true" /> <item android:drawable="@color/transparent"/> Customize the indicator: single View ᶄ Customize the indicator: single View Customize the indicator
  32. // Set click effect as state_pressed in selector <Button ...

    android:foreground=“@drawable/focus_selector"/> 52 Customize the indicator: single View ᶅ Customize the indicator: single View Customize the indicator
  33. 01 54 Introduction to Focus Common Problems Focus in Compose

    02 03 Introduction to Focus Common Problems
  34. 57 • Focus follows the declaration order of Composables •

    Priorities goes to elements inside the same level Tab Navigation flow // Level ᶃ // Level ᶄ // Level ᶄ Column { Row { Row { } } } ... ...
  35. 59 Row { Column { } Column { } }

    CustomButton("1") CustomButton("2") CustomButton("3") CustomButton("4")
  36. Modifier.focusable() 62 • Need to provide a focus indicator and

    handle the displaying logic by yourself 😭 Make the element focusable
  37. 63 Modifier var color by remember { .focusable() mutableStateOf(transparent) }

    // Modifier of your Composable Must set focusable as the last Modifier ⚠
  38. 64 Modifier .border(5.dp, indicatorColor) .onFocusChanged { focusState -> } color

    = if (focusState.isFocused) { black } else { transparent } var color by remember { ...} .focusable() 🎉
  39. 65 Modifier.clickable { // Do something } • With this

    Modifier, the Composable will automatically become focusable Handle click actions
  40. 67 Handle other actions 👉 Listen to KeyEvents instead (Again)

    • Can’t trigger anything (including long click) 😢
  41. 68 Listen to KeyEvents Modifier.onPreviewKeyEvent {} Modifier.onKeyEvent {} // Parent

    callback is invoked first // Start from children callback
  42. 69 Modifier.onKeyEvent { keyEvent -> } when (keyEvent.key) { Modifier.onKeyEvent

    Key.Enter -> { // Do something true } else -> false }
  43. 70 // Parse duplicates if (keyEvent.type != KeyEventType.KeyUp) { return@onPreviewKeyEvent

    false } when (keyEvent.key) { } when (keyEvent.key) { ... } Modifier.onKeyEvent { keyEvent -> } Modifier.onKeyEvent
  44. 73 Change the flow - Tab Navigation Modifier.focusProperties { }

    next = } previous = ... ... • Use FocusProperties with FocusRequester Change the flow
  45. Modifier 75 val (first, second) = remember { FocusRequester.createRefs() }

    first // First Composable // Second Composable (Destination) Modifier.focusRequester( ) ) second .focusRequester(
  46. 76 val (first, second) = remember { FocusRequester.createRefs() } Modifier

    .focusProperties { } second next = first) .focusRequester(
  47. 77 // Won’t be used The top most Modifier wins

    ⚠ Modifier .focusProperties { } .focusProperties { previous = second } next = second
  48. 78 Modifier.focusProperties { left = ... right = ... up

    = ... down = ... } Change the flow - Directional Nav Change the flow
  49. 79 val focusManager = LocalFocusManager.current Move the focus focusManager.moveFocus( )

    FocusDirection.Down // Go to the nearest focusable Composable // Direction you want the focus to go
  50. 80 • Go wherever you want (as long as it’s

    focusable) val requester = remember { FocusRequester() } requester.requestFocus() Request the focus
  51. Button( onClick = { ) { ... } TextField(... //

    If clicked, move to the TextField 81 ) }
  52. 82 Button( onClick = { ) { ... } TextField(...

    } ) val requester = remember { FocusRequester() } Modifier.focusRequester(requester) // If clicked, move to the TextField
  53. 83 Button( onClick = { ) { ... } TextField(...

    } ) val requester = remember { FocusRequester() } Modifier.focusRequester(requester) requester.requestFocus() // If clicked, move to the TextField
  54. 85 ᶃ Plug it to your phone ᶄ Tap any

    key ᶅ The focus indicator will appear (Same steps with other external tools 🎮) With a hardware keyboard
  55. 87 • D-pad navigation is enabled by default with pre-set

    hardware profiles ✅ • For custom profiles, select the option when creating the device 1⃣ Use an emulator
  56. 88

  57. 89

  58. 90

  59. 92 • Use a real device through AndroidStudio • Preferences

    → Tools → Device mirroring 2⃣ Use device mirroring
  60. 93

  61. 95 • Focus in Compose ɹ- Android Developers official documentation

    • Android App Development: Accessibility ɹ(LinkedIn Learning) - Renato Iwashima References