dispatchTouchEvent事件分发总结:
针由down开始的一系列触摸事件来说,到View后要么处理掉它,要么就派送给别的View。所有的View事件的入口都是dispatchTouchEvent方法,一个事件来了,要如何处理,由dispatchTouchEvent决定。View和ViewGroup,dispatchTouchEvent的处理是不同的。
ViewGroup是View的容器,有可能包含子View,当事件来了,是要传给子View,还是自己接手,而View是不包含子View的,事件来了,只能自己接手,这就是他们的区别。
一个最简单完整的触摸事件包括down,move..,up甚至还有cancel。
主要源码分析
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();
}
如果是Down,重置touch状态,清理和取消所有的TouchTarget,在mFirstTouchTarget不为空时,给他的子View(如果存在)发送cancel event,执行子View的dispatchTouchEvent方法,最后清空mFirstTouchTarget。
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;
}
如果是Down或者mFirstTouchTarget不为空,判断是否拦截,执行方法onInterceptTouchEvent判断,否则拦截true。
如果是Move但是mFirstTouchTarget为空,则说明,Down并没有在子View中找到处理目标,即Down事件结束后并没有mFirstTouchTarget的值,这时也不用往下传了,下面既然Down都搞不定,Move也不需要给你处理,这时打断是true,mFirstTouchTarget为空。
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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 ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
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();
}
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;
}
}
}
不拦截,传递给子View执行,当然,只有Down的条件下才会传递给子View。
childIndex是在xml中定义的子View的顺序,不过可以根据setChildrenDrawingOrderEnabled(boolean enabled)控制顺序,当isChildrenDrawingOrderEnabled返回true时, preorderedList == null时,customOrder为true,childIndex由getChildDrawingOrder决定。
preorderedList不是null时,由Z轴的大小值决定。大的先分发。当时null时,根据绘制child的顺序决定,一般是按照xml的顺序。
canViewReceivePointerEvents为true,说明child view可见并且存在动画,isTransformedTouchPointInView为true 说明点击在view内部,两个函数都不符合时,再看下一个childview。
newTouchTarget不为空,并且childView数量不为0,遍历每个child,找到target为child的target为newTouchTarget,先根据child查找newTouchTarget,从已mFirstTouchTarget为首的链表查找target,
如果newTouchTarget不为null,则结束循环,功能就是设置newTouchTarget。
如果newTouchTarget为空,传送给子view事件,dispatchTransformedTouchEvent返回true,处理了事件,设置newTouchTarget,并插到mFirstTouchTarget链表前面,结束循环。
循环结束后,newTouchTarget为空,mFirstTouchTarget不为空,则给newTouchTarget先设置为mFirstTouchTarget,看mFirstTouchTarget的next找到第一个不为null的next,给newTouchTarget设置给子view排序,buildOrderedChildList的目的,先根据mFirstTouchTarget,来获取newTouchTarget,对mFirstTouchTarget,
遍历完所有子view,newTouchTarget,仍未null,将其赋值为mFirstTouchTarget最末端的,也就是最后的next。
</pre><pre>
// 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;
}
}
上面已经有了拦截或不拦截,不管怎么样,处理结果已经有了。
mFirstTouchTarget如果为空,说明可能是Down,下面的没搞定(没处理),好吧,我自己处理,如果是move呢,道理一样的,这种状态肯定直接就拦截了,呵呵,还是自己处理,mFirstTouchTarget不为空表明已经有处理的事件目标,会直接返回true(down的时候,下面的view有了消费事件,alreadyDispatchedToNewTouchTarget为true),或者看是否拦截或取消(move的时候),如果不拦截了,就给cancelChild为true,给子view发cancel事件,如果没有拦截,cancelChild就是false,继续给下面的View消费。最后mFirstTouchTarget=next,next其实就是空,拦截后的下次move就不会有mFirstTouchTarget了,事件也就不会传给下面了。
// 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);
}
如果下面的ViewGroup收到cancel,会把自己的mFirstTouchTarget初始化为空。
自己手画了个流程图。呵呵
1:down,move,up等事件都会来dispatchTouchEvent过过场子。
2:down先来,之前的遗留target先清掉,看看需不需要打断--->去onInterceptTouchEvent,需要打断的直接来自己的onTouchEvent,不需要打断的去子View处理,处理好了,给mFirstTouchTarget赋值
3:move进来,看mFirstTouchTarget有值么,没有?下面的view不给力,down没处理好,好吧,肯定要打断,自己处理,处理好了自己的dispatchTouchEvent返回true噢,,有mFirstTouchTarget,好吧,下面的已经搞定了,那打断不?去onInterceptTouchEvent过一遍,打断的话,给子view发cancel,不打断的话继续走子view处理。
4:从上层看,dispatchTouchEvent返回的如果是true,总是会被处理掉的,要么子view处理,要么本身处理的。如果返回false,对上层调用的dispatchTouchEvent来说,总归是没处理的,至于怎么处理,就去父view即上层的onTouchEvent吧
5:从上层看他和从他看下层子view,逻辑都是一样的。