Display content edge-to-edge in your app

Figure 1: System bars with edge-to-edge

You can configure your app to draw its content behind the system bars. Together, the status bar and the navigation bar are called the system bars.

Your app achieves an edge-to-edge layout by drawing behind these system bars. When implementing edge-to-edge, your app should do the following:

  • Draw behind the navigation bar to achieve a more compelling and modern user experience.

  • Draw behind the status bar if it makes sense for your content and layout, such as in the case of full-width imagery. To do this, use APIs such as AppBarLayout, which defines an app bar pinned to the top of the screen.

Figure 2: Example of an app with imagery behind the status bar

Implementing edge-to-edge in your app requires the following steps:

  1. Lay out your app full-screen.
  2. Change the system bar colors and transparency.
  3. Handle any visual overlaps.

Step 1: Lay out your app in full screen

This is the primary step for ensuring that your app goes edge-to-edge, that is, laid out using the entire width and height of the display. Use WindowCompat.setDecorFitsSystemWindows(window, false) to lay out your app behind the system bars, as shown in the following code example:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  WindowCompat.setDecorFitsSystemWindows(window, false)
}

Java

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
}

Step 2: Change the color of the system bars

When operating in an edge-to-edge layout, your app needs to change the colors of the navigation and status bars to allow for the content underneath to be visible. After your app performs this step, the system handles all visual protection of the user interface in gesture navigation mode or in the button modes.

  • Gesture navigation mode: The system applies dynamic color adaptation, in which the contents of the system bars change color based on the content behind them. In the following example, the handle in the navigation bar changes to a dark color if it’s above light content, and vice versa.

    Figure 3: Handle color changes in gesture navigation mode
  • Button modes: The system applies a translucent scrim behind the system bars (for API level 29 or higher) or a transparent system bar (for API level 28 or lower).

    Figure 4: Translucent scrim behind system bars
  • Status bar content color: Control the color of the status bar content, such as the time and icons.

    Figure 5: Status bar content color

Edit the themes.xml file to ensure the color of the navigation bar and, optionally, to set the status bar as transparent and status bar content color as dark.

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

  <!-- Optional: set to transparent if your app is drawing behind the status bar. -->
  <item name="android:statusBarColor">
     @android:color/transparent
  </item>

  <!-- Optional: set the status bar light and content dark. -->
  <item name="android:windowLightStatusBar">
    true
  </item>
</style>

You can use the WindowInsetsController API directly, but we strongly recommend using the support library WindowInsetsControllerCompat where possible. You can use WindowInsetsControllerCompat API instead of theme.xml to control the status bar content color. To do so, use the setAppearanceLightNavigationBars() function, passing in true (changing the foreground color of the navigation to light colored) or false (reverting to the default color).

Kotlin

val windowInsetsController =
      ViewCompat.getWindowInsetsController(window.decorView)

windowInsetsController?.isAppearanceLightNavigationBars = true

Java

WindowInsetsControllerCompat windowInsetsController =
      ViewCompat.getWindowInsetsController(getWindow().getDecorView());
if (windowInsetsController == null) {
    return;
}

windowInsetsController.setAppearanceLightNavigationBars(true);

Step 3: Handle overlaps using insets

After you’ve implemented an edge-to-edge layout with color transparency, some of your app's views may be drawn behind the system bars (for example, as shown in figure 6).

You can address overlaps by reacting to insets, which specify which parts of the screen intersect with system UI such as the navigation bar or the status bar. Intersecting can mean simply being displayed above the content, but it can also inform your app about system gestures, too.

The types of insets that apply to displaying your app edge-to-edge are:

  • System bars insets. These insets are best used for views that are tappable and that shouldn’t be visually obscured by the system bars.

  • System gesture insets. These insets apply to gesture-navigational areas used by the system that take priority over your app.

System bars insets

System bars insets are the most commonly-used type of insets, and represent where the system UI is displayed in the z-axis above your app. They are best used to move or pad views in your app that are both tappable and shouldn't be visually obscured by the system bars.

For instance, the floating action button (FAB) in the following example is partially obscured by the navigation bar.

To avoid this kind of visual overlap caused by edge-to-edge in either gesture mode or the button modes, you can increase the view's margins by using getInsets(int) with WindowInsetsCompat.Type.systemBars(). Applying this solution to the example shown in figure 6 would result in the removal of visual overlaps for the button modes and gesture navigation mode, as shown in figures 7 and 8

Figure 7: Resolving visual overlap for button modes
Figure 8: Resolving visual overlap for gesture navigation mode (right)

The following code example shows how to implement system bars insets:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. Here the system is setting
  // only the bottom, left, and right dimensions, but apply whichever insets are
  // appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  view.updateLayoutParams<MarginLayoutParams>(
      leftMargin = insets.left,
      bottomMargin = insets.bottom,
      rightMargin = insets.right,
  )

  // Return CONSUMED if you don't want want the window insets to keep being
  // passed down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. Here the system is setting
  // only the bottom, left, and right dimensions, but apply whichever insets are
  // appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want want the window insets to keep being
  // passed down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

System gesture insets

System gesture insets represent the areas of the window, shown in orange in figure 9, where system gestures take priority over your app.

Figure 9: System gesture insets

Use these insets to move or pad swipeable views away from the edges. Common use cases include bottom sheets, swiping in games, and carousels implemented using ViewPager.

On Android 10 or higher, system gesture insets contain a bottom inset for the home gesture, and a left and right inset for the back gestures:

Figure 10: System gesture inset measurements

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here we're setting all of the
    // dimensions, but apply as appropriate to your layout. You could also
    // update the views margin if more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if we don't want the window insets to keep being passed
    // down to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here we're setting all of the
    // dimensions, but apply as appropriate to your layout. You could also
    // update the views margin if more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if we don't want the window insets to keep being passed
    // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Step 4: Immersive mode

Some content is best experienced in full screen, giving the user a more immersive experience. Refer to Immersive mode for information on implementing this feature with the WindowInsetsController and WindowInsetsControllerCompat libraries.

Kotlin

val windowInsetsController =
      ViewCompat.getWindowInsetsController(window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

WindowInsetsControllerCompat windowInsetsController =
      ViewCompat.getWindowInsetsController(getWindow().getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());