Reputation: 615
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
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
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
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
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
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
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
Reputation: 181
I used the following solution using this answer:
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(android.R.id.content)
) { _: View?, insets: WindowInsetsCompat ->
navigationBarHeight = insets.systemWindowInsetBottom
insets
}
Upvotes: 0
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
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
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
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
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
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