-
Android 事件分发涉及到三个方法
public boolean dispatchTouchEvent(MotionEvent ev)
这个方法用来分发事件的
public boolean onInterceptTouchEvent(MotionEvent ev)
这个方法是用来判断是否需要拦截事件的,如果返回true,表示拦截了事件,如果返回了false,说明没有拦截事件。如果View拦截了某个事件,那么在同一个事件序列中,此方法将不会被再次调用。这个方法只有ViewGroup才会有
public boolean onTouchEvent(MotionEvent ev)
这个方法表示是否消耗掉了触摸或者点击事件,返回true说明消耗掉了,返回false表明没有
-
onTouchEvent()、OnTouchListener中的onTouch()方法和OnClickListener中的onClick()方法被调用的优先级问题
当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch()方法就会被回调,如果onTouch()返回false,则当前View的onTouchEvent()方法会被回调,如果onTouch返回true,那么onTouchEvent()方法将不会被调用。
由此可见,onTouch()方法得优先级比onTouchEvent()的要高。
在OnTouchEvent()方法中,如果当前设置有OnClickListener,那么它的onClick方法会被调用,由此可见,平时我们常用的onClickListener其优先级最低,处于事件传递的尾端。 -
事件传递顺序
当一个事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件总先是传递给Activity,再传递给Window,再传递给顶级View,顶级View接收到事件后,在按照事件传递的机制去分发事件 -
View事件传递机制
事件先传递给ViewGroup,此时ViewGroup的dispatchTouchEvent()会被触发执行,在dispatchTouchEvent方法里,接着会调用onInterceptTouchEvent()方法,如果onInterceptTouchEvent()方法返回true,说明此ViewGroup拦截了事件,事件不再往下传递,如果返回false,则说明此ViewGroup没有拦截事件,事件会传递到它的子View中去。如果此ViewGroup拦截了事件,则会调用此ViewGroup的onTouchEvent()方法,如果返回true,则说明ViewGroup消费了这个事件,如果返回false,则说明事件没有消费,事件会回传给上一级的View,如果上级View和Window都不做处理,则会回传给Activity。
如果ViewGroup没有拦截事件,事件将会传递到它的子View中去,如果子View是个ViewGroup,那么事件将会像上一级ViewGroup传递的规则那样传递事件,如果子View是个普通的View,那么它的dispatchTouchEvent()方法将会被调用,因为普通的View没有onInterceptTouchEvent()方法,所以紧接着它的onTouchEvent方法会被调用(在没有注册OnTouchListener的情况下),如果onTouchEvent()方法返回true,说明事件被消费了,如果返回false,事件没有消费,会回传给它的上一级View。
所以dispatchTouchEvent()方法返回true,事件会被当前View消费掉或者传给子View,如果返回fase,则事件会回传给上一级View。 -
ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)三种事件拦截规则以及调用父View的requestDisallowInterceptTouchEvent()方法对事件拦截规则的影响。
View的拦截方法是onInterceptTouchEvent()方法,如果ACTION_DOWN被拦截,则这次操作的其他事件(ACTION_MOVE、ACTION_UP)都不会再触发onInterceptTouchEvent()方法了,它们默认和ACTION_DOWN一样,交由这个View的onTouchEvent()或者回传到上一级View进行处理。
如果ACTION_DOWN没有被拦截,则这次操作的其他事件会继续传递到onInterceptTouchEvent()方法中,一般我们在处理滑动冲突的时候,会根据具体逻辑拦截ACTION_MOVE事件。
默认情况下不会对ACTION_UP做拦截,因为拦截ACTION_UP后,子View就无法监听到onClick()方法了,因为点击包括了ACTION_DOWN和ACTION_UP两个事件如果在子View中调用了父View的requestDisallowInterceptTouchEvent()方法,那么它不会改变父View ACTION_DOWN事件的拦截规则,但是会改变ACTION_MOVE和ACTION_UP的拦截规则。比如:父View拦截了ACTION_DOWN事件,无论在子View中调用父View的requestDisallowInterceptTouchEvent(boolean b)传false或者是true,其他事件都会被拦截,不会再传到子View。如果父View没有拦截ACTION_DOWN事件,如果在子View中,调用父View的requestDisallowInterceptTouchEvent(true),则所有事件都会传递给子View。如果在子View中,调用了父View的requestDisallowInterceptTouchEvent(false)方法,则ACTION_MOVE和ACTION_UP是否拦截就取决于父View了。
注:上面以及下文说的父View是指某个ViewGroup,子View是指这个ViewGroup里面嵌套的View,可能是普通的View,也可能是个ViewGroup
-
父View是怎么拦截事件的
重写父View的onInterceptTouchEvent方法,分别对接收到的各个事件处理,return true说明是做了拦截,return false说明是没有做拦截。public boolean onInterceptTouchEvent(MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: return false; //表示没有拦截ACTION_DOWN事件 //return true; 表示拦截了ACTION_DOWN事件 break; case MotionEvent.ACTION_MOVE: return false; //表示没有拦截ACTION_MOVE事件 //return true; 表示拦截了ACTION_MOVE事件 break; case MotionEvent.ACTION_UP: return false; //一般不对ACTION_UP做拦截 } }
-
View的滑动冲突处理方法
View滑动冲突的产生一般是因为两层都能滑动的View嵌套在了一起导致的,当产生滑动事件的时候,究竟是父View处理还是子View处理,就会产生冲突。
解决滑动冲突的方法一般有两种:外部拦截法和内部拦截法
现在以父View水平滑动,子View竖直滑动为例,分别阐述一下外部拦截法和内部拦截法外部拦截法:所谓外部拦截法就是指触摸事件都交给父View处理,如果父View需要拦截此事件就拦截,不需要就不拦截。示例代码如下:
private int mLastX; private int mLastY; @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: intercepted = false; break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; }else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } mLastX = x; mLastY = y; return intercepted; }
内部拦截法:内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要处理触摸事件就消费掉,不需要处理就交由父容器进行处理,这种方法需要用到requestDisallowInterceptTouchEvent配合,示例代码如下:
//重写子元素的dispatchTouchEvent()方法
private int mLastX;
private int mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(ev);
}
//重写父元素的onInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}else {
return true;
}
}
Android事件分发机制就说到这里,如有错误请指正,谢谢!