23

I would like to calculate the navigationBar height. I've seen this presentation : https://chris.banes.me/talks/2017/becoming-a-master-window-fitter-nyc/

So, I tried to use the method View.setOnApplyWindowInsetsListener(). But, for some reason, it's never called.

Does anyone knows why ? Any limitation there ?

I've tried to use it like this :

navBarOverlay.setOnApplyWindowInsetsListener { v, insets -> 
   Timber.i("BOTTOM = ${insets.systemWindowInsetBottom}")
   return@setOnApplyWindowInsetsListener insets
}

Note that my root layout is a ConstraintLayout.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Belgikoi
  • 565
  • 2
  • 5
  • 13

14 Answers14

12

I faced the same issue.

If your root view is ConstraintLayout and contains android:fitsSystemWindows="true" attr, the view consumed onApplyWindowInsets callbacks. So if you set onApplyWindowInsets on child views, they never get onApplyWindowInsets callbacks.

Or check your parent views consume the callback.

KevinRyu
  • 256
  • 4
  • 6
  • 2
    I know it's too late, just asking here if someone knows the answer. What should we do in such cases, if we want one of the child views to receive callback to onApplyWindowInsets? – Madhan Oct 02 '21 at 04:01
  • The way I did is I set it on the ConstraintLayout and modify the child there. `ViewCompat.setOnApplyWindowInsetsListener(binding.constraintLayout) { view, insets -> val insetsTop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top binding.frameLayoutContainer.updatePadding(top = insetsTop) WindowInsetsCompat.CONSUMED }` – moonLander_ Apr 18 '22 at 05:28
  • Actually, I try again and you can try to call `setOnApplyWindowInsetsListener` on the root layout but return the `insets`. And then call it again on your child view, do what you need to do here, and return `WindowInsetsCompat.CONSUMED`. – moonLander_ Apr 18 '22 at 16:02
7

This is what I observed; in other words, your experience might be different.

[Layout for Activity]
<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"    <--
    tools:context=".MyAppActivity">

    ...

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Notice android:fitsSystemWindows="true" in the outer most layout. As long as we have it, setOnApplyWindowInsetsListener() does get called.

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

        ViewCompat.setOnApplyWindowInsetsListener(fab) { view, insets ->
            ...
            insets
        }
    }

Alternatively, if you are going for the "full screen", meaning you want your layout to extend to the status bar and the navigation bar, you can do something like the following.

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    WindowCompat.setDecorFitsSystemWindows(window, false)    <--

    ViewCompat.setOnApplyWindowInsetsListener(fab) { view, insets ->
        ...
        insets
    }
}

The same idea is applicable when you are using a Fragment, as long as the Activity (that contains the Fragment) has either fitsSystemWindows in the outer most layout or you set your Activity as full screen.

solamour
  • 2,764
  • 22
  • 22
  • Be sure to be using AndroidX SearchView, i wasted too much time until discovering this works, just using the correct SearchView package.: androidx.appcompat.widget.SearchView , i did not see it at first. – kiquenet85 May 23 '21 at 03:49
7

I have faced with this problem when I've used CollapsingToolbarLayout, problem is that CollapsingToolbarLayout not invoking insets listener, if you have CollapsingToolbarLayout, then right after this component all other view insets wouldn't be triggered. If so, then remove listener from CollapsingToolbarLayout by calling

ViewCompat.setOnApplyWindowInsetsListener(collapsingToolbarLayout, null)

If you don't CollapsingToolbarLayout, then some other view is blocking insets from passing from view to view.

Or you have already consumed them, I guess you didn't do it)

Ramin eghbalian
  • 2,348
  • 1
  • 16
  • 36
Jamshid
  • 101
  • 1
  • 2
3

There is also bug with CollapsingToolbarLayout, it prevents siblings to receive insets, you can see it in issues github link. One of the solutions is to putAppbarLayout below in xml other views for them to receive insets.

Thracian
  • 43,021
  • 16
  • 133
  • 222
2

I've had to (and I think I am expected to) explicitly call requestApplyInsets() at some appropriate time to make the listener get hit.

Check this article for some possible tips: https://medium.com/androiddevelopers/windowinsets-listeners-to-layouts-8f9ccc8fa4d1

Walt Armour
  • 344
  • 2
  • 10
2

I faced similar issue on API 30. For setOnApplyWindowInsetsListener() to work you have to make sure that your activity is in full-screen mode. You can use below method to do so

WindowCompat.setDecorFitsSystemWindows(activity.window, false) //this is backward compatible version

Also make sure you are not using below method anywhere to set UI flags

