dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
......
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) {
cancelAndClearTouchTargets(ev);
resetTouchState();//1
}
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//2
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//3
ev.setAction(action);
} else {
intercepted = false;//4
}
} else {
intercepted = true;//5
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//6
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 childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();//7
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);//8
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//9
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
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;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {//10
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);//11
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//12
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;
}
}
......
return handled;
}
当是down事件的时候,则会直接重置状态,在1处有一个重要的处理,即将mFirstTouchTarget置为空。
resetTouchState()
private void resetTouchState() {
clearTouchTargets();
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
mFirstTouchTarget关系到后面的分析。
当我们点击的时候,会先走MotionEvent.ACTION_DOWN事件,所以会进入第一个if中,然后看2处,这个值可以通过requestDisallowInterceptTouchEvent()设置,默认是false。因为为false,则继续进入到第二个if中,调用onInterceptTouchEvent()来判断是否拦截,这个时候分两种情况讨论。
onInterceptTouchEvent()返回true 表示拦截
当onInterceptTouchEvent()返回true时,intercepted则为true。则进入6处的判断。因为是取反,所以不会进去.
继续11处的判断,因为一开始mFirstTouchTarget被置为null,所以进入if中,调用dispatchTransformedTouchEvent()。
dispatchTransformedTouchEvent()
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//1
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
if (newPointerIdBits == 0) {
return false;
}
......
if (child == null) {//2
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);
}
transformedEvent.recycle();
return handled;
}
第一次进来,cancel为false,且不是MotionEvent.ACTION_CANCEL事件,所以不会进入dispatchTransformedTouchEvent方法中的1处。
前面传进来的child为null,所以会直接进入2处的if中。调用父类的dispatchTouchEvent,方法。而ViewGroup的父类是view,所以会进入View的dispatchTouchEvent中。然后调用onTouch--》onTouchEvent --> onClick or onLongClick,
具体细节看 https://www.jianshu.com/p/7c5bd3b97968
onInterceptTouchEvent()返回false 表示不拦截
不拦截的时候intercepted为false,则会进入6处的if。
进入之后,先根据Z值进行排序。然后寻找对应的child,找到对应的child之后,则调用dispatchTransformedTouchEvent进行分发。只有当dispatchTransformedTouchEvent返回true时,将mFirstTouchTarget赋值,并且alreadyDispatchedToNewTouchTarget置为true。否则还是不会将mFirstTouchTarget赋值。
对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()
该方法返回boolean,如下:
true---->事件被消费----->mFirstTouchTarget!=null
false--->事件未被消费--->mFirstTouchTarget==null
因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了
dispatchTransformedTouchEvent()的返回值!!!!从而决定了mFirstTouchTarget是否为null!!!!!!从而进一步决定了ViewGroup是否处理Touch事件.
既不是MotionEvent.ACTION_DOWN,mFirstTouchTarget=null
mFirstTouchTarget==null表明事件未被消费,即Down并没有在子View中找到处理目标,即Down事件结束后并没有mFirstTouchTarget的值,这时也不用往下传了,下面既然Down都搞不定,Move也不需要给你处理,这时打断是true。就跟拦截一样的流程
MotionEvent.ACTION_MOVE
当为move事件的时候,当view进行分发,然后mFirstTouchTarget不为null,则进入第一个if中,继续走到3处,分为两种情况,不拦截和拦截。
当不拦截的时候,会进入6处的if,但是现在是ACTION_MOVE事件,所以不会走进7、8、9处,mFirstTouchTarget不为null不为null,进入10处的else,然后根据taget.child继续分发。(简单的理解就是,包一个工程,交给别人做且跟签了合同了,那就继续给别人做)
当拦截的时候,不会进入6,并且会将alreadyDispatchedToNewTouchTarget = false。当alreadyDispatchedToNewTouchTarget = false时,将会进入12处的else。因为intercepted = true,name cancelChaild = true,然后进入dispatchTransformedTouchEvent(),第二个参数为true,这个时候就会将设置为cancel事件,然后调用child.dispatchTouchEvent(),相当于取消子View的事件,.并且把mFirstTouchTarget设置为null.因为move事件是多次的,第二次进入的时候,直接进入5,然后跟ACTION_DOWN拦截的时候处理一样。(简单的理解就是,包一个工程,本来交给别人做了,但是发现项目方有追加钱。油水大大的,这个时候,不想给别人做,就取消跟别人的合作,然后交由自己做)
总结
1、dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
2、dispatchTouchEvent 和 onTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。
3、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
ViewGroup 和View的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径),中间不做任何改动,不回溯、不终止,每个环节都走到。
4、ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
网友评论