滑动冲突|青训营笔记

126 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第9天

滑动冲突

1、常见的滑动冲突场景

  • 场景1:外部滑动方向和内部滑动方向不一致。

    主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。在这种效果中,可以通过左右滑动来切 换页面,而每个页面内部往往又是一个ListView。本来这种情况下是有滑动冲突的,但是ViewPager内部处理了这种滑动冲突,因此采用ViewPager时我们无须 关注这个问题,如果我们采用的不是ViewPager而是ScrollView等,那就必须手动处理滑动冲突了,否则造成的后果就是内外两层只能有一层能够滑动,这是 因为两者之间的滑动事件有冲突。

  • 场景2:外部滑动方向和内部滑动方向一致。当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题。因为当手指开始滑动的时候,系统无法知道 用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层能滑动,要么就是内外两层都滑动得很卡顿

  • 场景3:上面两种情况的嵌套。内层有一 个场景1中的滑动效果,然后外层又有一个场景2中的滑动效果。具体说就是,外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个页 面中又是一个ListView。虽然说场景3的滑动冲突看起来更复杂,但是它是几个单一的滑动冲突的叠加,因此只需要分别处理内层和中层、中层和外层之间的 滑动冲突即可,而具体的处理方法其实是和场景1、场景2相同的

3、滑动冲突的解决方法:

  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;//不拦截DOWN事件
            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;
    }
    
  2. 内部拦截法:

    父容器不拦截任何事件,所有事件传递给子View,子View需要此事件就拦截消耗掉,否则就交给父容器处理,这种方法不同于Android的事件分发机制,需要配合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:{
                int deltaX=x-mLastX;
                int deltaY=y-mlastY;
                if(父容器需要此点击事件){
      //允许拦截 
               parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP:{
                break;
            }
            default:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(event);
                  
        }
        
    }
    

    上面是子元素做的处理,除此之外,父元素也要做处理

    父元素需要拦截除了ACTION_DOWN之外的其它事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件

    为什么父容器不能拦截ACTION_DOWN事件呢?那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父 容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用了。

    父元素代码如下

    public boolean onInterceptTouchEvent(MotionEvent event){
        int action=event.getAction();
        if(action==MotionEvent.ACTION_DWON){
            return false;//表示不拦截
        }else{
            return true;
        }
    }
    

外部拦截和内部拦截的时机不同,本质上解决问题的思想没有区别

内部拦截法没有外部拦截法简单易用,所以一般使用外部拦截法

2、滑动冲突的处理规则:

  • 场景1:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事 件。这个时候我们就可以根据它们的特征来解决滑动冲突

    根据坐标来确定滑动方向从而拦截点击事件:可以依 据滑动路径和水平方向所形成的夹角,也可以依据水平方向和竖直方向上的距离差来判断,某些特殊时候还可以依据水平和竖直方向的速度差来做判断。

  • 场景2,3:需要根据业务需求来决定。比如业务上有规定: 当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态时则需要内部View来响应View的滑动