View的滑动冲突

事件分发机制

public dispatchTouchEvent(TouchEvent ev)
{
  boolean consume=false;
  if(onInterceptTouchEvent(ev)){
   consume= onTouchEvent(ev);
  }else{
    consume=child.dispatchTouchEvent(ev);
  }
return consume;
}

通过上面的伪代码,我们可以大致了解点击事件的传递规则:对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true,就表示它要拦截当前事件,接着事件交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截当前事件,这时当前事件会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。
当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListenter中的onTouch方法被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前view的onTouchEvent方法会被调用,如果返回true,那么onTouchEvent方法不会被调用。由此可见,给view设置OnTouchListener,其优先级比onTouchEvent要高。
当一个事件产生后,它的传递过程遵循如下原则:Activity->Window->View,即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级view。顶级view接收到事件后,就会按照事件分发机制去分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,以此类推。如果所有的元素都不处理这个事件,那么这个事件将会最终传给Activity处理,即Activity的onTouchEvent方法被调用。

滑动冲突场景

** 场景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 dealX = x-mInterceptX;
    int dealY = y-mInterceptY;
    if(父容器需要当前事件){
      intercept = true;
    }else{
      intercept=false;
    }
  case MotionEvent.ACTION_UP:
      intercept = false;
      mInterceptX = mInterceptY =0;
      break;
  }
  return intercept;
}

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

内部拦截法

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

@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().requestDiallowInterceptTouchEvent(false)时,父元素才能拦截所需要的事件。

你可能感兴趣的:(View的滑动冲突)