修复在ViewPager2中使用PhotoView出现的滑动异常

现象

最近在做一个图片查看器,采用水平ViewPager2+PhotoView去实现。但发现当放大图片并且图片处在非左右边界时,手指左右滑动,有时是PhotoView响应滑动,有时是外部ViewPager2响应滑动。

分析

经过反复实验,发现PhotoView图片处在上或下边界时,手指左右滑动稍微斜了一点,便会触发外部ViewPager2滑动。
查看PhotoView源码关于滚动处理部分:

private OnGestureListener onGestureListener = new OnGestureListener() {
    @Override
    public void onDrag(float dx, float dy) {
        if (mScaleDragDetector.isScaling()) {
            return; // Do not drag if we are already scaling
        }
        if (mOnViewDragListener != null) {
            mOnViewDragListener.onDrag(dx, dy);
        }
        mSuppMatrix.postTranslate(dx, dy);
        checkAndDisplayMatrix();

        /*
         * Here we decide whether to let the ImageView's parent to start taking
         * over the touch event.
         *
         * First we check whether this function is enabled. We never want the
         * parent to take over if we're scaling. We then check the edge we're
         * on, and the direction of the scroll (i.e. if we're pulling against
         * the edge, aka 'overscrolling', let the parent take over).
         */
        ViewParent parent = mImageView.getParent();
        if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
            if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH
                    || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
                    || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f)
                    || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
                    || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) {
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
            }
        } else {
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
        }
    }
     
     ...
}
复制代码

可以看出针对滑动冲突PhotoView采用的是内部拦截法,但是限定条件有问题:

if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH
    || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
    || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f)
    || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
    || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f))
复制代码

当图片缩放处于上边缘或下边缘的时候,直接调用了parent.requestDisallowInterceptTouchEvent(false);让父View有机会走拦截逻辑,而父View,这里是ViewPager2也很不客气的拦截了事件,导致触发外部ViewPager2滑动。

这个应该算是PhotoView的bug,外部用ViewPager的话也一样会有这个问题。

解决方法

由于PhotoView的代码不多,我直接复制到项目里,修改了onGestureListener#onDrag里的代码:

@Override
public void onDrag(float dx, float dy) {

    if (mScaleDragDetector.isScaling()) {
        return; // Do not drag if we are already scaling
    }
    if (mOnViewDragListener != null) {
        mOnViewDragListener.onDrag(dx, dy);
    }

    mSuppMatrix.postTranslate(dx, dy);
    checkAndDisplayMatrix();

    /*
     * Here we decide whether to let the ImageView's parent to start taking
     * over the touch event.
     *
     * First we check whether this function is enabled. We never want the
     * parent to take over if we're scaling. We then check the edge we're
     * on, and the direction of the scroll (i.e. if we're pulling against
     * the edge, aka 'overscrolling', let the parent take over).
     */
    ViewParent parent = mImageView.getParent();
    //拖动的时候
    if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !noScaleAndDrag) {
        float absDx = Math.abs(dx);
        float absDy = Math.abs(dy);
        if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH && absDx > absDy
                || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f && absDx > absDy)
                || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f && absDx > absDy)
                || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f && absDy > absDx)
                || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f && absDy > absDx
                || (mVerticalScrollEdge == VERTICAL_EDGE_BOTH) && absDy > absDx)
        ) {
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
        }
    } else {
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }
    }
}
复制代码

简单说明下,就是在请求父View继续拦截的条件里,增加了(滑动方向的距离>另一方向上的距离)的判定条件。

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