paint-brush
Android Material Component: Toolbar vs DisplayCutoutby@waseefakhtar
946 reads
946 reads

Android Material Component: Toolbar vs DisplayCutout

by Waseef AkhtarMarch 18th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

With Android 9 (API level 28), Google officially started supporting what’s famously known as the notch, a cutout display at the top. Since the status bar is not technically a part of your app, it is almost always taken care of by Android. But what happens when you want to hide the top status bar to make your view fullscreen? That is something you need to solve as a developer. And since I’ve already taken my time solving it last week, I thought of it as the perfect new tutorial for everyone of you.

People Mentioned

Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Android Material Component: Toolbar vs DisplayCutout
Waseef Akhtar HackerNoon profile picture

With Android 9 (API level 28), Google officially started supporting what’s famously known as the notch, a cutout display at the top (because the last two years were the years of the notch?). I can’t claim that but with the notch support, most of the brands came out with their version of a cutout display and with that, we as developers need to think about yet another edge case, especially if we’re working with a completely immersive experience.

To give you a little knowledge of what I’ve researched, what I noticed while dealing with the notch is that the devices’ notch are either the size of the status bar or the status bar is adjusted vertically is the notch is a bit longer. So since the status bar is not technically a part of your app, it is almost always taken care of by Android.

But..

What happens when you want to hide the status bar to make your view fullscreen? That is something you need to solve as a developer.

And since I’ve already taken my time solving it last week, I thought of it as the perfect new tutorial for everyone of you to save your time dealing with it.

Let’s get started.

Prerequisite ☝️

For this tutorial I’ve used Android Studio 4.0 Preview but things should work on other versions of AS as well.

Project Setup ⚙️

First things first. We need to have the problem to solve the problem. In order to disable the status bar to go immersive in an app. Do the following:

1. Open a new project.

2. Select an Empty Activity Project Template. This would create an empty screen with an Action Bar.

3. Type any name for the project and select Kotlin from the language

4. Click Finish.

5. Run the project to see the look of your initial app.

Material Dependency 😍

Since we need to style our app a bit, we need Android’s Material library which is not part of the app at the moment, so first thing is to add the Material library to our project. To do so:

  1. Open build.gradle (Module)
  2. Add this line in the dependency section:
  3. implementation 'com.google.android.material:material:1.1.0'

    Note: At the time of writing, the latest Material version was 1.1.0, but you can use any latest stable release you want from Google’s Maven Repository or MVN Repository.

3. Sync the project.

Adding a custom Toolbar 🖌

You must be thinking if we already have a toolbar set up, why add another. It is because the default toolbar that’s set up by the project is from a parent layout that we do not have a control of, so when we enable fullscreen on the app, the toolbar hides along with other system elements, but we want to have control over it in order to not hide it automatically. Here’s how to set it up:

1. Open styles.xml.

2. Replace your parent theme from Theme.AppCompat.Light.DarkActionBar to Theme.MaterialComponents.NoActionBar.Bridge (running the app at this stage should not display any toolbar).

3. Open activity_main.xml.

4. Add the following lines in place of the placeholder TextView to add a toolbar (running the app at this stage should display an empty toolbar).

<androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

5. Open MainActivity.kt.

6. Add this line after setContentView() to setup our own toolbar and display the app title.

setSupportActionBar(findViewById(R.id.toolbar))

Enabling fullscreen 🌅

To enable fullscreen, we need to hide the top status bar and the bottom system navigation bar, to do so, Android Developer Guides has just the right doc for it: https://developer.android.com/training/system-ui/immersive:

1.Open styles.xml.

2. Add a new style below the AppTheme:

<style name="AppTheme.Fullscreen" parent="AppTheme">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
</style>

3. Open AndroidManifest.xml

4. In the MainActivity attributes, add the android:theme attribute with the styles that you just created:

<activity 
            android:name=".MainActivity"
            android:theme="@style/AppTheme.Fullscreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>

5. Now in your MainActivity.kt, add the following lines:

 override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (hasFocus) {
            hideSystemUIAndNavigation(this)
        }
    }

    private fun hideSystemUIAndNavigation(activity: Activity) {
        val decorView: View = activity.window.decorView
        decorView.systemUiVisibility =
            (View.SYSTEM_UI_FLAG_IMMERSIVE
                    // Set the content to appear under the system bars so that the
                    // content doesn't resize when the system bars hide and show.
                    or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // Hide the nav bar and status bar
                    or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_FULLSCREEN)
    }

6. Run the app and you’re good to go!

Except that you’re not.

If you run the app at this moment on a device with a notch / cutout display, you’d notice that the toolbar is hidden by the notch:

And that’s the problem this story is all about.

The Fix 🔧

Approach I:

In order to solve the issum, the first approach you might be thinking to take it to simply add padding to the toolbar to position it below the notch. But since we have in different heights on different devices, we can’t hardcode a constant value (for instance, 24dp or 48dp) in order to add a margin on top of the toolbar.

But there must be a way the height of the status bar is set by the system, you must think. Yes there is, and you access it by @android:dimen/status_bar_height but since that’s a private value, adding a android:layout_marginTop=”@android:dimen/status_bar_height” to your toolbar throws a build error:

AAPT: error: resource android:dimen/status_bar_height is private.

And even if you did add a constant value, it might work for the notch but not for no-notch devices:

Approach II and the magic: 🎩

The approach that I had to take to solve this issue is to simply work on it programmatically: to add padding only when needed. And since Google has a few new APIs to deal with the notch, achieving this hack is quite easy:

1. In your MainActivity.kt, add a new method:

@SuppressLint("NewApi")
private fun adjustToolbarMarginForNotch() { 
    // Notch is only supported by >= Android 9
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        val windowInsets = window.decorView.rootWindowInsets
        if (windowInsets != null) {
            val displayCutout = windowInsets.displayCutout
            if (displayCutout != null) {
                 val safeInsetTop = displayCutout.safeInsetTop
                 val newLayoutParams = toolbar.layoutParams as ViewGroup.MarginLayoutParams
                 newLayoutParams.setMargins(0, safeInsetTop, 0, 0)
                 toolbar.layoutParams = newLayoutParams
            }
        }
    }
}

2. Call this method just right below hideSystemUIAndNavigation(this) in onWindowFocusChanged to let the change affect when we hide the system UIs.

Note: Since some of the methods that we call here are quite new to the Google APIs (for instance, rootViewInsets, displayCutout, etc), they’re only supported by Android 9. And since the notch is quite a new trend in devices, all the new devices with the notch do run Android ≥ 9, so we’re all good here. 👌

If you run the app now, you see that the margin is only added when we do have a notch:

Extra: Styling 🎨

So far so good, we just need to add some style to the empty area or extend our toolbar to the top to make it not look very odd. To do so:

1. In your activity_main.xml, add a new view with a size of the toolbar (?attr/actionBarSize) on top of toolbar, like:

<View
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

2. Run the app to see the changes.

And so, you can play around with the background of the view and/or the toolbar to adjust the style according to your taste:

Happy coding! 💻

Source code for the Final Version

GitHub

As always, let me know if my tutorial helped you in anyway and the things I can improve in writing tutorials by sending a DM or a Tweet at: twitter.com/waseefakhtar ✌️

Previously published at https://proandroiddev.com/android-material-toolbar-vs-displaycutout-6ae99b2b7ef0