View.setSystemUiVisibility(int visibility)
Hitesh Bisht
  • 486
  • 1
  • 6
  • 11
2

I found a solution for my case on APIs from 28 to 26. Because on higher versions everything was fine.

First, I need to mention, that I've tried everything mentioned in the answers above but nothing helped.

The solution:

ViewCompat.setOnApplyWindowInsetsListener(your_parent_view) { v, insets ->
    var consumed = false

    (v as ViewGroup).forEach { child ->
        // Dispatch the insets to the child
        val childResult = ViewCompat.dispatchApplyWindowInsets(child, insets)
        // If the child consumed the insets, record it
        if (childResult.isConsumed) {
            consumed = true
        }
    }

    // If any of the children consumed the insets, return an appropriate value
    if (consumed) WindowInsetsCompat.CONSUMED else insets
}

In my case your_parent_view was a FragmentViewContainer where my Fragment was located with the AppBarLayout to which I want to add padding.

It's not my finding, this is mentioned in Chris Banes's article, as a fix for another issue.

In addition, I've removed all the fitsSystemWindows=true lines and left this attribute only in my AppBarLayout, in the current fragment.

And of course, this line should be added to your Activity:

WindowCompat.setDecorFitsSystemWindows(window, false)

The additional ViewCompat.setOnApplyWindowInsetsListener(appBarLayout) was not required. The fitsSystemWindows=true attribute was doing its work.

Skullper
  • 714
  • 5
  • 22
1

My solution is to call it on navBarOverlay.rootView.

Pitel
  • 5,334
  • 7
  • 45
  • 72
1

putting ViewCompat.setOnApplyWindowInsetsListener into onResume worked for me with constraintLayout.

@Override
public void onResume() {
    super.onResume();
    ViewCompat.setOnApplyWindowInsetsListener(requireActivity().getWindow().getDecorView(), (v, insets) -> {
            boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
            int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
            return insets;
        });
} 
Randunu.KSW
  • 405
  • 4
  • 9
  • I think it might be related with my comment below. if there are multiple listeners set by setting the listener in onResume might be delaying the set and overriding the other listener. – Hadi Tok Feb 23 '23 at 11:12
1

I had this problem on android 7.1. But on android 11 it worked correctly. Just create a class:

import android.content.Context
import android.util.AttributeSet
import androidx.core.view.ViewCompat
import com.google.android.material.appbar.CollapsingToolbarLayout

class InsetsCollapsingToolbarLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : CollapsingToolbarLayout(context, attrs, defStyle) {

    init {
        ViewCompat.setOnApplyWindowInsetsListener(this, null)
    }

}

And use everywhere InsetsCollapsingToolbarLayout instead of CollapsingToolbarLayout

Pavel Shirokov
  • 432
  • 4
  • 6
0

In my app, it gets called once and not every time I wanted to. Therefore, in that one time it gets called, I saved the widnowInsets to a global variable to use it throughout the app.

fullmoon
  • 8,030
  • 5
  • 43
  • 58
0

I used the following solution using this answer:

ViewCompat.setOnApplyWindowInsetsListener(
        findViewById(android.R.id.content)
    ) { _: View?, insets: WindowInsetsCompat ->
        navigationBarHeight = insets.systemWindowInsetBottom
        insets
    }
  • This may work but if your toolbar has the custom color you'll need to change the status bar color accordingly. This may lead to hardcoded statusBar colors across the app where you have a different color of Toolbar. – Skullper Jul 12 '23 at 06:32
0

I used following solution in my project and it's works like a charm.

val decorView: View = requireActivity().window.decorView
val rootView = decorView.findViewById<View>(android.R.id.content) as ViewGroup
ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets ->
    val isKeyboardVisible = isKeyboardVisible(insets)
    Timber.d("isKeyboardVisible: $isKeyboardVisible")

    // Do something with isKeyboardVisible

    insets
}

private fun isKeyboardVisible(insets: WindowInsetsCompat): Boolean {
    val systemWindow = insets.systemWindowInsets
    val rootStable = insets.stableInsets
    if (systemWindow.bottom > rootStable.bottom) {
        // This handles the adjustResize case on < API 30, since
        // systemWindow.bottom is probably going to be the IME
        return true
    }
    return false
}

Use setWindowInsetsAnimationCallback instead of setOnApplyWindowInsetsListener in Android API > 30

Dilanka Laksiri
  • 408
  • 3
  • 12
0

One problem I had is ViewCompat.setOnApplyWindowInsetsListener was called again in some other place in the code for the same view. So make sure that is only set once.

Hadi Tok
  • 772
  • 8
  • 20