前言

上面 demo 用外部拦截法实现了滑动方向一致和不一致的两种冲突。

滑动冲突场景

  • 场景 1————外部滑动方向和内部滑动方向不一致
  • 场景 2————外部滑动方向和内部滑动方向一致
  • 场景 3————上面两种情况的嵌套

场景 1,主要是 ViewPager 和 Fragment 配合使用组成的页面横向滑动效果,而 Fragment 里又包含了 ListView 竖直滑动的控件。由于 ViewPager 里做了滑动冲突的处理,所以使用 ViewPager 时没有出现滑动冲突的现象。

场景 2,内外两层都在同一个方向可以滑动。比如,使用 ViewPager 和 Fragment 配合做页面横向滑动。在 Fragment 中使用 RecyclerView 做横向滑动效果。

场景 3,是场景 1 和场景 2 的结合。比如,使用 ViewPager 和 Fragment 配合做页面横向滑动,Fragment 中使用 ListView 做竖直方向的滑动,而 ListView 中的 item 中包含横向滑动的 RecyclerView。这种场景有些复杂,但有时在实际工作也会遇到,比如我公司项目锋绘动漫的首页就是这种场景。

滑动冲突的解决方式

外部拦截法

外部拦截法是指触摸事件先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则不拦截。外部拦截法需要重写父容器的 onInterceptTouchEvent 方法,在此方法中做相应的拦截即可,伪代码如下:

@Override public boolean onInterceptTouchEvent(MotionEvent ev) {

    boolean intercept = false;
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        intercept = false;
        break;
      case MotionEvent.ACTION_MOVE:
        int dealtX = x - mInterceptX;
        int dealtY = y - mInterceptY;
        if(父容器需要当前事件){
          intercept = true;
        }else{
          intercept = false;
        }
        break;
      case MotionEvent.ACTION_UP:
        intercept = false;
        mInterceptX = mInterceptY = 0;

        break;
    }

    return intercept;  }

在 onInterceTouchEvent 方法中,ACTION_DOWN 事件,父容器必须返回 false,因为一旦父容器拦截了 ACTION_DOWN,那么后续的 ACTION_MOVE 和 ACTION_UP 都会直接交由父容器处理,事件无法再传递给子元素。ACTION_UP 也必须返回 false,因为如果父容器 ACTION_UP 返回 true,就导致子元素无法接受 ACTION_UP,子元素中的 onClick 事件也就无法触发了。

内部拦截

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

@Override public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()){
      case MotionEvent.ACTION_DOWN:
        getParent().requestDisallowInterceptTouchEvent(true);
        break;
      case MotionEvent.ACTION_MOVE:
        if (父容器需要此事件){
          getParent().requestDisallowInterceptTouchEvent(false);
        }

        break;
      case MotionEvent.ACTION_UP:

        break;
    }
    return super.dispatchTouchEvent(ev); }

上述代码是内部拦截的典型代码,当面对不同的互动策略时只需要修改里面的条件即可。除了子元素需要做处理父元素也要重写 onInterceptTouchEvent 方法,拦截除了 ACTION_DOWN 以外的其他事件,这样当子元素调用 getParent().requestDisallowInterceptTouchEvent(false) 时,父元素才能拦截所需要的事件。