事件分发笔记
为什么写这篇
因为学事件分发看得最多的就是 u 型图,不知道有没有人跟我一样一直很疑惑为什么子View不消费就会让父View消费呢,为什么一旦接受了down事件,后续事件都会由它来处理呢,为什么拦截了之后后续事件都会发送给这个View呢…所以才想着记录下来,单纯记结论我感觉太难了(很容易搞乱…)
核心方法是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;
//重置mFirstTouchTarget
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//【分析点1】
final boolean intercepted;
//判断是否调用onInterceptTouchEvent是根据两个条件
//1、事件是否为DOWN
//2、mFirstTouchTarget是否被赋值了。
//mFirstTouchTarget是一个很重要的变量,它代表着
//是否有子View消费了事件。
//这个变量的赋值在addTouchTarget中,
//而addTouchTarget是在遍历子View的for循环中调用的
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//【分析点2】
//这是一块很长的代码块,只能在这个代码块里面才执行遍历子View
if (!canceled && !intercepted) {
//【分析点3】
//ACTION_DOWN、ACTION_POINTER_DOWN、ACTION_HOVER_MOVE才会进入遍历方法
//mFirstTouchTarget才有可能被赋值
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
//子view存在,开始遍历
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
...
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
//【分析点4】
//这个方法会调用child的dispatchTouchEvent
//如果返回true,即代表子View消费了事件(因为这个方法是带返回值的),给
//mFirstTouchTarget赋值
//否则跳过
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
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();
//【分析点5】
//给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//跳出for循环
break;
}
}
}
...
}
}
//【分析点6】
//如果(!canceled && !intercepted)不成立,又或者满足,但是事件不是
//ACTION_DOWN、ACTION_POINTER_DOWN、ACTION_HOVER_MOVE其中之一,就进入这里
if (mFirstTouchTarget == null) {
//为空代表没有子View消费事件,注意dispatchTransformedTouchEvent的第三个参数null
//它会导致调用super.dispatchTouchEvent(event);
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//【分析点7】
//mFirstTouchTarget != null的情况,也就是消费了DOWN事件
//可以理解为这里是后续的MOVE、UP会调用的地方
//调用分发,注意这个pointerIdBits,dispatchTransformedTouchEvent根据这个
//来判断是新的事件还是旧的事件,从而实现让消费了DOWN的事件也能继续消费MOVE和
//UP
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
return handled;
}
1、为什么子View的onTouchEvent返回false(不消费),就会调用父View的onTouchEvent
可以看到
分析点5、6
,因为mFirstTouchTarget
只有在for遍历里面通过addTouchTarget
赋值,因为子View
不消费,所以dispatchTransformedTouchEvent
返回false,代码就会继续执行到分析点6,而且传入的第三个参数是为null,从而调用到super.dispatchTouchEvent(event);
,又因为ViewGroup
的父类是View
,dispatchTouchEvent
最终会调用到onTouchEvent
,所以就表现为自己消费
2、为什么一旦接受了down事件,后续事件都会由它来处理
根据
分析点7
,mFirstTouchTarget
已经被赋值的情况下,进入dispatchTransformedTouchEvent
通过对比pointerIdBits
来判断是否还是原来消费DOWN
的那个目标View
,是的话继续分发给它
3、为什么拦截了之后后续事件都会发送给这个View呢
同1,最主要的原因还是
mFirstTouchTarget
这个变量,因为intercepted
为true
,就不会进入for
循环的代码块,导致mFirstTouchTarget
为null
,进而促使它调用自己的dispatchTouchEvent