Android进阶知识(十一):View的滑动冲突

Android进阶知识(十一):View的滑动冲突

  在界面中只要内外两层同时可以滑动,这个时候就会产生滑动冲突。以下内容将对View的滑动冲突常见情景,以及如何解决滑动冲突进行解析。

一、常见的滑动冲突场景

  常见的滑动冲突场景可以简单的分为以下三种(三种场景的图示如下图所示):

  • 场景1——外部滑动方向和内部滑动方向不一致;
  • 场景2——外部滑动方向和内部滑动方向一致;
  • 场景3——上面两种情况的嵌套;
    在这里插入图片描述

  对于场景1,主要是将ViewPager和Fragment配合使用组成的页面滑动效果,在这个效果中,可以通过左右滑动切换页面,而每个页面内部往往又是一个ListView。本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突
在这里插入图片描述

  对于场景2,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题,当手指滑动时,要么只有一层能滑动,要么就是内外两层都滑动得很卡顿。而场景3,是场景1和场景2两种情况的嵌套,因此只需要分别处理内层和中层、中层和外层之间的滑动冲突即可。
  从本质上来讲,这三种滑动冲突场景的复杂度其实是相同的,因为它们的区别仅仅是滑动策略的不同,至于解决滑动冲突的方法,是通用的
在这里插入图片描述

二、滑动冲突的处理规则

  一般来说,不管滑动冲突多么复杂,它都有既定的规则,根据这些规则就可以选择合适的方法处理。

  1. 场景1处理规则

  场景1的处理规则为:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件。
在这里插入图片描述

  因此可以根据滑动是水平滑动还是竖直滑动来判断由谁来拦截事件,从而解决滑动冲突。根据滑动过程中的两个点间的坐标就可以得出到底是水平滑动还是竖直滑动,而作为参考的依据有:滑动路径和水平方向所形成夹角、水平方向和竖直方向上的距离差以及水平和竖直方向的速度差。滑动过程示意图如下。
在这里插入图片描述

  1. 场景2处理规则

  对于场景2来说,比较特殊,其无法根据滑动的角度、距离差以及速度差来判断,但是这个时候一般都能在业务上找到突破点。根据业务的需求可以得出相应的处理规则

  1. 场景3处理规则

  场景3的滑动规则更加复杂,同场景2一样,其只能根据业务的需求得到相应的处理规则

三、滑动冲突的解决方式

  抛开滑动规则不说,解决滑动冲突的方式有:外部拦截以及内部拦截。这两种滑动冲突的解决方式并不依赖于具体的滑动规则,是通用的解决方法,在不同的场景下只需要修改有关滑动规则的逻辑即可。

  1. 外部拦截法

  外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,以此解决滑动冲突。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,这种方法的伪代码如下所示。

public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (父容器需要当前事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        default: break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}

  外部拦截发动画示意图如下。
在这里插入图片描述

  针对不同的滑动冲突,只需要修改父容器需要当前点击事件的这个条件即可,其他均不需做修改并且也不能做修改。父容器对于ACTION_DOWN这个事件必须不拦截,因为一旦拦截,那么后续的ACTION_MOVE和ACTION_UP事件将直接交由父容器处理(这在Android进阶知识(十):View的事件分发机制
中提到了)。

  1. 内部拦截法

  内部拦截法是指父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这种方法需要配合上一篇中提到的requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。重写子元素的dispatchTouchEvent方法的伪代码如下。

public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (父容器需要此事件) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION.UP:
            break;
        default: break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}

  当面对不同滑动策略时只需要修改里面的条件即可,其他不需要也不能改动。除了子元素需要处理之外,父元素也要默认拦截处理ACTION_DOWN之外的其他事件。父元素需要做的修改如下。

public boolean onInterceptTouchEvent(MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}

  内部拦截法动画示意如下。
在这里插入图片描述

  通过上述的两种方法,对于滑动冲突的三种场景,只需要修改相关的滑动规则逻辑即可
  这里需要提及的一点是,一般情况下推荐使用外部拦截法处理滑动冲突,原因在于内部拦截法处理修改子元素之外还需要修改父元素,相对来讲比较复杂
在这里插入图片描述

参考资料:《Android开发艺术探索》

原创文章 78 获赞 25 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_38196407/article/details/105749958