0

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).

hardysim
  • 2,756
  • 2
  • 25
  • 52
  • Copied from https://issuetracker.google.com/issues/145617093 – hardysim Dec 05 '19 at 12:42
  • You might want to register `OnApplyWindowInsetsListener` at the root view of your fragment/activity layout, and update FAB's margins when it gets called. – sergej shafarenka Jan 05 '20 at 15:21
  • You mean delegate the call of `OnApplyWindowInsetsListener` (in the `Activity`) to the `View` of the current / all pages of the `ViewPager`? How do I get the view (of the FAB) from the activity to update its margins? – hardysim Jan 07 '20 at 08:08
  • In the fragment where the FAB is, take fragment's (or even activity's) root view, set `OnApplyWindowInsetsListener` on it and once the listener is called, update margin of your FAB according to the received inserts. You might want to check https://github.com/chrisbanes/insetter or https://github.com/beworker/edge-to-edge. The later one has an app with some examples. Hope it helps. – sergej shafarenka Jan 07 '20 at 11:11
  • @sergejshafarenka I've got you wrong and thought I should add this explicitly on the `Activity`. To make it clear: going edge-to-edge and update margin/padding via `OnApplyWindowInsetsListener` is working fine for "normal views" but not for views *in a `ViewPager`*. So the examples / libraries are not helping because they do what I'm already doing (basically adding `OnApplyWindowInsetsListener`). – hardysim Jan 09 '20 at 10:52

2 Answers2

3

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()
hardysim
  • 2,756
  • 2
  • 25
  • 52
1

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
+}
Ryohji
  • 11
  • 2