解决ViewPager2中RecyclerView使用ItemTouchHelper产生的滑动冲突

该使用场景可能比较少见,大家碰不上,仅在此作一次记录。

最近项目中需要添加列表条目的侧滑删除,第一时间想到的是利用RecyclerView的ItemTouchHelper去实现,然而功能写完了后,发现,滑动冲突了!

具体情况是这样的,外层是常见的VeiwPager2 + Fragment,最后一个Fragment里面放了竖直的RecyclerView。按理说ViewPager2没法继续左滑,不应该和列表条目左滑删除手势产生冲突,但实际是左滑删除有时不响应。

继续多次测试后发现,当左滑速度快的时候,外层ViewPager2响应,并出现滑动到边缘的水波纹阴影?,慢的时候则是正常的侧滑删除。很明显,某些情况下,左滑手势被ViewPager2拦截了。我们知道ViewPager2真实面目是RecyclerView,直接定位到RecyclerView#onInterceptTouchEvent方法中:

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
        ...
        case MotionEvent.ACTION_MOVE: {
            if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                mLastTouchX = x;
                startScroll = true;
            }
            if (startScroll) {
                setScrollState(SCROLL_STATE_DRAGGING);
            }
        }
        ...
        
        return mScrollState == SCROLL_STATE_DRAGGING;
}
复制代码

很明显,当滑动快的时候,Math.abs(dx) > mTouchSlop条件成立,onInterceptTouchEvent方法返回true,ViewPager2拦截了滑动。

再回过头来看ItemTouchHelper是如何处理侧滑冲突的

 private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
            @NonNull MotionEvent event) {
        ...
        if (action == MotionEvent.ACTION_DOWN) {
            ...
        } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            ...
        } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
            if (index >= 0) {
                //?ACTION_MOVE会走到这里!
                checkSelectForSwipe(action, event, index);
            }
        }
        ...
        return mSelected != null;
    }
复制代码

checkSelectForSwipe中继续调用select方法(关键),select方法中调用了

?
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
复制代码

也就是说,ItemTouchHelper在ACTION_MOVE的时候才请求ViewPager2不要拦截,显然为时已晚,当滑动快的时候,ViewPager2就已经拦截了,代码根本走不到这里。

解决的思路也很简单,只要让ViewPager2(RecyclerView)的onInterceptTouchEvent方法,在ViewPager2滑动到边缘后,返回false不拦截,将事件交给内层RecyclerView即可。

fun ViewPager2.fixHorizontalScroll() {
    val recyclerView = getChildAt(0) as RecyclerView
    val touchSlopField = recyclerView::class.java.superclass
        .getDeclaredField("mTouchSlop").also { it.isAccessible = true }

    val originTouchSlop = touchSlopField.get(recyclerView) as Int
    recyclerView.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {

        private var initialX = -1F

        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
            when (e.action) {
                MotionEvent.ACTION_DOWN -> {
                    //恢复mTouchSlop
                    touchSlopField.set(recyclerView, originTouchSlop)
                    initialX = e.x
                }
                MotionEvent.ACTION_MOVE -> {
                    val dx = e.x - initialX
                    if ((dx < 0 && !recyclerView.canScrollHorizontally(1)) ||
                        (dx > 0 && !recyclerView.canScrollHorizontally(-1))
                    ) {
                        //?ViewPager2滑动到边缘,将mTouchSlop设置为最大
                        touchSlopField.set(recyclerView, Integer.MAX_VALUE)
                    }
                }
            }
            return false
        }

        override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {

        }

        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
            //恢复mTouchSlop
            touchSlopField.set(recyclerView, originTouchSlop)
        }
    })

}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享