viewpager2滑动冲突(一)浅谈

487 阅读3分钟

事件:viewpager2嵌套viewpager2产生滑动冲突,只有最外层的viewpager2可以滑动,最内层的 ViewPager2 无法滑动

简化表达:把外层的 ViewPager2 称为parent,内部的 ViewPager2 称为child

分析: 首先贴张滑动冲突的图

966283-b9cb65aceea9219b.webp

我们知道触摸事件的处理是在 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只能眼睁睁看着这次滑动事件被消费掉。这里印证了第二种可能性