ViewPager2 OnApplyWindowInsetsListener 未调用

ViewPager2 OnApplyWindowInsetsListener not called

我在 ViewPager2 中的 Fragment 上有一个 FAB,它在边到边时应该尊重 window 插入。我在 FAB 上添加了一个 OnApplyWindowInsetsListener 来更新它的边距。这在使用旧的 ViewPager.

时工作正常

更新到 ViewPager2 时,OnApplyWindowInsetsListener 似乎没有在开头调用。不过,当我启动 ActionMode 时。然后,调用侦听器并使用新边距,直到我离开父 Fragment。


我已经分叉了演示项目来说明问题。请参阅 https://github.com/hardysim/views-widgets-samples/tree/edge-to-edge 分支 edge-to-edge 上的 "ViewPager2 with Nested RecyclerViews" 示例 (ParallelNestedScrollingActivity)。

在这里,我向 ViewPager2 页面上使用的(嵌套)RecyclerView 添加了一个 FAB,并将 Activity-UI 设置为边到边(参见 View.goEdgeToEdge())。然后,FAB 在导航栏后面,我们需要更新它的边距以添加 window 插图。

这就是它不起作用的地方(但它在旧的 ViewPager 上运行良好)。

这似乎是 ViewPager2 实施错误。 pager 第一次获取我们创建的视图时,pager 会为其调用 requestApplyInsets。但不幸的是,该视图没有附加父视图,因此 requestApplyInsets 的调用无效。

可以通过在onViewAttachedToWindow上调用requestApplyInsets添加View.OnAttachStateChangeListener来解决。

您的 ParallelNestedScrollingActivity 示例似乎运行良好:

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
+}

这已在 issue tracker 最初提出的问题中得到回答:

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.

所以我创造了一个小帮手

/**
 * 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
        })
    }
}

View:

上调用 ViewCompat.setOnApplyWindowInsetsListener() 后立即调用
ViewCompat.setOnApplyWindowInsetsListener(view) { view, insets ->
    // [do stuff]
    insets
}
view.requestApplyInsetsWhenAttached()