事件:viewpager2嵌套viewpager2产生滑动冲突,只有最外层的viewpager2可以滑动,最内层的 ViewPager2 无法滑动
简化表达:把外层的 ViewPager2 称为parent,内部的 ViewPager2 称为child
分析: 首先贴张滑动冲突的图
我们知道触摸事件的处理是在 onTouchEvent 里做的,事件表明parent 的 onTouchEvent 触发了。但是child的onTouchEvent没有触发,而child 位于 parent 的上层,在没有打断的情况下child 的 onTouchEvent 一定在 parent之前触发,得出结论parent在onInterceptTouchEvent事件中把这个滑动拦截并消费了。
论证: 首先 我们找到viewpager2的onInterceptTouchEvent事件,源码如下
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return isUserInputEnabled() && super.onInterceptTouchEvent(ev); } isUserInputEnabled viewpager2是否可以滑动
super.onInterceptTouchEvent(ev)是继承了recycleview onInterceptTouchEvent事件的实现,主要看move事件
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = x;
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = y;
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
return mScrollState == SCROLL_STATE_DRAGGING;
可以看到recycleview onInterceptTouchEvent方法里面,move事件只需要支持滑动(上下或者左右)并且滑动距离大于最小滑动距离就把事件拦截了,不会继续向下分发。所以当viewpager2嵌套viewpager2的时候,当滑动方向相同时,只有外层的viewpager2才会接受到事件。
反思:为什么viewpager不会有这样的问题 初步推断:第一种可能性:viewpager的onInterceptTouchEvent事件返回的是false或者super,不会直接消费,这样的话move事件就会往下传,外层的viewpager就能收到,但是如果外层的viewpager不做处理的话,也同样不会消费这次事件,内外层的viewpager都不会在onInterceptTouchEvent方法消费这个时间的话,那么事件会继续向下分发(没有下一层直接回溯),进入到ontouchevent是从下层往上层传递的,这时要是在内层ontouchevent中做处理,理论上是没有问题的。
第二种可能性:内层的viewpager使用requestParentDisallowInterceptTouchEvent(true)请求父view以及父view的父view(这里理论上无限套娃)不要拦截。然后内层的viewpager根据自身情况是否消费此次事件。也就是所谓的滑动冲突内部解决法。
论证: 先贴代码
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
。。。
case MotionEvent.ACTION_MOVE: {
//超过最小滑动距离,并且横向滑动距离大于两倍的纵向
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0
? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
mIsUnableToDrag = true;
}
}
}
return mIsBeingDragged;
可以看到代码里面当超过最小滑动距离,并且横向滑动距离大于两倍的纵向滑动距离的时候,会调用 requestParentDisallowInterceptTouchEvent(true),然后把mIsBeingDragged置为true。表示这次事件我要消费了!外层viewpager只能眼睁睁看着这次滑动事件被消费掉。这里印证了第二种可能性