View事件的滑动冲突以及解决方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37639900/article/details/88890438


滑动冲突是我们开发中经常遇到的一个问题,本文笔者将从滑动冲突的分类和滑动冲突的处理两方面进行介绍。

滑动冲突的分类

常见的滑动冲突可以简单分为以下三种:

  1. 内部滑动方向和外部滑动方向不一致
  2. 内部滑动方向和外部滑动方向一致
  3. 以上两种的嵌套

第一种方向不一致:我们常见的是ViewPager+Fragment结合使用时Fragment内部往往是一个ListView的情况,这种情况是有冲突的,只是ViewPager内部处理了这种滑动冲突,若果将ViewPager换成ScroolView等就必须手动处理了。

第二种方向一致,这种情况稍微有些复杂,需要我们自己去判断滑动效果到底交给内层还是外层去处理,因为系统无法知道用户到底想让哪一层的View滑动,这种场景主要是指内外两层View同时只能上下滑动或左右滑动。

第三种前两中的嵌套使用,这种情况只需要分别处理内层和中层、中层和外层之间的滑动冲突即可。

滑动冲突的处理规则

接下来分别说这三种冲突处理规则和解决方式概括为一句话:谁需要响应事件的时候谁就去拦截事件,关于事件的拦截机制以及源码分析可以参考一下我写的另外一篇文章在这里不做过度赘述。Android事件分发机制原理及源码分析
对于第一种(方向不一致时)冲突: 根据水平滑动还是垂直滑动,判断事件到底是由谁去拦截,比如我们检测水平方向和垂直方向两点的距离差值进行比,判断哪个方向滑动。

**对于第二种方向一致时的滑动冲突:**这种情况比较特殊,我分根据滑动的角度、距离差以及速度差来判断,这种只能从业务逻辑上找突破点,比如业务上规定:当处于某种状态书让外部View响应用户滑动,而处于另一种状态时需要内部View响应用户滑动。

**对于第三种冲突:**我们需要结合第一种和第二种分别去处理内层与中层、中层与外层之间的冲突。
这里只是理论上对滑动冲突的解决方案进行描述,一遍理解记忆接下来我将从第三节去详细分析每种冲突对应的结局方案。

滑动冲突的解决方案外部拦截法和内部拦截法

1:外部拦截法

外部拦截法是指点击事件都事先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这种方法比较符合点击事件的分发机制,外部拦截伐需要重写父容器的onInterceptTouchEvent方法在内部做相应的拦截即可:

/**
 *外部拦截法:复写父容器的onInterceptTouchEvent方法
 */
 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted=false;
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //step1:不拦截ACTION_DOWN事件,因为一拦截后续的ACTION_MOVE和ACTION_UP事件直接交给父容器,此时事件将无法传递给子view
                intercepted=false;
                break;
            case MotionEvent.ACTION_MOVE:
                //step2:
                if (Math.abs(x-mLastx)>Math.abs(y-mLastY)){
                    //父容器需要处理当前点击事件,将其拦截
                    intercepted=true;
                }else {
                    intercepted=false;
                }
                break;

            case MotionEvent.ACTION_UP:
                //step3:一方面ACTION_UP事件本事没太多意义,另一方面,假如交给子元素处理,
                // up事件进行拦截导致自元素无法接受到up事件,导致子元素的onClick事件无法触发
                intercepted=false;
                break;

        }
        mLastx=x;
        mLastY=y;
        return intercepted;
    }

2:内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器处理,这种方法和Android的事件分发机制不一致需要配合requestDiaallowInterceptTouchEvent方法才能正常工作比外部拦截法稍显复杂期代码如下:

step1:复写子元素的dispatchTouchEvent方法

 /**
     *内部拦截法 复写子元素的dispatchTouchEvent方法:
     *配合父容器requestDisallowInterceptTouchEvent(boolean)方法使用
     *并且父容器需要拦截除了ACTION_DOWN事件以外的其他事件,因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERECPT标记位的控制,
     *一旦拦截DOWN事件所有事件将无法传递到子元素中去
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //父容器不拦截DOWN事件,因为此事件不受FLAG_DISSALLOW_INTERECPT标志未的影响,一旦拦截DOWN事件所有事件将无法传递到子元素中去
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(x-mLastx)>Math.abs(y-mLastY)){
                    //父容器响应事件则通知父容器需要处理当前点击事件,将其拦截
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mLastx=x;
        mLastY=y;
        return super.dispatchTouchEvent(ev);
    }

step2:复写父容器的onInterceptTouchEvent方法过滤down事件

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //拦截除了ACTION_DOWN事件以外的其他事件,因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERECPT标记位的控制,
        if (ev.getAction()==MotionEvent.ACTION_DOWN){
            return false;
        }else {
            return true;
        }
    }

总结

对于事件冲突:主要是要根据逻辑判断谁去响应用户事件,然后通过内部拦截或外部拦截法进行滑动冲突的处理。
这里附上我的另一片博客Android事件分发机制原理及源码分析 以供参考。

猜你喜欢

转载自blog.csdn.net/weixin_37639900/article/details/88890438