View事件的分发可以用如下伪代码来描述:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
根据图片其实已经可以大致总结出规律。
Touch事件由activity开始向下传递。
顶层容器的dispatchTouchEvent会对事件是否拦截进行判断。默认情况下不拦截事件,则事件会被分发到子View,如果onInterceptTouchEvent返回true,则事件交给本View的onTouchEvent方法去处理。
作为View,默认情况下在dispatchTouchEvent方法中会调用onTouchEvent方法。当onTouchEvent返回true时,代表View消费该事件,并且后续事件也交给View处理。否则后续事件不再交给View。
下面从源码的角度来对以上结论进行验证。
首先从activity的dispatchTouchEvent开始。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先会对ACTION_DOWN事件进行判断,如果满足条件,就会调用onUserInteraction方法。这个方法是系统预留的方法,在key事件,touch事件触发的时候会调用这个方法。如果想监听这些事件的触发可以重写这个方法。
接下来activity会把事件交给window处理并监听其返回值。如果返回true则activity的dispatchTouchEvent返回true,否则activity会调用自己的onTouchEvent方法。从这里也可以看出来,如果子View没有消费Touch事件,那么activity会自己处理事件。
接下来看一下window是怎么处理Touch事件的。这里的getWindow获取的实际上是PhoneWindow对象,那么去PhioneWindow的superDispatchTouchEvent方法中看一看。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这里的mDecor指的是DecorView,即顶层View。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView是FrameLayout的子类,而FrameLayout没有重写dispatchTouchEvent。那么调用的就是ViewGroup中的dispatchTouchEvent方法。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {//如果是down事件则进行初始化工作
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;//拦截事件标记
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//mGroupFlags中的一位来表示是否拦截事件,节约存储空间
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//判断是否拦截
ev.setAction(action); // restore action in case it was changed
} else {//如果设置了requestDisallowInterceptTouchEvent那么不拦截事件
intercepted = false;
}
} else {
intercepted = true;
}
......
if (!canceled && !intercepted) {
......
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);//获取点击事件坐标
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
......
//如果子View正在执行动画或者其区域不包括点击事件发生的坐标则不能接收事件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
......
resetCancelNextUpFlag(child);
//调用子View的dispatchTouchEvent方法并关心返回值
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//如果子View的dispatchTouchEvent返回true,则记录这个View,后续事件都传给他
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
......
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {//如果子View没有消费事件
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);//自己处理事件
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
......
}
......
}
......
return handled;
}
可以看到,在ViewGroup里面主要是做了这么几件事:
1.判断是否拦截点击事件,如果拦截事件,最终事件会交给自己的onTouchEvent去处理
2.判断子View是否能够处理事件,如果可以,就交给子View去处理,并关心其处理状态,如果子View没有消费事件,那么事件也会交给自己的onTouchEvent去处理。
接下来看一下View的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();//如果是down事件则停止滑动
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}//如果设置了onTouchListener那么事件会交给onTouch方法去处理,并且不会交给onTouchEvent去处理
if (!result && onTouchEvent(event)) {//如果事件还没被处理,就交给onTouchEvent处理,并且关心返回值
result = true;
}
}
......
return result;
}
可以看到View的dispatchTouchEvent主要是做了一些判断,并把事件交给onTouch或者onTouchEvent去处理。至于为什么onTouch处理后onTouchEvent就不再处理事件了。其实很好理解,便于对事件的控制。比如给View设置了onTouchListener,我们就可以在onTouch方法中判断是否要消费事件,而不会受到onTouchEvent的影响。
可以看到,从activity开始,事件其实是一个递归的过程,先分发到最底层View,如果最底层View不消费事件,那么事件就会回到上一级去处理,如果上一级还不消费事件,则事件会继续往上传递,直到activity。