Belgikoi
Belgikoi

Reputation: 615

setOnApplyWindowInsetsListener never called

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.

Upvotes: 27

Views: 30705

Answers (14)

Skullper
Skullper

Reputation: 775

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.

Upvotes: 4

Hadi Tok
Hadi Tok

Reputation: 771

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.

Upvotes: 0

Pavel Shirokov
Pavel Shirokov

Reputation: 462

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

Upvotes: 1

Dilanka Laksiri
Dilanka Laksiri

Reputation: 268

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

Upvotes: 0

Hitesh Bisht
Hitesh Bisht

Reputation: 536

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)

Upvotes: 3

Randunu.KSW
Randunu.KSW

Reputation: 415

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;
        });
} 

Upvotes: 1

I used the following solution using this answer:

ViewCompat.setOnApplyWindowInsetsListener(
        findViewById(android.R.id.content)
    ) { _: View?, insets: WindowInsetsCompat ->
        navigationBarHeight = insets.systemWindowInsetBottom
        insets
    }

Upvotes: 0

Thracian
Thracian

Reputation: 66899

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.

Upvotes: 3

Jamshid
Jamshid

Reputation: 121

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)

Upvotes: 9

solamour
solamour

Reputation: 3214

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.

Upvotes: 8

Pitel
Pitel

Reputation: 5393

My solution is to call it on navBarOverlay.rootView.

Upvotes: 3

KevinRyu
KevinRyu

Reputation: 286

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.

Upvotes: 14

Badr Bujbara
Badr Bujbara

Reputation: 8691

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.

Upvotes: 0

Walt Armour
Walt Armour

Reputation: 344

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

Upvotes: 2

Related Questions