Going edge-to-edge with Gesture Navigation

Going edge-to-edge with Gesture Navigation

To aid developers to create immersive experiences, Android Q adds new system navigation models to allow apps to take over more of the user’s screen.

This talk will look at how apps should work with the new gesture navigation, and ways to mitigate gesture conflicts. We’ll look at how to update apps to go edge-to-edge, drawing behind the system bars to create a immersive experience for users. Finally we’ll go through ways to make the WindowInsets APIs easier to use.

86f746f6c0c522ab2261bd55791d8a4a?s=128

Chris Banes

July 02, 2019
Tweet

Transcript

  1. Going edge-to-edge Gesture Navigation

  2. Chris Banes @chrisbanes @rohanscloud Rohan Shah

  3. None
  4. Which nav will Android use in the future? 3-button mode

    is required for Android devices Gestural nav may be offered as a polished, immersive experience, and can additionally be offered out-of-box
  5. Why is this so important for apps? Gaining an edge

    on user experience given market desire for beautiful UI Handling conflicts with gestures for optimal UX
  6. Making your app nav-ready 1. Make your UI edge-to-edge 2.

    Leverage insets for better UI 3. Override system gestures
  7. What is
 edge-to-edge?

  8. 10:00

  9. App bounds 10:00

  10. None
  11. None
  12. 10:00

  13. 10:00

  14. 10:00 Drawing behind the navigation bar Strongly recommended on Q

    Optional before Q
  15. 10:00

  16. 10:00

  17. 10:00 Drawing behind the status bar Recommended on Q Optional

    before Q
  18. 10:00

  19. 10:00

  20. Going edge-to-edge

  21. System will help From Q onwards, the system is responsible

    for protecting system buttons/handles
  22. System will help From Q onwards, the system is responsible

    for protecting system buttons/handles
  23. System will help From Q onwards, the system is responsible

    for protecting system buttons/handles Protection can be in the form of dynamic color adaptation
  24. System will help From Q onwards, the system is responsible

    for protecting system buttons/handles Or the system can draw a translucent scrim Shown in button mode when targetSdkVersion >= 29
  25. Going immersive Avoid overlaps with system UI Request to be

    laid out fullscreen Change system bar colors 1 2 3
  26. <style name="Theme.MyApp" parent="..."> <item name="android:navigationBarColor"> @android:color/transparent </item> </style> values-v29/themes.xml Change

    system bar colors 1
  27. <style name="Theme.MyApp" parent="..."> <!-- 70% white --> <item name="android:navigationBarColor"> #B3FFFFFF

    </item> </style> values/themes.xml Change system bar colors 1
  28. <style name="Theme.MyApp" parent="..."> <!-- 70% black --> <item name="android:navigationBarColor"> #B3000000

    </item> </style> values-night/themes.xml Change system bar colors 1
  29. view.systemUiVisibility = // Tells the system that you wish to

    be laid out // as if the navigation bar was hidden View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or // Tells the system that you wish to be laid out at // the most extreme scenario of any other flags View.SYSTEM_UI_FLAG_LAYOUT_STABLE Request to be laid out fullscreen 2
  30. view.systemUiVisibility = // Tells the system that you wish to

    be laid out // as if the navigation bar was hidden View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or // Optional, if we want you be laid out fullscreen, // behind the status bar View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or // Tells the system that you wish to be laid out at // the most extreme scenario of any other flags View.SYSTEM_UI_FLAG_LAYOUT_STABLE Request to be laid out fullscreen 2
  31. Insets

  32. What are insets? A collection of values which tell you

    how much to inset (move) content in by Which content to move, depends on the inset type and its purpose
  33. I N S E T S System window Tells you

    where the system- ui is being displayed over your app Use to move clickable views away from edges
  34. I N S E T S System window Tells you

    where the system- ui is being displayed over your app Use to move clickable views away from edges
  35. System window I N S E T S Tells you

    where the system- ui is being displayed over your app Use to move clickable views away from edges
  36. System window I N S E T S Tells you

    where the system- ui is being displayed over your app Use to move clickable views away from edges
  37. System gesture Represents the areas of the window where system

    gestures take priority Includes the vertical edges for swiping back, and bottom edge for home Use to move draggable views away from edges New in Q I N S E T S
  38. System gesture New in Q I N S E T

    S Represents the areas of the window where system gestures take priority Includes the vertical edges for swiping back, and bottom edge for home Use to move draggable views away from edges
  39. System gesture New in Q I N S E T

    S Represents the areas of the window where system gestures take priority Includes the vertical edges for swiping back, and bottom edge for home Use to move draggable views away from edges
  40. System gesture New in Q I N S E T

    S Represents the areas of the window where system gestures take priority Includes the vertical edges for swiping back, and bottom edge for home Use to move draggable views away from edges
  41. System gesture New in Q I N S E T

    S Represents the areas of the window where system gestures take priority Includes the vertical edges for swiping back, and bottom edge for home Use to move draggable views away from edges
  42. Mandatory 
 system gesture Subset of system gesture insets Defines

    areas which cannot be overridden by apps In Q, currently only used by the home gesture zone I N S E T S New in Q
  43. System window insets Use to move clickable views away from

    edges System gesture insets Use to move draggable views away from edges Mandatory system gesture insets Use to check what gesture areas can not be excluded
  44. So how do I actually handle insets? WindowInsets

  45. ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> // Do something with the

    insets // return the insets insets } Tip: always use the ViewCompat method
  46. getSystemWindowInsets(): Insets getSystemGestureInsets(): Insets getMandatorySystemGestureInsets(): Insets getStableInsets(): Insets WindowInsetsCompat

  47. getSystemWindowInsets(): Insets getSystemGestureInsets(): Insets getMandatorySystemGestureInsets(): Insets getStableInsets(): Insets WindowInsetsCompat data

    class Insets( left: Int, top: Int, right: Int, bottom: Int )
  48. None
  49. <style name="Theme.MyApp" parent="..."> <item name="android:navigationBarColor"> @android:color/transparent </item> </style> values-v29/themes.xml

  50. <style name="Theme.MyApp" parent="..."> <item name="android:navigationBarColor"> @android:color/transparent </item> </style> values-v29/themes.xml

  51. view.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or SYSTEM_UI_FLAG_LAYOUT_STABLE

  52. view.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or SYSTEM_UI_FLAG_LAYOUT_STABLE

  53. view.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or SYSTEM_UI_FLAG_LAYOUT_STABLE

  54. VC.setOnApplyWindowInsetsListener(bottomNav) { v, insets -> v.updatePadding( bottom = insets.systemWindowInsets.bottom )

    insets }
  55. VC.setOnApplyWindowInsetsListener(bottomNav) { v, insets -> v.updatePadding( bottom = insets.systemWindowInsets.bottom )

    insets }
  56. VC.setOnApplyWindowInsetsListener(bottomNav) { v, insets -> v.updatePadding( bottom = insets.systemWindowInsets.bottom )

    insets }
  57. VC.setOnApplyWindowInsetsListener(bottomNav) { v, insets -> v.updateLayoutParams<MarginLayoutParams>( bottomMargin = insets.systemWindowInsets.bottom )

    insets }
  58. VC.setOnApplyWindowInsetsListener(bottomNav) { v, insets -> v.updateLayoutParams<MarginLayoutParams>( bottomMargin = insets.systemWindowInsets.bottom )

    insets }
  59. None
  60. None
  61. <LinearLayout android:padding="32dp">

  62. After applying all of the steps...

  63. VC.setOnApplyWindowInsetsListener(bottomBar) { v, insets -> v.updatePadding( bottom = insets.systemWindowInsets.bottom )

    insets } <LinearLayout android:padding="32dp"> Overwrites
  64. VC.setOnApplyWindowInsetsListener(bottomBar) { v, insets -> v.updatePadding( bottom = bottomPad +

    insets.systemWindowInsets.bottom ) insets } val bottomPad = bottomBar.bottomPadding // 32dp
  65. VC.setOnApplyWindowInsetsListener(bottomBar) { v, insets -> v.updatePadding( bottom = bottomPad +

    insets.systemWindowInsets.bottom ) insets } val bottomPad = bottomBar.bottomPadding
  66. data class InitialPadding(
 val left: Int, val top: Int, val

    right: Int, val bottom: Int
 ) private fun recordInitialPaddingForView(view: View) = ... fun View.doOnApplyWindowInsets(f: (View, WindowInsets, InitialPadding) -> Unit) { // Create a snapshot of the view's padding state val initialPadding = recordInitialPaddingForView(this) // Set an OnApplyWindowInsetsListener which proxies to the given // lambda, passing in the original padding state ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets -> f(v, insets, initialPadding) // Always return the insets, so that children can also use them insets } }
  67. data class InitialPadding(
 val left: Int, val top: Int, val

    right: Int, val bottom: Int
 ) private fun recordInitialPaddingForView(view: View) = ... fun View.doOnApplyWindowInsets(f: (View, WindowInsets, InitialPadding) -> Unit) { // Create a snapshot of the view's padding state val initialPadding = recordInitialPaddingForView(this) // Set an OnApplyWindowInsetsListener which proxies to the given // lambda, passing in the original padding state ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets -> f(v, insets, initialPadding) // Always return the insets, so that children can also use them insets } }
  68. val right: Int, val bottom: Int
 ) private fun recordInitialPaddingForView(view:

    View) = ... fun View.doOnApplyWindowInsets(f: (View, WindowInsets, InitialPadding) -> Unit) { // Create a snapshot of the view's padding state val initialPadding = recordInitialPaddingForView(this) // Set an OnApplyWindowInsetsListener which proxies to the given // lambda, passing in the original padding state ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets -> f(v, insets, initialPadding) // Always return the insets, so that children can also use them insets } }
  69. bottomBar.doOnApplyWindowInsets { v, insets, initialPadding -> v.updatePadding( bottom = initialPadding.bottom

    + insets.systemWindowInsets.bottom ) insets }
  70. - Nick Butcher

  71. @BindingAdapter( "paddingLeftSystemWindowInsets", "paddingTopSystemWindowInsets", "paddingRightSystemWindowInsets", "paddingBottomSystemWindowInsets", requireAll = false ) fun

    applySystemWindows( view: View, applyLeft: Boolean, applyTop: Boolean, applyRight: Boolean, applyBottom: Boolean ) { view.doOnApplyWindowInsets { view, insets, padding -> val left = if (applyLeft) insets.systemWindowInsetLeft else val top = if (applyTop) insets.systemWindowInsetTop else 0
  72. <androidx.recyclerview.RecyclerView android:clipToPadding="false" app:paddingBottomSystemWindowInsets="@{ true }" /> No more handling insets

    in code !
  73. None
  74. Overriding gestures

  75. What conflicts will apps face? Swiping in from the edges

    will go back or re-activate system gestures when in immersive mode. Any horizontally-draggable content that relies on gestures in the back zone Examples: sliders, seek bars, drag handles, etc.
  76. What conflicts will apps face? Swiping in from the edges

    will go back or re-activate system gestures when in immersive mode. Any horizontally-draggable content that relies on gestures in the back zone Examples: sliders, seek bars, drag handles, etc.
  77. Overriding gestures New API available to you for opting out

    a small part of the edge for app use Should be used extremely sparingly and tied to a draggable handle or visual affordance New in Q
  78. View.setSystemGestureExclusionRects(List<Rect> rects)

  79. List<Rect> exclusionRects; public void onLayout(...) { // Update rect bounds

    and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java
  80. List<Rect> exclusionRects; public void onDraw(Canvas canvas) { // Update rect

    bounds and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java
  81. Restrictions on overriding gestures Apps can only opt out 200

    dp of each edge If apps opt out of more, only the bottom 200 dp will be overridden for the app Requested
  82. Restrictions on overriding gestures Apps can only opt out 200

    dp of each edge If apps opt out of more, only the bottom 200 dp will be overridden for the app Honored Requested
  83. Non-sticky immersive mode Apps can only opt out 200 dp

    of each edge from bringing in system bars and activating gestures
  84. Non-sticky immersive mode Apps can only opt out 200 dp

    of each edge from bringing in system bars and activating gestures
  85. Non-sticky immersive mode Apps can only opt out 200 dp

    of each edge from bringing in system bars and activating gestures
  86. Non-sticky immersive mode Apps can only opt out 200 dp

    of each edge from bringing in system bars and activating gestures
  87. Non-sticky immersive mode Apps can only opt out 200 dp

    of each edge from bringing in system bars and activating gestures
  88. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  89. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  90. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  91. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  92. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  93. Sticky immersive mode By updating, apps can opt the entire

    edge out of bringing in system bars and reactivating gestures Once the gestures are active, the system will not respect any Back overrides
  94. Sticky immersive mode Useful for single-purpose UIs that want to

    keep the user from accidentally going back Examples: games, drawing apps, etc.
  95. Common scenarios

  96. Scrolling views Content should be drawn behind the navigation bar

    When the user scrolls to the end, the content is drawn above the navigation bar S C E N A R I O S
  97. Scrolling views Content should be drawn behind the navigation bar

    When the user scrolls to the end, the content is drawn above the navigation bar S C E N A R I O S
  98. Scrolling views Content should be drawn behind the navigation bar

    When the user scrolls to the end, the content is drawn above the navigation bar S C E N A R I O S
  99. Scrolling views Content should be drawn behind the navigation bar

    When the user scrolls to the end, the content is drawn above the navigation bar S C E N A R I O S
  100. Scrolling views Common way to achieve this is through padding

    We’ll pad our scrolling view using the system window insets S C E N A R I O S
  101. recyclerview.setOnApplyWindowInsetsListener { v, insets -> // Set the bottom padding

    so that the content bottom // is above the nav bar (y-axis) v.updatePadding(bottom = insets.systemWindowInsetBottom) // return the insets insets }
  102. <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" /> Enables our items to

    still be drawn within the parent’s padding
  103. Bottom sheets For bottom sheets which collapse, ensure enough content

    is visible above the navigation bar to be dragged S C E N A R I O S
  104. Bottom sheets For bottom sheets which collapse, ensure enough content

    is visible above the navigation bar to be dragged Use the system gesture insets to increase the peeking content height S C E N A R I O S
  105. Bottom sheets For bottom sheets which collapse, ensure enough content

    is visible above the navigation bar to be dragged Use the system gesture insets to increase the peeking content height S C E N A R I O S
  106. // Record the original peek height val origPeekHeight = behavior.peekHeight


    
 view.setOnApplyWindowInsetsListener { v, insets -> val gestureInsets = insets.getSystemGestureInsets() // Update the peek height so that it is above the navigation bar behavior.peekHeight = gestureInsets.bottom + origPeekHeight // return the insets insets }
  107. Bottom sheets Remember, users can always swipe to go home

    Highlight draggable content to guide user S C E N A R I O S
  108. Bottom navigation For bottom tabbed navigation, pad the bottom content

    in by the system window inset S C E N A R I O S
  109. Bottom navigation For bottom tabbed navigation, pad the bottom content

    in by the system window inset S C E N A R I O S
  110. Landscape S C E N A R I O S

  111. Landscape S C E N A R I O S

    If you have content that starts near a vertical edge, think about padding all content in horizontally Make sure to test apps with button navigation too
  112. rootView.setOnApplyWindowInsetsListener { v, insets -> // Set the left/right padding

    so that the content // is above the nav bar (y-axis) v.updatePadding( left = insets.systemWindowInsetLeft, right = insets.systemWindowInsetRight ) // return the insets insets }
  113. Landscape S C E N A R I O S

  114. Navigation drawers New swipe-when-peeking behavior in Q Beta 5 Works

    with all versions of DrawerLayout Update to DrawerLayout 1.1.0-alpha02 for best experience S C E N A R I O S
  115. Carousels For ViewPagers and horizontal scrolling views, recommend to not

    exclude edges S C E N A R I O S
  116. Carousels For ViewPagers and horizontal scrolling views, recommend to not

    exclude edges S C E N A R I O S
  117. More support coming Raise bugs for difficult patterns issuetracker.google.com

  118. TL;DR G E S T U R E N A

    V I G A T I O N Users will expect apps to work with all navigation modes Create immersive UIs by going edge-to-edge Proactively update Jetpack and MDC libraries for automatic support Only use gesture exclusion APIs if you really need them Let us know about troublesome scenarios in your apps
  119. Chris Banes @chrisbanes @rohanscloud Rohan Shah Thanks!