Android edge-to-edge layout

Default layout

Base theme without action bar:

<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
</style>

Default layout

Set background color for status and navigation bars

<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
  <item name="android:statusBarColor">@android:color/holo_purple</item>
  <item name="android:navigationBarColor">@android:color/holo_red_dark</item>
</style>

Colored status and navigation bars

Make status and navigation bars transparent

<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
  <item name="android:statusBarColor">@android:color/transparent</item>
  <item name="android:navigationBarColor">@android:color/transparent</item>
</style>

Transparent status and navigation bars

Make status bar text color contrasted

<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
  <item name="android:windowLightStatusBar">true</item>
  ...
</style>

windowLightStatusBar = true -> status bar will be drawn compatible with a light background

Status bar text color compatible with a light background

Make the layout fit the screen edge-to-edge

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

Layout fits the screen edge-to-edge

Layout is letterboxed in landscape mode

Layout is cutout in landscape mode

This 'bug' is present even in Google products like Maps and Google Earth; Sky Map is even displayed in full cutout mode.

Make layout fit the screen edge-to-edge in landscape mode

<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
  ...
  <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>

windowLayoutInDisplayCutoutMode can take one of three values:

  • default: content renders into the cutout area when displayed in portrait mode, but is letterboxed when in landscape mode
  • shortEdges: content always renders into the cutout area
  • never: content never renders into the cutout area

Layout edge-to-edge in landscape mode

windowLayoutInDisplayCutoutMode requires API level 27, so if your app supports lower API level, then extract it into "values-v27/themes.xml".

  • values/themes.xml:
<style name="Base.Theme.ShortEdges" parent="Theme.Material3.Light.NoActionBar">
  ...
</style>

<style name="Theme.ShortEdges" parent="Base.Theme.ShortEdges" />
  • values-v27/themes.xml:
<style name="Theme.ShortEdges" parent="Base.Theme.ShortEdges">
  <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>

How to determine safe region

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
  ...
  ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.my_view), this::onApplyWindowInsets);
}

@NonNull
public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat windowInsets) {
  final Insets displayCutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
  final Insets systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  
  final Insets safeInsets = Insets.of(
    max(displayCutoutInsets.left, systemBarsInsets.left),
    max(displayCutoutInsets.top, systemBarsInsets.top),
    max(displayCutoutInsets.right, systemBarsInsets.right),
    max(displayCutoutInsets.bottom, systemBarsInsets.bottom)
  );

  return WindowInsetsCompat.CONSUMED;
}

Set component view margins

final ViewGroup.MarginLayoutParams mlp = 
  (ViewGroup.MarginLayoutParams) view.getLayoutParams();

mlp.leftMargin = safeInsets.left;
mlp.topMargin = safeInsets.top;
mlp.bottomMargin = safeInsets.bottom;
mlp.rightMargin = safeInsets.right;

view.setLayoutParams(mlp);

The button is in the safe zone

Resources