Android Q / Gesture Navigation

Gesture Navigation – Edge Cases

One of the big Android announcements at Google I/O 2019 was the introduction of gesture navigation on Android Q. Initially it may feel like this is purely a system-level change and does not affect apps directly, but closer inspection reveals that apps may need to change to properly accommodate this. The touch areas that the system now uses to detect these user gestures will overlap with the app UI, so apps which also have touch handling active near the edges of the display may need to accommodate the system gesture handling. In this series we’ll take a look at how this new behaviour affects apps, and provide some guidance for migrating various common app behaviours to play nicely with the navigation gestures.

The first aspect that we’ll explore of ensuring that out apps play nicely with gesture navigation, is how those edge gestures are actually handled. Previously we discussed how the gestures actually work and looked at how they might affect things like scrolling / ViewPager / carousels. However the biggest clash was with DrawerLayout which uses both a hamburger menu and a bezel swipe gesture from the sides of the display to open the drawer. This is clearly going to clash with the new back gesture which is also uses bezel swipes from the left and right edges of the display.

Here’s a simple app which contains a DrawerLayout for navigation. We can see how tapping the hamburger icon opens the drawer, and we can swipe it closed as well, but if we attempt a bezel swipe gesture to open it, the system “back” is triggered instead:

Fortunately this is actually really easy to improve as the good folks at Google have thought about this problem and come up with an improved way of dealing with this:

The bezel swipe causes to drawer to open, and it can be swiped away as before, but if the drawer is already open and the user performs a second bezel swipe gesture then the system back behaviour will be triggered. While this isn’t perfect, because it doesn’t feel immediately intuitive, it’s quite an easy behaviour for users to discover if they are already using bezel swipes, so they will be able to learn this behaviour quite quickly. It is also worth remembering that swiping from the right edge will still trigger the system “back”.

This behaviour has been included in the DrawerLayout support library version 1.1.0-alpha01 and we can get it for free just by including the appropriate dependency:

dependencies {
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.core:core-ktx:1.2.0-alpha01'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
    implementation 'androidx.navigation:navigation-fragment:2.0.0'
    implementation 'androidx.navigation:navigation-ui:2.0.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
    implementation 'androidx.drawerlayout:drawerlayout:1.1.0-alpha01'
}

Often DrawerLayout is an implicit dependency of other support libraries you might be using so, if you do not already have an explicit dependency upon it in your build.gradle adding one will force all of these implicit dependencies to use the version that you specify, so you can get this new behaviour across the entire app by adding this one dependency.

That’s fine if we’re using DrawerLayout but what should we do if we have another control where we wish to handle an edge gesture? This is actually quite straightforward, as well – we can inform the system of any edge regions where we want to disable the system gestures with a call to View#setSystemGestureExclusionRects() which has also been added to ViewCompat.

Here’s a very basic custom View:

lass MyCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {}

We add this to our layout so that it covers the top quarter:




    

    


We can see how the back gesture is handled by the system:

If we bezel swipe either inside the bounds of our custom view, or outside it, we see the back arrow appear.

But we can alter our custom view to exclude its own bounds from the system gesture handling:

class MyCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val boundingBox: Rect = Rect()
    private val exclusions = listOf(boundingBox)

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (changed) {
            boundingBox.set(left, top, right, bottom)
            ViewCompat.setSystemGestureExclusionRects(this, exclusions)
        }
    }
}

The key line is the highlighted one which makes a call to setSystemGestureExclusionRects() via ViewCompat. (On versions of Android prior to Q this will have no effect). If we now run this we can see the updated behaviour:

This is the technique that the new version of DrawerLayout is using internally to implement the new behaviour that we saw earlier.

There is one thing worth mentioning here: While there is the facility to exclude regions from the system gestures, this should be used sparingly, and only in a small number of cases. The reason for this is that users will quickly become familiar with the new gestures, and if your app denies those behaviours without good reason it will become a source of frustration to your users. Also denying the system gestures for the entire display is a really bad idea because it could leave you users without an easy way to exit your app, and that will be even more frustrating to your users!

In the final article in this series we’ll take a look at how to follow the other new guideline of expanding behind the system controls.

The source code for this article is available here.

© 2019, Mark Allison. All rights reserved.

Copyright © 2019 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.