首先我们来把viewgroup和view的dispatchTouchEvent源码贴出来,之前我们分析了viewgroup的源码和view的源码,那么我们来总结下.假设我们的事件流(down,move.....move,up)在中间的某一个move在onTouchEvent当中返回了false。
viewgroup的源码。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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();
}
// Check for interception.
//分析1开始
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;
}
//分析1结束
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//分析2开始
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
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();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//分析2结束
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 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 {
// 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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
我们在之前的文章当中知道了mFirstTouchTarget在down事件开始之前是为null的,在down事件按下之后执行到if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))这句判断语句的时候我们我们知道这个判断语句是真正执行分发的语句,那么mFirstTouchTarget对象在这个条件语句块里才正式被赋值。那么我们可以看到当执行的move事件时,会执行分析1里的语句,分析1由前文可知是判断是否拦截的,我们这里不做拦截,那么分析1可以直接略过,由此进入分析2,这里的分析2由于是move,所以会直接跳入到下面的代码。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} 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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
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;
}
}
去执行else里的代码块,这时候的alreadyDispatchedToNewTouchTarget为false,那么会执行while循环里的else代码块,那么我们可以看到在
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
else代码块里的这段代码执行了move事件的分发。同理up事件也是在该处执行的事件分发。那么接下来看看view的dispatchTouchEvent
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
在不考虑给view setOnTouchListener
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
该判断逻辑不会进入,当然这里提一句总结,如果该view设置了onTouchListener的监听,可以很明显的看到,它会优先执行该view的onTouch方法,如果该onTouch方法返回的是true,那么是不会执行onTouchEvent方法。那么我们看看当onTouchEvent返回了false,那么整个dispatchTouchEvent都会返回false。那么我们就要回到viewgroup真正的实际做分发的方法里去看,如果child的dispatchTouchEvent返回了false将会发生什么。这里就要回到viewgroup的dispatchTransformedTouchEvent方法中了
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
这里的代码我们分析过,只研究单点的问题,那么肯定if (newPointerIdBits == oldPointerIdBits)条件是满足的,那么我们去判断child.hasIdentityMatrix()是否成立就可以了,虽然不知道这个方法的含义,但是我们可以用逻辑推断,他的2个条件分支里面分别执行了handled = super.dispatchTouchEvent(event);和handled = child.dispatchTouchEvent(event);那么这里的child是什么呢,这个参数的实参是target.child,而target是mFirstTouchTarget,之前我们讲在down事件发生时,mFirstTouchTarget是在newTouchTarget = addTouchTarget(child, idBitsToAssign);这段代码里赋值的,而这里的child是对于这个viewgroup遍历他的子view产生的,所以这里的mFirstTouchTarget保存的应该是这个viewgroup子view的一个链表,假设这个viewgroup的深度只有1也就是里面是一个子view不是一个viewgroup,那么这个child就是这个子view,则可以得出结论child不为null,那么如果这个viewgroup的事件在down事件能顺利的分发到子view里面去的话那么肯定是执行了child.dispatchTouchEvent(event);这个语句,所以child.hasIdentityMatrix()条件则肯定成立的。我们在看到实际执行分发的代码中不论是handled = super.dispatchTouchEvent(event);和handled = child.dispatchTouchEvent(event);只能2选1,也就是说会不会到子view来进行分发,是判断这里的child是否为null是一个关键,如果child为null,那么就会调用super.dispatchTouchEvent(event);,这里的super是指viewgroup的上级view去处理了,进入的是view的dispatchTouchEvent(event),但是这里的super的对象就是viewgroup,如果调用child.dispatchTouchEvent(event);则是给子view去分发了,那么我们这里的能进入这个方法的途径在viewgroup的分发方法里有3个地方会调用,一个是在遍历的时候if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),这个是当为down事件时能进去,move和up都进不去,那么这里的child就是他实际的子view,还有一个地方能调用 handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);这里的判断条件是mFirstTouchTarget == null的时候,也就是说如果执行down,move,up事件时,这个时候的mFirstTouchTarget是有值的不为null,所以这里执行不到,剩下的一个可以执行到分发方法的代码是dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits),这里的target是mFirstTouchTarget,但是这个target是单链表结构的,他在循环体中,没进入一次while的循环体,这个target的值的会变为他的指针域的对象,上面我们假设过这个viewgroup的深度为1,那么这里的target在第一次进入这个循环体的时候target.child就是这个viewgroup的子view,然后这个target就会指向他的指针域,这个时候的指针域指向的是一个null,那么第二次进入循环体的时候判断就不符合条件,则会跳出循环体。那么我们在move,up事件的时候,始终会进入到分发事件代码中,我们也分析完了分发的代码,在分发中,如果子view的onTouchEvent返回false是发生在move或者up的时候,其实到最后新产生的move事件还是会分发到子view,而作为viewgroup的时候始终处理不了在某个时间点的事件,而如果当在down事件发生的时候返回false,那么我们在viewgroup的事件分发的代码中可以得知子view的分发返回的是false,那么在第一个dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits);条件判断的时候这里的if条件进入不了,那么mFirstTouchTarget对象就无法初始化,也就说这个单链表结构是个空链,则会进入handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);这里的语句,因为他的判断条件是mFirstTouchTarget==null,那么他会将这个子view当成一个普通的视图去处理,这里可以看到dispatchTransformedTouchEvent方法当中的view的参数直接传一个null值,那么他实际上就是交给他的super来进行分发了,则后面的move,up事件在产生的时候,因为在一组事件流当中只有一个down和up事件,其他的N个move事件,也就说在一个事件流当中,如果down事件的子view在onTouchEvent返回了false,那么后面的move和up事件不会在有mFirstTouchTarget初始化的地方,表示move和up事件不会分发到子view去处理。所以这里我们可以得出结论,如果子view的down事件中onTouchEvent返回false,那么子view不再分发事件,如果子view在down事件中onTouchEvent返回了true,那么子view会接收后续事件,一直到事件流完成,而他的父view不会在处理事件。所以如果我们要处理事件冲突的时候,肯定是在符合条件的某个时间段点想让子view去处理事件,而不符合条件的时间段由父view来处理,如果我们我们要处理事件冲突,在view的onTouchEvent方法里来处理就变的不方便,我们可以在父view的事件拦截的方法中去处理,在某个条件下,让父view处理,把事件拦截掉,在其他条件下让子view来处理,不去拦截。后面的while循环的代码贴出来,其实就是一个遍历链表的一个算法
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
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;
}
while循环里的else代码块中第一个if判断则为false,执行第二个if判断,假设cancelchild为true的时候,TouchTarget 是一个单链表的一个结构,我们可以看到这里的next是mFirstTouchTarget这个节点的指针域,如果predecessor的对象为null的时候就是将mFirstTouchTarget头部节点往后移动一个位置,如果不为null那么声明的predecessor节点则等于mFirstTouchTarget的指针域,单链表的指针域都是指向的下一个节点,可以看到最后的结果target也移动到了头节点的下个节点,这里的while条件判断的是target这个节点不为null,那么可以知道这就是一个遍历链表结构的代码一直找到链表的链尾。