OnApplyWindowInsetsListener gives systemWindowInsetBottom that is always 0

5.2k views Asked by At

systemWindowInsetBottom AND stableInsetBottom are both always 0

I have an Activity, that has a background texture, and I am using FLAG_LAYOUT_NO_LIMITS to let that background go behind the status and navigation bar. But I don't want the other contents of the view to go behind those system UI components.

At first I thought of using resources.getIdentifier("navigation_bar_height", "dimen", "android") to get the height of the navigation bar, but that's just the default height of the navigation bar, so it won't work on devices where the user has hidden the navigation bar. (Samsung devices)

Then I found out about WindowInsets and android:fitsSystemWindows="true"

It works for the status bar, but it does not work for the navigation bar.

In my Activity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    //These 2 flags don't seem to change anything
    window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
    window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR)
    //This flag is required so the background can go behind the navbar
    window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)

    rootView.setOnApplyWindowInsetsListener { _, insets ->
        val topTextViewParams = rootView.tvTop.layoutParams as ViewGroup.MarginLayoutParams
        topTextViewParams.topMargin = insets.systemWindowInsetTop

        val bottomTextViewParams = rootView.tvBottom.layoutParams as ViewGroup.MarginLayoutParams
        bottomTextViewParams.bottomMargin = insets.systemWindowInsetBottom //Inset is always 0

        insets.consumeSystemWindowInsets()
    }
}

And my layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:id="@+id/rootView"
        android:background="@color/colorPrimary"
        tools:context=".MainActivity">

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <TextView
                android:id="@+id/tvBottom"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                android:text="Text aligned to bottom of layout with no margin"/>
        <TextView
                android:id="@+id/tvTop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:text="Text aligned to top of layout with no margin"/>
    </android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>

Example screenshots from Nexus 5x Emulator running API 28. Getting same results on my actual device running API 26.

Example Images

What do I have to do to get the actual size of the navigation bar, if it is present?

2

There are 2 answers

0
lbenedetto On

I spend like 3 hours researching this before I posted and then 5 minutes after I posted.

Those 5 minutes were very fruitful, because I found this post, which led me to try adding:

<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>

To my AppTheme, which worked.

So, if you're trying to do this, make sure you:

override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
    val childCount = childCount
    for (index in 0 until childCount)
        getChildAt(index).dispatchApplyWindowInsets(insets)
    // let children know about WindowInsets

    return insets
}

OR
Use a "materialish layout such as DrawerLayout or CoordinatorLayout" like I did in my post.

Make sure to use android:fitsSystemWindows="true"

Make sure to add those properties to your theme.

And finally make to do something in your setOnApplyWindowInsetsListener

0
basharovV On

Here's what I had to do to get both completely transparent status bar and navigation bar, with insets applied:

My root view is a ContstraintLayout. Somehow it's working even without fitsSystemWindows and I'm getting the insets for both the top and bottom, which I've applied to my Toolbar and FrameLayout margins.

In the theme:

<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>

setImmersive function in Activity:

I've created this function to apply the insets and window flags required to achieve the immersive look for a layout which can have:

  • Root view
    • Top view
    • Content
    • Bottom view
    /**
     * Apply immersive mode to this activity
     * @param rootView the root layout which will receive the window insets
     * @param topView the top view to apply insets to (optional)
     * @param bottomView the bottom view to apply insets to (optional)
     */
    fun setImmersive(rootView: ViewGroup, topView: View? = null, bottomView: View? = null) {

        window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)

        rootView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        bottomView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

        rootView.setOnApplyWindowInsetsListener { _, insets ->
            Log.d(javaClass.simpleName + " windowInsets", insets.toString())

            // Apply the top insets
            if (topView != null)  {
                insets.systemWindowInsetTop.apply {
                    if (this > 0) {
                        Log.d(javaClass.simpleName, "applying windowInsetTop $this")
                        val layoutParams = topView.layoutParams as ViewGroup.MarginLayoutParams
                        layoutParams.topMargin = this
                        topView.layoutParams = layoutParams
                    }
                }
            }

            // Apply the bottom insets
            if (bottomView != null)  {
                insets.systemWindowInsetBottom.apply {
                    if (this > 0) {
                        Log.d(javaClass.simpleName, "applying windowInsetBottom $this")
                        val layoutParams = bottomView.layoutParams as ViewGroup.MarginLayoutParams
                        layoutParams.bottomMargin = this
                        bottomView.layoutParams = layoutParams
                    }
                }
            }

            insets.consumeSystemWindowInsets()
        }
    }

Call this function in your onCreate eg.:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.brand_activity)
        setImmersive(root_view, topView = toolbar, bottomView = root_content)
}

The OnApplyWindowInsetsListener on the root view was getting called twice, once with the correct insets WindowInsets{systemWindowInsets=Rect(0, 98 - 0, 168) and again with 0 insets WindowInsets{systemWindowInsets=Rect(0, 0 - 0, 0). So I'm only setting margins on the non-zero insets.

In the layout:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/root_view">

    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/root_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/toolbar_layout"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.Toolbar
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:id="@+id/toolbar">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/toolbar_title"
                android:textStyle="bold"
                android:layout_gravity="center" />

        </androidx.appcompat.widget.Toolbar>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>