Android Touch事件的分发机制一直没有仔细去看。今天以一个例子总结一下。
用这样一个情形来讲下,一个LinearLayout A 里面嵌套一个LinearLayout B,然后这个B里面有个一个Button C。用户点击Button C的触发流程以及各种情况的分析如下:
View的分发机制毫无疑问是从根View至子View进行分发。而View事件的分发是由dispatchTouchEvent来进行的。所以这时候会首先走A(继承于ViewGroup)的dispatchTouchEvent,下面贴出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;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
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;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
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;
}
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;
}
return target.dispatchTouchEvent(ev);
}
可以看出dispatchTouchEvent方法里会有一个
onInterceptTouchEvent方法。对于这个方法的理解是这样的,如果这个方法返回true,表明这个view将事件进行拦截了,这样事件就不会再往子view进行传递。然后就会执行的是这个view的onTouchEvent方法。如果onInterceptTouchEvent方法返回false,则dispachTouchEvent事件会往子View进行传递,也就是会执行子View的dispachTouchEvent方法。这个子View也是会进行和父View一样的逻辑处理。这些事件的传递要么会在某层View被拦截然后执行某层View的onTouchEvent事件;要么会一直进行传递一直到一个没有子View属性的View,比如说TextView、Button等。然后会执行这个子View的dispachTouchEvent方法。对于Button这样的View,其本身是没有dispachTouchEvent方法,一直追溯到其父类View的代码会有dispachTouchEvent方法如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
如果子View没有设置onTouch事件就会走子View的onTouchEvent事件,否则会直接返回true。
如果子View返回false,也就是子View 的dispachTouchEvent返回false,则会将事件向上传递,然后执行分发View的onTouchEvent事件。
对开头的例子有以下几种情况:
1、A 的dispatchTouchEvent肯定会被执行,然后接着执行A的onInterceptTouchEvent,如果onInterceptTouchEvent返回true,则A对事件进行了拦截,执行A的onTouchEvent事件。事件不会再往下传递;如果返回false,则事件会传递到B,
2、B的dispatchTouchEvent会进行执行,然后接着执行B的onInterceptTouchEvent,如果返回为true,则B对这个事件进行了拦截,会执行B的onTouchEvent事件。如果返回false,则事件继续往下传递到C。
3、C是一个原子View,也就是不能包含子View的View,所以其dispatchTouchEvent的实现和ViewGroup不一样。如果C设置了onTouch事件,则C的dispatchTouchEvent直接返回true,事件结束。这时候也不会执行C的onTouchEvent事件,因为onClick事件是在onTouchEvent方法里执行,所以onClick事件也不会执行。如果C的dispatchTouchEvent返回false,会执行父View的super.dispatchTouchEvent,其实也就是View的dispatchTouchEvent,这时候也就是会执行B的onTouchEvent方法,如果这时候B的dispatchTouchEvent也返回false,则会回到A的dispatchTouchEvent方法。如果A也返回false,那就到Activity的onTouchEvent了。