Gesture Navigation Tutorial for Android
In this Android tutorial, you will learn how to add support for gesture navigation to your app, a feature that was added in Android 10.
Version
- Kotlin 1.3, Android 5.0, Android Studio 3.4

In a world of fragmented devices, manufacturers want modern UIs. Often each manufacturer implements a different solution, making app design and development tough. Developers needed a way to unify the ecosystem. In Android 10, Google added Gesture Navigation, a new system navigation mechanism that replaces the button navigation alternatives with three, unique gestures.
In this tutorial, you’ll learn how to add support for Gesture Navigation to your app by working with a simple app for creating and managing notes: NoteMaker. You’ll learn how to:
- Make your UI edge-to-edge, allowing your app to display its content behind the status and navigation bars.
- Leverage insets, determining how much to inset your content to accommodate gesture areas.
- Override gestures in special cases.
For this tutorial, you need Android Studio and an Android device or emulator with Android 10.
Learning the Gestures
Before you start writing code to implement the gestures, it is helpful to learn what the gestures are and how they work. Check them out Below!
The navigating back gesture:
Swiping in from an edge takes you back. Watch the left edge of the screen. You can swipe in from the left or right edge to go back.
The navigating home gesture:
Swiping up from the bottom is the gesture for home. Watch the bottom edge of the screen.
The recent apps or overview gesture:
Finally, swiping up and holding in the middle takes you to your recent apps or overview. Watch the bottom edge of the screen. Now that you’ve seen the gestures, it’s time to see how they are implemented in an app.
Getting Started
Start by downloading the materials by clicking the Download Materials button at the top or bottom of the tutorial. Open the begin project in Android Studio. You’re ready to go!
To get yourself familiar with the app, enable 3-button navigation in your phone settings. You can find this option in System ‣ Gestures ‣ System navigation. Don’t worry, you’ll switch to gesture navigation in a few moments.
Build and run your project in Android Studio. You’ll see the Note Overview screen. Take your time and explore the app before you continue.
Click the button on the bottom right corner and you’ll see the screen where you can edit your note. You can pick a color for your note or delete the note by using the controls in the bottom sheet. Edit the note’s title and content according to the image below. Select the color for the note.
Finally, click the check-mark button on the top right corner to save the note. On the Note Overview screen, you can see the note you created.
Seeing the Problem at Hand
Now, you’ll see why you need to check your app’s behavior with gesture navigation enabled. Go to your device settings and search for System navigation. You’ll find a screen like this:
Switch your system navigation to Fully gestural navigation and go back to the app. Click the note you created and try to change its color. Notice how you have to struggle to drag the bottom sheet bar if you even can drag it at all.
Go back to the Note Overview screen by clicking the check-mark button on the top right corner or by swiping in from the left or right edge of the screen.
Great! Now that you are familiar with the app, and you saw what problems this new system navigation could cause, you’re ready to add support for gesture navigation to NoteMaker!
Making Your App Edge-to-Edge
In a typical Android app, the bounds of the app are below the status bar and above the navigation bar.
When making your app edge-to-edge, you want the app to display its content as if the navigation and status bars weren’t there. That creates a more immersive experience.
In Android 10, Google recommends drawing behind the navigation bar and the status bar. So, in edge-to-edge your app’s bounds look like this:
Changing System Bar Colors
The first thing you need to do when making your app UI edge-to-edge is to change the system bar colors. Open styles.xml and look inside. You’ll see your base app theme AppTheme.
Add the following items at the bottom of the AppTheme:
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
Alternatively, you can do this dynamically by using Window.setNavigationBarColor()
and Window.setStatusBarColor()
in the onCreate()
.
Build and run your project. See that your system bar colors are now transparent.
Requesting Fullscreen Layout
Before you can lay out the view edge-to-edge, your app must tell the system that it can handle this type of view. You can do this by using View.setSystemUiVisibility()
to set the following flags:
SYSTEM_UI_FLAG_LAYOUT_STABLE
-
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
.
At the top of NotesOverviewActivity.kt, add the following import to the list of imports:
import android.view.View
Now, add the following code below onCreate()
:
private fun requestToBeLayoutFullscreen() {
root.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
}
These flags tell the system to display your app fullscreen as if the navigation and status bars weren’t there. You can also set SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
. That allows you to draw behind the status bar.
Finally, call the code you’ve added from onCreate()
. Add the following code to onCreate()
, right below setContentView()
:
// Request to be layout full screen
requestToBeLayoutFullscreen()
Nice! Build and run your project. See that your content is now edge-to-edge.
Repeat the same three steps in SaveNoteActivity.kt. After that, build and run your project. Open the note you created at the beginning of the tutorial and you should see something like this:
Leveraging Insets
After running your app and looking at the modified activities, you can see that some of your views are behind the system bars. This is where insets come into play.
Insets are a collection of values that tell you how much to inset, or move, content. There are two types of insets: System window insets and system gesture insets.
Understanding System Window Insets
System window insets tell you where the system UI displays over your app. You can use these insets to move clickable views away from the system bars.
To consume system window insets, you have to implement the OnApplyWindowInsetsListener
interface. WindowInsets
provide regular visual insets for all system bars through getSystemWindowInsets()
.
Leveraging System Window Insets
Now that you know about system windows insets, you can leverage them to improve your Notes Overview screen. At the top of NotesOverviewActivity.kt, add these imports to the list of imports:
import android.view.ViewGroup
import androidx.core.view.updatePadding
Below requestToBeLayoutFullscreen()
, add the following code:
private fun adaptViewForInsets() {
// Prepare original top padding of the toolbar
val toolbarOriginalTopPadding = toolbar.paddingTop
// Prepare original bottom margin of the "Add Note" button
val addNoteButtonMarginLayoutParam =
addNoteButton.layoutParams as ViewGroup.MarginLayoutParams
val addNoteButtonOriginalBottomMargin =
addNoteButtonMarginLayoutParam.bottomMargin
}
This code prepares everything you need to update your views for the system window insets. It stores the toolbar’s top padding and the Add Note button’s bottom margin to fields. You use those fields to update paddings depending on the insets.
Next, you have to register the OnApplyWindowInsetsListener
that allows you to access the WindowInsets
. Add the following code to the bottom of adaptViewForInsets()
:
// Register OnApplyWindowInsetsListener
root.setOnApplyWindowInsetsListener { _, windowInsets ->
// Update toolbar's top padding to accommodate system window top inset
val newToolbarTopPadding =
windowInsets.systemWindowInsetTop + toolbarOriginalTopPadding
toolbar.updatePadding(top = newToolbarTopPadding)
// Update "Add Note" button's bottom margin to accommodate
// system window bottom inset
addNoteButtonMarginLayoutParam.bottomMargin =
addNoteButtonOriginalBottomMargin +
windowInsets.systemWindowInsetBottom
addNoteButton.layoutParams = addNoteButtonMarginLayoutParam
// Update notes recyclerView's bottom padding to accommodate
// system window bottom inset
notes.updatePadding(bottom = windowInsets.systemWindowInsetBottom)
windowInsets
}
This code updates several things:
- The toolbar’s top padding to accommodate the system window top inset.
- The Add Note button’s bottom margin to accommodate the system window bottom inset.
- The bottom padding of your note list to accommodate the system window bottom inset.
Great! Before testing this, you have to call adaptViewForInsets()
from your onCreate()
. In onCreate()
, below requestToBeLayoutFullscreen()
, add this:
// Adapt view according to insets
adaptViewForInsets()
Build and run your project. You should see something like this:
Notice the position of the Add Note button and how your toolbar handles the status bar.
You should also update the Save Note screen in a similar fashion. First, add these imports to the list of imports in SaveNoteActivity.kt:
import android.view.ViewGroup
import androidx.core.view.updatePadding
Similarly, add the following code below requestToBeLayoutFullscreen()
:
private fun adaptViewForInsets() {
// Prepare original top padding of the toolbar
val toolbarOriginalTopPadding = toolbar.paddingTop
// Prepare original bottom margin of colors recycler view
val colorsLayoutParams = colors.layoutParams as ViewGroup.MarginLayoutParams
val colorsOriginalMarginBottom = colorsLayoutParams.bottomMargin
}
On the Save Note screen, you also have to update your toolbar and the bottom margin of the color list. This code prepares the original margin and padding values that you’ll use later.
Next, add the following code to the bottom of adaptViewForInsets()
:
// Register OnApplyWindowInsetsListener
root.setOnApplyWindowInsetsListener { _, windowInsets ->
// Update toolbar's top padding to accommodate system window top inset
val newToolbarTopPadding = toolbarOriginalTopPadding +
windowInsets.systemWindowInsetTop
toolbar.updatePadding(top = newToolbarTopPadding)
// Update colors recycler view's bottom margin to accommodate
// system window bottom inset
val newColorsMarginBottom = colorsOriginalMarginBottom +
windowInsets.systemWindowInsetBottom
colorsLayoutParams.bottomMargin = newColorsMarginBottom
colors.layoutParams = colorsLayoutParams
windowInsets
}
This code does two things:
- It updates the toolbar’s top padding to accommodate the system window top inset.
- It also updates the color list bottom margin to accommodate the system window bottom inset.
Invoke the method in onCreate()
, below requestToBeLayoutFullscreen()
:
// Adapt view according to insets
adaptViewForInsets()
Build and run your project. You should see something like this:
Notice that the toolbar now handles the system window top inset correctly. However, it’s still difficult to drag the bottom sheet. You’ll improve that next. :]
System Gesture Insets
System gesture insets represent the areas of the window where system gestures take priority. They include the vertical edges for swiping back and the bottom edge for navigating home. You use them to move draggable views away from edges.
To consume system gesture insets, you also have to implement the OnApplyWindowInsetsListener
interface. This time you’ll call getSystemGestureInsets()
instead of getSystemWindowInsets()
.
Leveraging System Gesture Insets
You probably already have a feeling of where you can leverage this. That’s right, it’s your bottom sheet in the Save Note screen.
Add these imports to SaveNoteActivity.kt:
import com.google.android.material.bottomsheet.BottomSheetBehavior
import android.os.Build
import android.view.WindowInsets
import androidx.constraintlayout.widget.ConstraintLayout
Now, add the following code at the beginning of adaptViewForInsets()
in SaveNoteActivity.kt:
// Prepare original peek height of the bottom sheet
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
val bottomSheetOriginalPeekHeight = bottomSheetBehavior.peekHeight
To update your bottom sheet’s peek height, you have to get a reference to its BottomSheetBehavior
. You also have to store the original peek height.
Add the following method below adaptViewForInsets()
:
private fun adaptBottomSheetPeekHeight(
bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout>,
bottomSheetOriginalPeekHeight: Int,
windowInsets: WindowInsets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// If Q, update peek height according to gesture inset bottom
val gestureInsets = windowInsets.systemGestureInsets
bottomSheetBehavior.peekHeight = bottomSheetOriginalPeekHeight
+ gestureInsets.bottom
} else {
// If not Q, update peek height according to system window inset bottom
bottomSheetBehavior.peekHeight = bottomSheetOriginalPeekHeight
+ windowInsets.systemWindowInsetBottom
}
}
This code updates your bottom sheet’s peek height for the bottom inset. In Android 10, you use the gesture bottom inset. Below Android 10, you use the window bottom inset to determine how much you have to increase the peek height.
Finally, call the adaptBottomSheetPeekHeight()
you added. Add this code to the bottom of your OnApplyWindowInsetsListener
, right above windowInsets
:
// Update bottom sheet's peek height
adaptBottomSheetPeekHeight(bottomSheetBehavior, bottomSheetOriginalPeekHeight, windowInsets)
Build and run your project. Notice how easy it is to drag out the bottom sheet on the Save Note screen.
Mandatory System Gesture Insets
Mandatory system gesture insets are a subset of system gesture insets. They define areas apps can’t override. In Android 10, only the home gesture zone uses them.
Handling Conflicting App Gestures
This new gesture navigation model may conflict with your app’s current gestures. As a result, you may need to make adjustments to your app’s user interface. So, the last thing you have to do to make your app gesture navigation ready is overriding system gestures.
In NoteMaker, try to select a color for your note. Notice that if you try to scroll through colors by dragging from the right or left edge of the screen, Android triggers the system Back gesture.
You can opt-out of the Back gesture by telling the system which regions need to receive touch input. You can do this by passing a list of Rect
s to the View.setSystemGestureExclusionRects()
API introduced in Android 10.
Add these imports to your list of imports in SaveNoteActivity.kt:
import android.graphics.Rect
import androidx.core.view.doOnLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
Next, add the following code below adaptBottomSheetPeekHeight()
:
private fun excludeGesturesForColors(
bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout>,
windowInsets: WindowInsets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
bottomSheetBehavior.setBottomSheetCallback(object :
BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// NO OP
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == STATE_EXPANDED) {
// Exclude gestures when bottom sheet is expanded
} else if (newState == STATE_COLLAPSED) {
// Remove exclusion rects when bottom sheet is collapsed
}
}
})
}
}
This code still doesn’t do anything smart. It only lets you execute certain code depending on the bottom sheet’s state.
Now, in excludeGesturesForColors()
, add the following code to the first if-condition below the comment // Exclude gestures...
:
root.doOnLayout {
val gestureInsets = windowInsets.systemGestureInsets
// Common Rect values
val rectHeight = colors.height
val rectTop = root.bottom - rectHeight
val rectBottom = root.bottom
// Left Rect values
val leftExclusionRectLeft = 0
val leftExclusionRectRight = gestureInsets.left
// Right Rect values
val rightExclusionRectLeft = root.right - gestureInsets.right
val rightExclusionRectRight = root.right
// Rect for gestures on the left side of the screen
val leftExclusionRect = Rect(
leftExclusionRectLeft,
rectTop,
leftExclusionRectRight,
rectBottom
)
// Rect for gestures on the right side of the screen
val rightExclusionRect = Rect(
rightExclusionRectLeft,
rectTop,
rightExclusionRectRight,
rectBottom
)
// Add both rects and exclude gestures
root.systemGestureExclusionRects = listOf(leftExclusionRect, rightExclusionRect)
}
This code excludes the Back gesture in the area where the user can scroll through the available colors. In the same method, add the following code to the second if-condition below the comment // Remove exclusion...
:
root.doOnLayout { root.systemGestureExclusionRects = listOf() }
Now when the bottom sheet is collapsed, the system registers the gestures on the whole side of the screen.
Finally, add the following code to the bottom of OnApplyWindowInsetsListener
, right above windowInsets
:
// Exclude gestures on colors recycler view when bottom sheet is expanded
excludeGesturesForColors(bottomSheetBehavior, windowInsets)
Build and run your project. Notice that when the bottom sheet expands, you can’t go back when dragging from the left or right edge of the screen in the area where your color list is. But, when you try to drag from the edge in that same area with the bottom sheet collapsed, you can go back.
Handling Landscape Orientation
When making your app edge-to-edge, remember to check how your app behaves in landscape orientation.
Enable 3-button navigation and remove android:screenOrientation="portrait"
for your activities in AndroidManifest.xml. Run the app in the landscape orientation. You’ll get something like this, which obviously isn’t great:
This tutorial doesn’t cover how to handle landscape orientation. You can experiment with landscape yourself by applying the techniques we used for portrait. You’ll have to play some more with the padding in the landscape orientation, but it’s an excellent challenge to practice what you’ve learned. :]
Testing 3-Button Navigation and Gestural Navigation
Well done! You’ve adapted your app to be nav-ready. Finally, test your app in both navigation modes.
Use the device settings like you did in the previous steps to try each navigation mode and observe how the app behaves in each. When finished, your app will behave like this:
Where to Go From Here?
Wow, that was a lot of work!
You can download the completed project files by clicking on the Download Materials button at the top or bottom of the tutorial.
If you want to check out some more materials on this topic, please check this talk from the Google I/O’19 and Official Android documentation.
I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!
Comments