带有 RecyclerView 滚动问题的垂直 ViewPager2

Cal*_*Rai 8 vertical-scrolling android-fragments android-recyclerview android-nestedscrollview android-viewpager2

我正在使用带有两个垂直方向的片段的 ViewPager2。当用户向下滑动到第二个片段时,有一个 RecyclerView 在同一垂直方向滚动内容。

问题是当我滚动 RecyclerView 的内容时,有时 ViewPager2 捕获滚动事件,有时 RecyclerView 捕获滚动事件。

我希望这样,当用户滚动到 RecyclerView 的顶部时,ViewPager 仅在用户到达 RecyclerView 内容的顶部时才滑回第一个片段。

我试过使用 recyclerView.isNestedScrollingEnabled = false,但运气不佳。我也尝试将 RecyclerView 放入 NestedScrollView,但不推荐这样做,因为 RecyclerView 然后创建它需要的数据集的每个 ViewHolder,这显然效率不高。

Cal*_*Rai 18

所以......我能够通过阅读一些文档来弄清楚。我将在此处发布答案,以帮助其他遇到类似问题的人:

由于 ViewPager2 不支持嵌套滚动视图,与 NestedScrollView 不同,我们需要在布局中使用自定义包装器包装嵌套滚动视图,以便能够处理被嵌套滚动视图父级拦截的触摸和滑动事件。在我们的例子中,子元素是 RecyclerView,父元素是 ViewPager2。

您可以在此处找到包装类。只需将它添加到您的项目中,然后将您的可滚动视图包装在其中,类似于以下内容:

    <NestedScrollableHost
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/my_recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" />

    </NestedScrollableHost>
Run Code Online (Sandbox Code Playgroud)

这里有几件事需要注意:文档说这个解决方案不适用于 ViewPager 中其他可滚动视图中的可滚动视图。此解决方案仅适用于 ViewPager 的即时滚动视图。

另一个注意事项是包装类使用requestDisallowInterceptTouchEvent()来确保子可滚动视图告诉父级不要滚动,如果子级需要滚动。

  • 在我的例子中,垂直 recyclerview 面临滚动问题,它很不稳定,并且大多数时候 viewpager2 选择滑动操作并发生水平滑动。 (2认同)

小智 5

我得到的最好的解决方案是使用gestureDetector.SimpleOnGestureListenerinside on recyclerView.addOnItemTouchListener(this)

步骤1:在OnCreate()方法中。该类GestureListener是在 Step-3 创建的。

gestureDetector = new GestureDetector(getActivity(), new GestureListener());
Run Code Online (Sandbox Code Playgroud)

步骤2:调用recyclerView.addOnItemTouchListener()方法。

recyclerView.addOnItemTouchListener(this);
Run Code Online (Sandbox Code Playgroud)

步骤3:创建GestureListener扩展的类GestureDetector.SimpleOnGestureListener

public class GestureListener extends GestureDetector.SimpleOnGestureListener {
    private final int Y_BUFFER = 10;

    @Override
    public boolean onDown(MotionEvent e) {
        // Prevent ViewPager from intercepting touch events as soon as a DOWN is detected.
        // If we don't do this the next MOVE event may trigger the ViewPager to switch
        // tabs before this view can intercept the event.
        Log.d("vp", "true1");
        recyclerView.getParent().requestDisallowInterceptTouchEvent(true);
        return super.onDown(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (Math.abs(distanceX) > Math.abs(distanceY)) {
            Log.d("vp2", "true");
            // Detected a horizontal scroll, allow the viewpager from switching tabs
            recyclerView.getParent().requestDisallowInterceptTouchEvent(false);
        } else if (Math.abs(distanceY) > Y_BUFFER) {
            // Detected a vertical scroll prevent the viewpager from switching tabs
            Log.d("vp3", "false");
            recyclerView.getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.onScroll(e1, e2, distanceX, distanceY);
    }
}
Run Code Online (Sandbox Code Playgroud)

步骤 4gestureDetector.onTouchEvent(e)从调用onInterceptTouchEvent()

@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
    gestureDetector.onTouchEvent(e);
    return false;
}
Run Code Online (Sandbox Code Playgroud)

更新

科特林版本

这是上一个答案的 kotlin 版本。

val gestureDetector = GestureDetector(requireActivity(), object: GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent?): Boolean {
        rv.parent.requestDisallowInterceptTouchEvent(true)
        return super.onDown(e)
    }

    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        if (abs(distanceX) > abs(distanceY)) {
            rv.parent.requestDisallowInterceptTouchEvent(false)
        } else if (abs(distanceY) > 10) {
            rv.parent.requestDisallowInterceptTouchEvent(true)
        }
        return super.onScroll(e1, e2, distanceX, distanceY)
    }
})

rv.addOnItemTouchListener(object: RecyclerView.OnItemTouchListener {
    override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
    override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
    override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
        gestureDetector.onTouchEvent(e)
        return false
    }
})
Run Code Online (Sandbox Code Playgroud)