View的事件分发主要是针对MotionEvent事件的分发,下面通过Android源码一步步分析MotionEvent事件的分发过程。
当一个点击事件发生时,事件首先传递给当前activity,由当前activity的dispatchTouchEvent方法进行分发,下面看下该函数的源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
通过代码可以看出,事件的分发工作主要交给当前activity内部的Window进行分发,继续看Window的superDispatchTouchEvent方法,由于Window类的唯一实现是PhoneWindow类,所以下一步继续查看PhoneWindow的superDispatchTouchEvent方法,代码如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
可以看出PhoneWindow类什么也没做,只是将事件转交给mDecor处理,mDecor是DecorView类型的一个对象,DecorView继承自FrameLayout, FrameLayout又继承自ViewGroup,下面看下DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
可以看到,只是调用了父类的dispatchTouchEvent方法,由于FrameLayout没有重写该方法,所以最终还是调用ViewGroup中的方法。
下面重点分析一下ViewGroup中对事件分发处理的过程,主要看一下dispatchTouchEvent方法中的代码片段
// 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;
}
此处代码表示ViewGroup要不要对当前事件调用onInterceptTouchEvent,当事件为ACTION_DOWN时,一定会调用onInterceptTouchEvent,mFirstTouchTarget != null不为空的情况在于,当ViewGroup将ACTION_DOWN交给它的任一子View处理时,且该View的DispatchTouchEvent返回true时(即该View处理此ACTION_DOWN事件),则mFirstTouchTarget 会被指向该View。所以当同一事件序列的后续事件中的ACTION_MOVE,...,ACTION_UP事件到来的时候,onInterceptTouchEvent不会再被调用。
当ViewGroup不拦截事件时,事件会交由ViewGroup的某个子View处理,具体代码如下:
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
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;
}
// 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();
}
是否将事件交由某个子View来处理,由以下两个函数做判断,canViewReceivePointerEvents和isTransformedTouchPointInView,前者判断子View是否可见以及是否在播放动画,后者判断点击事件的坐标是否落在子元素的区域内,如果这两点都满足,那么事件就交由该子元素处理,从而完成一轮事件的分发。此时会在dispatchTransformedTouchEvent里调用子元素的dispatchTouchEvent方法
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;
}
如果dispatchTransformedTouchEvent返回true,也即子View处理此ACTION_DOWN事件,那么执行下面if分支里的代码。
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处理了ACTION_DOWN事件,则在addTouchTarget方法里mFirstTouchTarget 会被赋值指向此子View, 代码如下
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}