Edge-to-Edge Display in Android: Kotlin XML Implementation

In modern Android development, utilizing the full screen real estate is crucial for creating immersive user experiences. Edge-to-edge display support, often referred to as going “full bleed,” allows your app to draw content behind the system bars (status and navigation bars), creating a seamless and modern look. This article provides a comprehensive guide on how to implement edge-to-edge display support in your Android applications using Kotlin and XML.

What is Edge-to-Edge Display?

Edge-to-edge display refers to the practice of drawing your app’s content behind the system bars, making use of the entire screen. This is typically achieved by making the system bars transparent or translucent, allowing the app’s content to show through.

Why Implement Edge-to-Edge?

  • Immersive Experience: Provides a more engaging user interface.
  • Modern Look: Aligns with contemporary design trends.
  • Better Space Utilization: Maximizes screen real estate.

Steps to Implement Edge-to-Edge Display Support

Here’s a step-by-step guide on how to implement edge-to-edge display support in your Android app:

Step 1: Update Dependencies

First, ensure your project uses a Material Components theme, and your build.gradle file includes the necessary dependencies. Update your app’s build.gradle file:

dependencies {
    implementation("com.google.android.material:material:1.6.0") // Or later
    implementation("androidx.core:core-ktx:1.7.0") // Or later
}

Sync your project after updating the dependencies.

Step 2: Set Theme for Edge-to-Edge Support

Update your app’s theme to enable edge-to-edge support. In your res/values/styles.xml file, ensure you’re using a Material Components theme and set the necessary flags:

<resources>
    <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="android:windowLightStatusBar">true</item> <!-- Light status bar -->
        <item name="android:windowLightNavigationBar">true</item> <!-- Light navigation bar -->
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    </style>
</resources>
  • android:windowLightStatusBar: Sets the status bar icons to be dark (for light backgrounds).
  • android:windowLightNavigationBar: Sets the navigation bar icons to be dark (for light backgrounds).
  • android:navigationBarColor: Makes the navigation bar transparent.
  • android:statusBarColor: Makes the status bar transparent.
  • android:windowDrawsSystemBarBackgrounds: Allows the app to draw behind the system bars.

Step 3: Implement in Kotlin Activity

In your main Activity, use the WindowCompat API to configure the window for edge-to-edge display:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Enable edge-to-edge display
        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContentView(R.layout.activity_main)
    }
}
  • WindowCompat.setDecorFitsSystemWindows(window, false): This line is essential. It tells the system that your app wants to handle the insets (the areas covered by system bars) itself, allowing your app to draw behind them.

Step 4: Adjusting Layout to Account for System Bars

After enabling edge-to-edge, your content might be obscured by the system bars. Use the Padding or Margin to ensure your content is visible.

Using View.OnApplyWindowInsetsListener

Apply insets to your views to prevent content from being hidden. In your layout file (activity_main.xml), give the root view an ID, for example, main_container.

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Edge-to-Edge!"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Now, adjust your Kotlin activity:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.ViewCompat
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat
import android.view.View

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Enable edge-to-edge display
        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContentView(R.layout.activity_main)

        val mainContainer = findViewById<View>(R.id.main_container)
        ViewCompat.setOnApplyWindowInsetsListener(mainContainer) { view, windowInsets ->
            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
            // Apply the top and bottom insets as padding to the view
            view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
            WindowInsetsCompat.CONSUMED
        }
    }
}
  • ViewCompat.setOnApplyWindowInsetsListener: This listener allows you to react to window insets changes.
  • windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()): Retrieves the system bar insets.
  • view.setPadding(insets.left, insets.top, insets.right, insets.bottom): Applies the insets as padding to the view, ensuring content isn’t hidden.
  • WindowInsetsCompat.CONSUMED: Indicates that the insets have been handled, preventing them from being passed further down the view hierarchy.

Step 5: Handling Light System Bars

If you have a light-themed app, ensure the status bar icons are dark for better visibility. Similarly, for dark-themed apps, use light icons.

import android.os.Build
import android.view.View
import androidx.core.view.WindowInsetsControllerCompat

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Enable edge-to-edge display
        WindowCompat.setDecorFitsSystemWindows(window, false)
        
        setContentView(R.layout.activity_main)

        val mainContainer = findViewById<View>(R.id.main_container)
        ViewCompat.setOnApplyWindowInsetsListener(mainContainer) { view, windowInsets ->
            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
            WindowInsetsCompat.CONSUMED
        }
        
        // Set status bar icons to dark for light theme
        val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
        windowInsetsController.isAppearanceLightStatusBars = true

        // Optional: Handle navigation bar as well
        windowInsetsController.isAppearanceLightNavigationBars = true
    }
}

Key points:

  • WindowCompat.getInsetsController(window, window.decorView): Provides a controller to modify system bar appearance.
  • windowInsetsController.isAppearanceLightStatusBars = true: Sets status bar icons to dark.
  • windowInsetsController.isAppearanceLightNavigationBars = true: Sets navigation bar icons to dark (optional).

Complete Example

Here is a complete example combining all the steps to implement edge-to-edge display support in an Android app.

build.gradle

dependencies {
    implementation("com.google.android.material:material:1.6.0") // Or later
    implementation("androidx.core:core-ktx:1.7.0") // Or later
    implementation("androidx.appcompat:appcompat:1.4.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.2")
}

styles.xml

<resources>
    <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="android:windowLightStatusBar">true</item> <!-- Light status bar -->
        <item name="android:windowLightNavigationBar">true</item> <!-- Light navigation bar -->
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    </style>
</resources>

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Edge-to-Edge!"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        WindowCompat.setDecorFitsSystemWindows(window, false)
        
        setContentView(R.layout.activity_main)

        val mainContainer = findViewById<View>(R.id.main_container)
        ViewCompat.setOnApplyWindowInsetsListener(mainContainer) { view, windowInsets ->
            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
            WindowInsetsCompat.CONSUMED
        }

        val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
        windowInsetsController.isAppearanceLightStatusBars = true
        windowInsetsController.isAppearanceLightNavigationBars = true
    }
}

Best Practices

  • Test on Multiple Devices: Ensure compatibility and correct display on different screen sizes and Android versions.
  • Consider Dark Theme: Adjust your theme to properly handle edge-to-edge in dark mode.
  • Accessibility: Always prioritize accessibility. Ensure that content is readable and not obscured by system bars.

Conclusion

Implementing edge-to-edge display support enhances the user experience by creating a more immersive and modern interface. By following the steps outlined in this guide, you can effectively implement edge-to-edge in your Android apps using Kotlin and XML, making optimal use of the available screen real estate while ensuring your content remains accessible and visually appealing.