Touch事件传递与滑动冲突解决方案

2017-08-31  本文已影响0人  出云月

一.当一个触摸事件产生后,它的传递过程顺序如下:Activity -> Window -> DecorView,即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶层View DecorView;然后在不被拦截的情况下,触摸事件会被传递到触摸位置对应的最底层View。传递完成后就要处理触摸事件了,处理顺序是从最底层View向Activity进行的。

这里写图片描述

跟Touch事件有关的处理方法主要由三个:
//分派事件
public boolean dispatchTouchEvent(MotionEvent ev)
//拦截事件 只有ViewGroup才具有该方法
public boolean onInterceptTouchEvent(MotionEvent ev)
//处理事件
public boolean onTouchEvent(MotionEvent event)

为了能够方便理解事件分发的流程,我们设计一个实例,实例布局如图所示
1.如果所有的View都不处理事件(onTouchEvent方法返回false),整个事件分发流程对应如图所示:


这里写图片描述

2.如果把ViewGroupA或者ViewGroupB的onInterceptTouchEvent()方法返回true,即拦截事件,事件分发过程如下所示:


这里写图片描述 这里写图片描述

某个View一旦决定拦截,那么这一个事件序列都只能由它处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent()不会再被调用。也就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他事件都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent()去询问它是否要拦截了。

二.常见的滑动冲突场景
常见的滑动冲突的场景可以分为如下三种:
场景1 --- 外部滑动方向和内部滑动方向不一致
场景2 --- 外部滑动方向和内部滑动方向一致
场景3 --- 上面两种情况的嵌套

这里写图片描述

1.外部拦截法
所谓外部拦截法是指所有的触摸事件都会先经过经过父容器的传递,从而父容器在需要此触摸事件的时候就可以拦截此触摸事件,否者就传递给子View。这样就可以解决滑动冲突的问题,这种方法比较符合触摸事件的传递、处理机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在该方法中根据滑动冲突处理规则做相应的拦截即可,这种方法的典型代码如下:

@Override
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;
}

2.内部拦截法
内部拦截法是指父容器不拦截任何触摸事件,所有的触摸事件都传递给子元素,如果子元素需要此触摸事件就直接消耗掉,否者就交由父容器进行处理,这种方法和Android中的事件传递、处理机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。这种方法需要重写子元素的dispatchTouchEvent方法和父容器的onInterceptTouchEvent方法,这种方法的典型代码如下:

子元素的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
     getParent().requestDisallowInterceptTouchEvent(true);
     break;
 }
 case MotionEvent.ACTION_MOVE: {
     int deltaX = x - mLastX;
     int deltaY = y - mLastY;
     if (父容器需要当前触摸事件) {
         getParent().requestDisallowInterceptTouchEvent(false);
     }
     break;
 }
 case MotionEvent.ACTION_UP: {
     break;
 }
 default:
     break;
 }

 mLastX = x;
 mLastY = y;
 return super.dispatchTouchEvent(event);
}
父容器的onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
 int action = event.getAction();
 if (action == MotionEvent.ACTION_DOWN) {
     return false;
 } else {
     return true;
 }
}
上一篇下一篇

猜你喜欢

热点阅读