该使用场景可能比较少见,大家碰不上,仅在此作一次记录。
最近项目中需要添加列表条目的侧滑删除,第一时间想到的是利用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)
}
})
}
复制代码