我们直接对比ViewGroup和view的dispatchTouchEvent方法来理解touch事件的分发。
如下是ViewGroup的dispatchTouchEvent方法:
//ViewGroup中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果是ACTION_DOWN这个动作的话,就会mMotionTarget设为null,来重新寻找目标view
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//disallowIntercept是否禁用拦截功能,onInterceptTouchEvent:是否拦截
//1.如果禁用了拦截即disallowIntercept为true,或者没有禁用,但是没有拦截,
//就会进入if之内寻找需要消费此touch事件的子view了
//2.如果拦截了,则不会进入此if中,则直接到64行执行
if (disallowIntercept || !onInterceptTouchEvent(ev)) {//禁用了拦截而且没有拦截
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//如果此处有其中一个子view的dispatchTouchEvent返回true,那么就把
//此子view当做mMotionTarget
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}//end action == MotionEvent.ACTION_DOWN
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//如果第37行没有成立,即当前viewgroup的所有子view的dispatchTouchEvent
//都返回false,那么就是没找到mMotionTarget,即mMotionTarget为null,在
//此情况下就会执行此句,也是View中的dispatchTouchEvent方法了,因为所有的
//ViewGroup的父类都是View,view的dispatchTouchEvent可以参考下一个方法。
return super.dispatchTouchEvent(ev);
}
//如果是允许拦截,而且拦截了
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
//如果mMotionTarget不为空,那么move和up事件直接交给其处理
return target.dispatchTouchEvent(ev);
}
如下是View的dispatchTouchEvent方法:
/* 此处为view的dispatchTouchEvent方法
* 如果此方法返回true,那么如上ViewGroup中的dispatchTouchEvent方法
* 的mMotionTarget就不为空了
*
* */
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
通过如上源码可以了解到,在viewgroup的dispatchTouchEvent方法的调用流程中,是先会去调用onInterceptTouchEvent方法(19行),
如果不允许拦截(disallowIntercept为true)或者是允许拦截(disallowIntercept为false)但不拦截(onInterceptTouchEvent返回false)的情况下,会把此touch事件分发给此viewgroup的所有子view(37行);在此情况之下,如果所有的子view的dispatchTouchEvent返回的都是false,那么mMotionTarget为null,就会执行到64行,super.dispatchTouchEvent(ev),也就是View中的dispatchTouchEvent方法了,因为所有的ViewGroup的父类都是View,所以64行表示viewgroup自己消耗了touch事件,怎么消耗的参考view的dispatchTouchEvent方法;如果有子view的dispatchTouchEvent方法返回true,那么就把他赋值给mMotionTarget,那么下次move和up事件来的时候直接交给其处理(91行)
如果onInterceptTouchEvent返回为true,也就是ViewGroup拦截此次touch事件,那么就不会进入if判断语句的内部,而会执行到64行,viewgroup自己来消耗touch事件。
总结来说,如果onInterceptTouchEvent为false但是所有的子view的dispatchTouchEvent都返回false,或者是直接拦截,在这两种情况之下,都会执行到64行,super.dispatchTouchEvent(ev),而此行也就是父类的dispatchTouchEvent方法,参考方法二。
看如下伪代码:
View mTarget=null;//保存捕获Touch事件处理的View
public boolean dispatchTouchEvent(MotionEvent ev) {
//....其他处理,在此不管
if(ev.getAction()==KeyEvent.ACTION_DOWN){
//每次Down事件,都置为Null
if(!onInterceptTouchEvent()){
mTarget=null;
View[] views=getChildView();
for(int i=0;i<views.length;i++){
if(views[i].dispatchTouchEvent(ev))
mTarget=views[i];
return true;
}
}
}
//当子View没有捕获down事件时,ViewGroup自身处理。
//此时把此ViewGroup当做一个view来看待
//这里处理的Touch事件包含Down、Up和Move
if(mTarget==null){
return super.dispatchTouchEvent(ev);
}
//...其他处理,在此不管
if(onInterceptTouchEvent()){
//...其他处理,在此不管
}
//当子View捕获了down事件时,mTarget不为空,那么Move和UP直接交给mTarget处理
//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
return mTarget.dispatchTouchEvent(ev);
}
此处的伪代码是viewgroup的dispatchTouchEvent方法的简化,可以很清楚的看到有子view消费事件和没有子view消费事件的流程
参考如下资料:
http://www.cnblogs.com/linjzong/p/4191891.html
http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/lmj623565791/article/details/39102591
同时可以验证
http://blog.csdn.net/chdjj/article/details/22910581
滑动冲突的案例