Setting Statusbar padding to NavigationView in Android

12,891

Solution 1

<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="@layout/nav_header"
app:menu="@menu/menu_navigation"
android:fitsSystemWindows="false"
android:layout_gravity="start"/>

android:fitsSystemWindows="false"

and CoordinatorLayout => android:fitsSystemWindows="false"

Solution 2

First, remember that you cannot draw behind status bar before Lollipop.

Solution

For Lollipop+, you should set the android:fitsSystemWindows="true" for DrawerLayout and leave the default for NavigationView .

Then on your activity or fragment after setting the header:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
    navigationView.setOnApplyWindowInsetsListener { v, insets ->

        val header = navigationView.getHeaderView(0)
        header.setPadding(
            header.paddingLeft, 
            header.paddingTop + insets.systemWindowInsetTop,
            header.paddingRight, 
            header.paddingBottom
        )
        insets.consumeSystemWindowInsets()
    }
}

Explanation

The window is passing a object called insets (with dimensions for top, left, bottom, right) to it's childs views. The child views that have the fitSystemWindows=true will do nothing with this insets and may send it forward to its children. The views that have it false may use the values to add extra padding or margin to itself.

Set fitSystemWindows=false of the NavigationView is not working because it is using the insets to apply extra margin to itself. What you want is your header view LinearLayout to apply this margin. But there is two intermediate container views inside NavigationView that will not pass forward the insets to your LinearLayout, so you have to intercept the windows inset before these containers and apply yourself the margin or padding of your header view.

What you should never do

Never assume that the window insets will be a fixed value or get them from resources, no matter what the Android version is. Specific devices have specific values for window insets, some have thicker "notches" to fit big cameras, some manufacturers decide to make it thinner than Android recommended specs.

Solution 3

You're using LinearLayout as the root element of your header layout. FrameLayout, LinearLayout, etc. are basic layouts that ignore the android:fitsSystemWindows attribute. I recommend you to use CoordinatorLayout as your header layout's root element. Using CoordinatorLayout with its android:fitsSystemWindows attribute set to true will automatically adjust its internal padding to prevent its child elements from being overlapped by system windows such as the status bar.

These info are from this article, "Why would I want to fitsSystemWindows?" by Ian Lake.

While the basic layouts (FrameLayout, LinearLayout, etc) use the default behavior, there are a number of layouts that already customize how they react to fitsSystemWindows to fit specific use cases.

...

CoordinatorLayout also takes advantage of overriding how it handles window insets, allowing the Behavior set on child Views to intercept and change how Views react to window insets, before calling dispatchApplyWindowInsets() on each child themselves. It also uses the fitsSystemWindows flag to know if it needs to paint the status bar background.

To achieve what you want, make sure that you follow this layout structure.

layout/activity_xxxxx.xml

<android.support.v4.widget.DrawerLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...

    <!-- drawer -->
    <android.support.design.widget.NavigationView
        ...
        android:fitsSystemWindows="true" 
        app:headerLayout="@layout/nav_header" />

<android.support.v4.widget.DrawerLayout>

layout/nav_header.xml

<android.support.design.widget.CoordinatorLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...

<android.support.design.widget.CoordinatorLayout>
Share:
12,891
Sreekanth
Author by

Sreekanth

Updated on June 17, 2022

Comments

  • Sreekanth
    Sreekanth almost 2 years

    I have an activity which hosts a DrawerLayout and NavigationView from support library. I'm setting a header layout to the navigation view and I want the navigation header height to be "wrap_content". So when I set the height to "wrap_content" the header layout goes behind the status bar.

    The result I want is that the navigation drawer should draw behind the status bar but the navigation header should be pushed down by the statusbar height.

    Below is the screenshot of what I am getting. Note the "SIGN IN" button going behind the status bar.

    screenshot

    Activity layout

    <android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/nav_drawer"
    android:fitsSystemWindows="true">
    
    <android.support.design.widget.CoordinatorLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
        <android.support.v4.view.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"></android.support.v4.view.ViewPager>
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <include layout="@layout/include_toolbar"/>
    
            <android.support.design.widget.TabLayout
                app:theme="@style/ThemeOverlay.AppCompat.Dark"
                style="@style/MyCustomTabLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/tabs"
                />
    
        </android.support.design.widget.AppBarLayout>
    
    </android.support.design.widget.CoordinatorLayout>
    
    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/menu_navigation"
        android:fitsSystemWindows="true"
        android:layout_gravity="start"/>
    
    </android.support.v4.widget.DrawerLayout>
    

    Navigation View Header Layout

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="?attr/colorPrimaryDark"
              android:padding="16dp"
              android:theme="@style/ThemeOverlay.AppCompat.Dark"
              android:orientation="vertical"
              android:fitsSystemWindows="true"
              android:gravity="bottom">
    
    <TextView
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text_user_name"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
    
    <TextView
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text_email"
        android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
    
    <Button
        android:id="@+id/button_sign_in"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Sign In"/>
    
    </LinearLayout>
    

    I've searched through StackOverflow for a solution but couldn't find it. So someone please shed some light. Thanks in advance.