ViewGroup事件分发
viewgroup通过dispatchTouchEvent进行事件分发,在一组触摸事件中,触摸点id不会发生改变。
认定ACTION_DOWN(按下事件)作为一组事件的开始,每当收到按下事件时,都会清空touchTarget,然后进行子view进行事件分发。在对子view进行事件分发时,会把消耗本次事件的view保存到touchTarget中。
由于按钮事件发生时,touchTarget会被清空,所以按钮事件之外的事件(滑动,抬起)才能在touchTarget事件中找到之前响应的view,在进行事件分发时遍历view时,会去touchTarget中寻找这次分发的view是否存在。
遍历子view之前的工作
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
.......
遍历子view
}
我们可以看到,拦截事件只作用于DOWN事件,其次遍历子view也只发生在以下事件中MotionEvent.ACTION_DOWN、MotionEvent.ACTION_POINTER_DOWN、MotionEvent.ACTION_HOVER_MOVE,我们可以看到没有MOVE、UP事件。也就是子viewDOWN事件不要的话,这一组事件的其他事件也收不到了。
事件分发遍历子view
首先判断当前点击的位置是否在当前的子view中,不在的话执行continue,在验证下一个view,遍历默认是按addView的倒置顺序遍历。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
如果点击的位置在当前viewGroup的子view的范围(即点击的是否为当前view),那么执行这个子view的dispatchTouchEvent前先判断touchTarget中是否已经存在当前view,存在的话说明当前的事件是此view前一个事件的后续事件,需要换个一个跳出当前view的遍历,执行其他的逻辑。这里也有一个作用:把这段放在当前点击的位置是否在当前子view的范围之后,在后续的事件过来后能保证这个事件是该view的后续事件。
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;
}
如果touchTarget中不存在的话,说明这是这一组事件的第一个事件,执行这个view的dispatchTouchEvent,如果这个view消耗掉了这个事件(也即onTouch或onTouchEvent返回true)并把这个view加入到touchTarget中。
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);
TouchTarget事件分发
当前viewGroup没有找到可以消耗本事件的View时,就会再次把本事件分发给自己(但自己也不一定会消耗掉,也就是说还会往父View传递消耗结果),一般是由于点击了子view之外的区域。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
如果第一个touchTarget不为null,不管前面是否消耗掉本事件,只要第一个touchTarget不为null都会执行。while(target != null)循环主要用于多点触控点击了不同view调用的,因为多点触控时: actionMasked == MotionEvent.ACTION_POINTER_DOWN,所以不会清空touchTarget。
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;
while (target != null) {
final TouchTarget next = target.next;
//如果这个事件已经被消耗掉了,就不需要再次dispatch了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//这里的代码执行只能是由于newTouchTarget = getTouchTarget(child)时返回非null
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}