Android的事件分发其实就是MotionEvent的事件的分发过程,即当一个MotionEvent产生以后,系统需要把这个事件传递给一个具体的View,这个传递过程就是分发过程。MotionEvent的分发过程由三个重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
当一个点击事件产生后,它的传递过程遵循如下顺序:Activity -> Window -> View
那么我们就先看下Activity中的dispatchTouchEvent方法吧!
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
onUserInteraction();该方法是空方法,需要自己实现。
getWindow().superDispatchTouchEvent(ev)从这个方法开始分发,但是你点击进去会发现该方法是抽象方法,所以我们需要查看它的实现类,从Window的代码注释可以看到,该类只存在一个PhoneWindow实现类,那么我们去看看PhoneWindow的superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
该方法调用了DecorView的superDispatchTouchEvent方法,DecorView这个类继承了FrameLayout,由此可见调用的其实就是ViewGroup中的dispatchTouchEvent方法,那么我们就来看看其中的具体实现吧
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
方法一进来我们就可以看到每次ACTION_DOWN的时候源码会重置触摸状态和清空触摸目标,注意:变量mFirstTouchTarget就是用来保存触摸目标的
// Check for interception.final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
// restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
这段代码我们看到是处理中断事件的,具体逻辑是先判断MotionEvent的行为是否为ACTION_DOWN或者mFirstTouchTarget是否不为空,然后判断mGroupFlags是否被设置,该值的设置由子View调用requestDisallowInterceptTouchEvent方法以请求父View不允许中断事件分发
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
接着查看源码,如果本view不中断事件,那么intercepted就返回false,就执行以下代码
if (!canceled && !intercepted) {
......
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);
......
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
......
}
这段代码中首先遍历子View,然后调用dispatchTransformedTouchEvent方法去向子类分发MotionEvent,分发完成并且返回true的话,就调用addTouchTarget方法为mFirstTouchTarget赋值,注意:这里首次为mFirstTouchTarget赋值,之前都是为空的
执行完以上代码接着执行以下代码
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
......
}
可见如果之前intercepted为true,也就是当前View中断了事件的分发,那么就会调用dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);注意参数null,其实和以上intercepted为false执行的子View分发调用的方法一样,只是传递的参数不同,那么让我们看看dispatchTransformedTouchEvent方法的内部实现
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
......
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
......
}
由此可见mFirstTouchTarget为空的时候就调用了View的dispatchTouchEvent方法
那么我们接着看View中的dispatchTouchEvent方法的具体实现
public boolean dispatchTouchEvent(MotionEvent event) {
......
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
......
return result;
}
从源码可见,它判断了是否有设置触摸监听器,如果有就调用它的触摸监听器,如果没有设置,就调用onTouchEvent方法,onTouchEvent方法中我们发现当触摸行为为ACTION_UP的时候会执行onClick方法(如果有设置的前提下)
到此事件分发就结束了,我们来总结一下
1,View是没有中断事件的,一旦传递给它那么就回调用onTouchEvent事件(没有设置触摸监听器的前提下)
2,一旦ACTION_DOWN的时候onInterceptTouchEvent返回true,那么后续的MOVE和UP等事件就不会向下分发,直到下一次ACTION_DOWN的到来(也就是下一次点击事件)
3,如果都没有处理事件,也就是所有都返回false,那么最终会调用Activity的onTouchEvent方法
个人愚见,如有错误,欢迎指正