事件分发机制源码分析
# 分发流程
Activity.dispatchTouchEvent --> PhoneWindow.superDispatchTouchEvent --> DecorView.superDispatchTouchEvent --> ViewGroup.dispatchTouchEvent
# ACTION_DOWN
每次事件都从MotionEvent.ACTION_DOWN开始
ACTION_DOWN到达时会清空mFirstTouchTarget和mGroupFlags,并检测onInterceptTouchEvent(ev)判断当前viewGroup是否需要拦截,
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清空标记
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查是否需要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//由于前面已经重置了标记,这里disallowIntercept一定会为false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
...
}
...
}
拦截的话由于mFirstTouchTarget为null,将调用dispathTransformedTouchEvent并传递child为null,由于child为null将调用本身的dispatchTouchEvent(此方法为父类view所有)
private boolean dispatchTransformedTouchEvent(...) {
...
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
// child为null,调用父类View的dispatchTouchEvent方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
...
}
不拦截的话将ACTION_DOWN的分发给合适的子类(子类根据Z轴和定义的顺序递归、判断是否可获取焦点,是否在子类的坐标内等),同样调用dispathTransformedTouchEvent并传递该子view后,调用child.dispatchTouchEvent,如果子View消费了该事件,viewGroup将把该view存入mFirstTouchTarget
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (!canceled && !intercepted) {
...
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 经过一系列的判断后找到可以处理事件的子view
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
// 子view成功消费事件后把该view存入mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
# ACTION_MOVE、ACTION_UP
后面ACTION_MOVE、ACTION_UP到达时,先判断FLAG_DISALLOW_INTERCEPT是否被设置,被设置将跳过拦截,当跳过拦截或不拦截时,此时由于mFirstTouchTarget不为null,直接将该事件分派给该子view,如果此时事件被拦截,mFirstTouchTarget将再次被置为null,且mFirstTouchTarget保存的子view将收到ACTION_CANCEL的事件(后面的事件只能viewgroup自行处理)
//事件被viewGroup拦截后,此时mFirstTouchTarget不为null
if (mFirstTouchTarget == null) {
...
} else {
// 走这里
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
...
// intercepted为true,所以cancelChild为true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//还是在这个方法内,给已保存的子view发送一个ACTION_CANCEL的事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
...
}
}
# 解决事件冲突
针对事件冲突,可使用外部拦截法和内部拦截法(都是已经分发完ACTION_DOWN后)
# 外部拦截法
由viewgroup的onInterceptTouchEvent内判断是否拦截,拦截后mFirstTouchTarget被置为null,将保存的子view将收到ACTION_CANCEL的事件,接下来的事件都只能自己处理。
# 内部拦截法
稍微复杂一点,viewgroup除了ACTION_DOWN不拦截(ACTION_DOWN如果拦截,子view将不再接收到后面的事件)其他都拦截,子view在收到ACTION_DOWN时调用parent的requestDisallowInterceptTouchEvent(true),让parent不拦截事件,dispatchTouchEvent中判断是否需要继续消费事件,不消费则再调用parent的requestDisallowInterceptTouchEvent(false)将接下来的事件都交给parent。