ViewPager2 OnApplyWindowInsetsListener not called

2.1k Views Asked by At

I have a FAB on a Fragment in a ViewPager2 which should respect the window insets when going edge-to-edge. I'm adding a OnApplyWindowInsetsListener on the FAB which updates its margin. This works fine when using the old ViewPager.

When updating to ViewPager2 it seems like the OnApplyWindowInsetsListener is not called at the beginning. It is though, when I start the ActionMode. Then, the listener is called and the new margin is used until I leave parent Fragment.


I've forked the demo project to illustrate the problem. See "ViewPager2 with Nested RecyclerViews" example (ParallelNestedScrollingActivity) on the branch edge-to-edge on https://github.com/hardysim/views-widgets-samples/tree/edge-to-edge .

In here, I've added a FAB to the (nested) RecyclerView used on a ViewPager2-page and set the Activity-UI to edge-to-edge (see View.goEdgeToEdge()). Then, the FAB is behind the navigation bar we need to update its margin to add the window insets.

And this is where it's not working (but it works fine with the old ViewPager).

2

There are 2 best solutions below

0
On BEST ANSWER

This has been answered in the issue tracker where it was originally asked:

The problem here is that the pages are not yet attached to the view hierarchy when the window insets are dispatched. The system doesn't call the OnApplyWindowInsetsListener with the current insets when a view is attached, so you'll have to call requestApplyInsets() when the view is attached to the hierarchy.

So I've created a little helper

/**
 * Call this everytime when using [ViewCompat.setOnApplyWindowInsetsListener]
 * to ensure that insets are always received.
 */
private fun View.requestApplyInsetsWhenAttached() {
    // https://chris.banes.dev/2019/04/12/insets-listeners-to-layouts/

    if (isAttachedToWindow) {
        // We're already attached, just request as normal
        requestApplyInsets()

    } else {
        // We're not attached to the hierarchy, add a listener to request when we are
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View) {
                v.removeOnAttachStateChangeListener(this)
                v.requestApplyInsets()
            }

            override fun onViewDetachedFromWindow(v: View) = Unit
        })
    }
}

which is called right after calling ViewCompat.setOnApplyWindowInsetsListener() on a View:

ViewCompat.setOnApplyWindowInsetsListener(view) { view, insets ->
    // [do stuff]
    insets
}
view.requestApplyInsetsWhenAttached()
0
On

It seems like ViewPager2 implementation bug. First time the pager gets the view we created, pager calls requestApplyInsets for it. But unfortunately, the view does not attached parent view so that the call of requestApplyInsets have no effect.

It can be solved by adding View.OnAttachStateChangeListener which calls requestApplyInsets on onViewAttachedToWindow.

Your ParallelNestedScrollingActivity sample seems like working well by:

diff --git a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
index 4e3753a..d2683df 100644
--- a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
+++ b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
@@ -29,0 +30 @@ import android.widget.TextView
+import androidx.core.view.ViewCompat
@@ -57 +58,3 @@ class ParallelNestedScrollingActivity : Activity() {
-            val root = inflater.inflate(R.layout.item_nested_recyclerviews, parent, false)
+            val root = inflater.inflate(R.layout.item_nested_recyclerviews, parent, false).apply {
+                addOnAttachStateChangeListener(RequestApplyInsetsOnAttached)
+            }
@@ -132,0 +136,5 @@ internal val CELL_COLORS = listOf(
+
+private object RequestApplyInsetsOnAttached : View.OnAttachStateChangeListener {
+    override fun onViewAttachedToWindow(view: View) = ViewCompat.requestApplyInsets(view)
+    override fun onViewDetachedFromWindow(view: View) = Unit
+}