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

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.

Chris Banes

July 02, 2019
Tweet

More Decks by Chris Banes

Other Decks in Programming

Transcript

  1. Going edge-to-edge
    Gesture Navigation

    View Slide

  2. Chris Banes
    @chrisbanes @rohanscloud
    Rohan Shah

    View Slide

  3. View Slide

  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

    View Slide

  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

    View Slide

  6. Making your app nav-ready
    1. Make your UI edge-to-edge
    2. Leverage insets for better UI
    3. Override system gestures

    View Slide

  7. What is

    edge-to-edge?

    View Slide

  8. 10:00

    View Slide

  9. App bounds
    10:00

    View Slide

  10. View Slide

  11. View Slide

  12. 10:00

    View Slide

  13. 10:00

    View Slide

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

    View Slide

  15. 10:00

    View Slide

  16. 10:00

    View Slide

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

    View Slide

  18. 10:00

    View Slide

  19. 10:00

    View Slide

  20. Going edge-to-edge

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  25. Going immersive
    Avoid overlaps with system UI
    Request to be laid out fullscreen
    Change system bar colors
    1
    2
    3

    View Slide

  26. <br/><item name="android:navigationBarColor"><br/>@android:color/transparent<br/></item><br/>
    values-v29/themes.xml
    Change system bar colors 1

    View Slide

  27. <br/><!-- 70% white --><br/><item name="android:navigationBarColor"><br/>#B3FFFFFF<br/></item><br/>
    values/themes.xml
    Change system bar colors 1

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  31. Insets

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  44. So how do I actually handle insets?
    WindowInsets

    View Slide

  45. ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    // Do something with the insets
    // return the insets
    insets
    }
    Tip: always use the ViewCompat method

    View Slide

  46. getSystemWindowInsets(): Insets
    getSystemGestureInsets(): Insets
    getMandatorySystemGestureInsets(): Insets
    getStableInsets(): Insets
    WindowInsetsCompat

    View Slide

  47. getSystemWindowInsets(): Insets
    getSystemGestureInsets(): Insets
    getMandatorySystemGestureInsets(): Insets
    getStableInsets(): Insets
    WindowInsetsCompat
    data class Insets(
    left: Int,
    top: Int,
    right: Int,
    bottom: Int
    )

    View Slide

  48. View Slide

  49. <br/><item name="android:navigationBarColor"><br/>@android:color/transparent<br/></item><br/>
    values-v29/themes.xml

    View Slide

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

    View Slide

  51. view.systemUiVisibility =
    SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
    SYSTEM_UI_FLAG_LAYOUT_STABLE

    View Slide

  52. view.systemUiVisibility =
    SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
    SYSTEM_UI_FLAG_LAYOUT_STABLE

    View Slide

  53. view.systemUiVisibility =
    SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
    SYSTEM_UI_FLAG_LAYOUT_STABLE

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  59. View Slide

  60. View Slide

  61. android:padding="32dp">

    View Slide

  62. After applying all of the steps...

    View Slide

  63. VC.setOnApplyWindowInsetsListener(bottomBar) { v, insets ->
    v.updatePadding(
    bottom = insets.systemWindowInsets.bottom
    )
    insets
    }
    android:padding="32dp">
    Overwrites

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  69. bottomBar.doOnApplyWindowInsets { v, insets, initialPadding ->
    v.updatePadding(
    bottom = initialPadding.bottom +
    insets.systemWindowInsets.bottom
    )
    insets
    }

    View Slide

  70. - Nick Butcher

    View Slide

  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

    View Slide

  72. android:clipToPadding="false"
    app:paddingBottomSystemWindowInsets="@{ true }" />
    No more handling
    insets in code
    !

    View Slide

  73. View Slide

  74. Overriding gestures

    View Slide

  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.

    View Slide

  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.

    View Slide

  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

    View Slide

  78. View.setSystemGestureExclusionRects(List rects)

    View Slide

  79. List exclusionRects;
    public void onLayout(...) {
    // Update rect bounds and the exclusionRects list
    setSystemGestureExclusionRects(exclusionRects);
    }
    CustomView.java

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  94. Sticky
    immersive mode
    Useful for single-purpose UIs that want to
    keep the user from accidentally going
    back
    Examples: games, drawing apps, etc.

    View Slide

  95. Common scenarios

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  102. 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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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
    }

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  117. More support coming
    Raise bugs for difficult patterns
    issuetracker.google.com

    View Slide

  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

    View Slide

  119. Chris Banes
    @chrisbanes
    @rohanscloud
    Rohan Shah
    Thanks!

    View Slide