看一遍就忘不了,事件分发机制分析——dispatchTouchEvent

看一遍就忘不了,事件分发机制分析——dispatchTouchEvent

Android小彩虹2021-08-20 0:21:39370A+A-

事件分发流程图

(该图片转自Gityuan的文章,重新作画)Android事件分发机制

该图调用逻辑可用如下伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;

    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);
    } else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

有如下一些结论:

  • 如果一个 view 在 ACTION_DOWN 时没有消费事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 都不会交与此view处理

  • 如果一个 view 在 ACTION_DOWN 时消费了事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 就直接交给此 view 处理

  • 如果一个 ViewGroup 在一定条件下,拦截了 ACTION_MOVE 事件,那么此前消费事件的子 view 会收到一个 ACTION_CANCEL 事件,同时 onInteceptTouchEvent 在此事件序列中再不会被调用

  • ...

但是,真的理解了这些结论从何而来了吗?恐怕没有一次深入的源码分析理解,是记不住的!

这次,我们结合View视图与源码来分析一次触屏(从 按下 -> 抬起 )的调用流程,源码分析将会以注释的形式展示。

应用视图层级

这是一张最简单的视图,在MainActivity中设置的布局,最外层的 ConstraintLayout ,还有我们自己添加的 MyViewgroup MyView 。

除此之外,还有还有系统添加的外层ViewGroup,整个视图的包含关系是:

ACTION_DOWN 的调用时序图:

该 ACTION_DOWN 事件会依次经过:

DecorView -> Activity -> PhoneWindow -> DecorView -> LinearLayout -> FrameLayout -> ActionBarOverlayLayout -> ContentFrameLayout -> ConstraintLayout -> MyViewgroup -> MyView

该调用过程是一个递归的过程,可由下图表示:

我们将结合源码分析 ACTION_DOWN ACTION_MOVE 两个事件的调用流程,其中我们重点分析 ViewGroup 的 dispatchTouchEvent 方法,

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 当触屏、按home、back等一些人为的操作,会调用此方法,这里就是触屏按下
        onUserInteraction();
    }
    // 调用 PhoneWindow 的 superDispatchTouchEvent 方法
    // 若 superDispatchTouchEvent 返回true(说明事件被底下的 View 消费了),
    // 直接return true,不会再执行 Activity 的 onTouchEvent
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

PhoneWindow.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    // 直接调用 DecorView 的 superDispatchTouchEvent
    return mDecor.superDispatchTouchEvent(event);
}

DecorView.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    // 调用父类 dispatchTouchEvent ,虽然 DecorView 继承自 FrameLayout
    // 但 FrameLayout 没重写 dispatchTouchEvent,所以看 ViewGroup
    return super.dispatchTouchEvent(event);
}

我们将会贴两遍 ViewGroup 的 dispatchTouchEvent() 方法,只不过注释只与当前事件相关

ACTION_DOWN

在MyView的区域按下,我们省略掉MyViewgroup之前的调用,即从 LinearLayout - ConstraintLayout,都是相同的递归调用,设定我们的递归调用已经到了MyViewgroup

ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    // 过滤事件,如果window没有被遮挡,就继续事件分发
    if (onFilterTouchEventForSecurity(ev)) {
	final int action = ev.getAction();
    
        // 这里 & MotionEvent.ACTION_MASK(取低8位) 是因为支持多点触控,事件就会分的更细:
        // 第一根手指按下产生事件:ACTION_DOWN
        // 第二根手指按下产生事件:ACTION_POINTER_DOWN
        // 此时抬起一根手指产生事件:ACTION_POINTER_UP
        // 再抬起另一根手指产生事件:ACTION_UP
        // 通过 ev.getAction() 得到的值包含了 动作(低8位)、触控点索引(9-16位)等,而不单单是上述的几种行为动作,
        // 你不能 if (ev.getAction() == ACTION_POINTER_DOWN) 这样比对,因为ACTION_POINTER_DOWN只是 ev.getAction() 的一部分
        // 那么现在需要知道当前 MotionEvent 的action是何种类型,就需要从 ev.getAction() 
        // 返回的值里面剥离开来,所以加了 & ACTION_MASK 过滤掉其他信息,只取低8位,此举与 ev.getActionMasked 等效
	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.
            // 这里是 ACTION_DOWN 事件,清除所有的TouchTargets,重置标志位,新的开始
	    cancelAndClearTouchTargets(ev);
	    resetTouchState();
	}

	// Check for interception.
	final boolean intercepted;
        // 这里是 ACTION_DOWN,mFirstTouchTarget 为null,因为一遍递归还没走完
        // 还没有找到消费事件的 TouchTarget,为什么叫firstTouchTarget,因为有多点触控的情况,这里的first
        // 指的是消费第一根手指落下的事件的子view,TouchTarget 是链表结构,链接多个都能消费事件的 子 view
	if (actionMasked == MotionEvent.ACTION_DOWN
		|| mFirstTouchTarget != null) {
	    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
	    if (!disallowIntercept) {
                // 如果该 ViewGroup 的child没有调用requestDisallowInterceptTouchEvent,判断是否要拦截
		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.
            // 若不是 ACTION_DOWN ,并且子 view 没有消费(mFirstTouchTarget == null),就直接拦截,这里是 ACTION_DOWN 事件,不会走到这里
	    intercepted = true;
	}

	// If intercepted, start normal event dispatch. Also if there is already
	// a view that is handling the gesture, do normal event dispatch.
	if (intercepted || mFirstTouchTarget != null) {
	    ev.setTargetAccessibilityFocus(false);
	}

	// Check for cancelation.
        // 检查当前是否是取消事件,当前是 ACTION_DOWN ,canceled为false
	final boolean canceled = resetCancelNextUpFlag(this)
			|| actionMasked == MotionEvent.ACTION_CANCEL;

	// Update list of touch targets for pointer down, if needed.
        // 把事件拆分,因为可能是多点触控,需要将事件拆分给不同的 TouchTargets,这里split为true
	final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
	TouchTarget newTouchTarget = null;
	boolean alreadyDispatchedToNewTouchTarget = false;
	if (!canceled && !intercepted) {

            // 不是取消事件并且没有拦截,才会走到这里
            // 这里 childWithAccessibilityFocus 为 null
	    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
		    ? findChildWithAccessibilityFocus() : null;
            
            // 只有在以下几种情况才去寻找能消费的子view,这里是 ACTION_DOWN ,往if里走
	    if (actionMasked == MotionEvent.ACTION_DOWN
		    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
		    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 获取当前MotionEvent的索引,什么意思呢?
                // 因为支持多点触控,那么就需要知道当前这个 MotionEvent 是由哪个触控点产生的
                // 那么这个触控点信息,肯定需要包含在这个 MotionEvent 中,就在action的9-16位中,
                // action是32位int值,将9-16位取出,再右移8位,得到的就是这个 actionIndex
                // 当前就一根手指,actionIndex = 0,若是第二根手指按下,actionIndex = 1
		final int actionIndex = ev.getActionIndex(); // always 0 for down
                // idBitsToAssign = 1
		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.
                    // 根据子view 的z轴值的大小,先排个序,z轴越大的,优先安排扫描
                    // 若当前子view中,没有设置了z轴的view,那么这个 preorderedList 为null,这里为null
		    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    // 当前 preorderedList 为null,这里 customOrder 为 false,也就是没有自定义扫描顺序,按默认的来
		    final boolean customOrder = preorderedList == null
			    && isChildrenDrawingOrderEnabled();
		    final View[] children = mChildren;
                    
                    // 循环查找,从外往里扫描,可以理解成,一个ViewGroup中
                    // 有多个子view,从布局中后写的view开始扫描,因为后写的view的区域可能会
                    // 覆盖先写的view,例如一个FrameLayout里,写在下面的view就会覆盖
                    // 到上面的view,这样先扫描覆盖在上面的view符合我们的视觉模型
		    for (int i = childrenCount - 1; i >= 0; i--) {
			final int childIndex = getAndVerifyPreorderedIndex(
				childrenCount, i, customOrder);
                        // 结合 preorderedList childIndex 来得出优先扫描的view,这里就是按照正常顺序
			final View child = getAndVerifyPreorderedView(
				preorderedList, children, 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.
                        // 这里 childWithAccessibilityFocus 为 null
			if (childWithAccessibilityFocus != null) {
			    if (childWithAccessibilityFocus != child) {
				continue;
			    }
			    childWithAccessibilityFocus = null;
			    i = childrenCount - 1;
			}
                        // 判断当前child能否接受事件,并且手指按下的点落在child区域内,如果有一样不符合,那就继续循环下一个子view,我们这里是符合条件的
			if (!canViewReceivePointerEvents(child)
				|| !isTransformedTouchPointInView(x, y, child, null)) {
			    ev.setTargetAccessibilityFocus(false);
		            continue;
			}
                        // 如果之前已经有子view消费了事件,那么 mFirstTouchTarget 不为null,
                        // 而当前事件是多点触控产生的(第二根手指按下),那么这里去比对 mFirstTouchTarget 中的child属性
                        // 与 当前的child,是同一个的话,就复用,只是把 pointerIdBits 更新下
                        // 我们这里 newTouchTarget 为null,ACTION_DOWN 的递归还没走完呢, mFirstTouchTarget 肯定为null
			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.
                            // 走到这里面,说明子view已经消费了事件,之前我们已经设定,当前已经递归到了MyViewgroup,
                            // 所以上面的 dispatchTransformedTouchEvent 里面就剩下一次调用了,那就是调用MyView的dispatchTouchEvent,
                            // MyView消费了 ACTION_DOWN 事件,执行到这里已经跳出了最后一次递归,当前 ViewGroup 是 MyViewGroup ,
                            // 也即当前 this 对象 是 MyViewGroup
			    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();
                            // 这里面会给 mFirstTouchTarget 赋值,我们这里的话,就是MyView了,
                            // mFirstTouchTarget 的 child 会指向 MyView
                            // 那么,现在找到最终的消费事件的view了:MyView
			    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 已经被消费标志 置为true,下面要用
			    alreadyDispatchedToNewTouchTarget = true;
                            // 找到消费的子view了,就不再找了,跳出循环
                            // 注意,当前是在遍历 MyViewgroup 的循环中,要时刻注意当前的递归调用栈
			    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;
		}
	    }
	}

	// Dispatch to touch targets.
        // 如果没有找到一个能消费的子view,没有找到的话,这里 mFirstTouchTarget 才为null,我们这里找到了:MyView
	if (mFirstTouchTarget == null) {
	    // No touch targets so treat this as an ordinary view.
            // 那就把自己当成普通的view,而不是 ViewGroup,把事件分给自己,看看自己是否消费
	    handled = dispatchTransformedTouchEvent(ev, canceled, null,
		    TouchTarget.ALL_POINTER_IDS);
	} else {
            
            // 如果已经有了消费的子view,那就分发给这个子view
	    // 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;
                // 上面我们已经看到 alreadyDispatchedToNewTouchTarget 为true
                // 上面的addTouchTarget()方法中 mFirstTouchTarget 
                // 与 newTouchTarget 指向同一个对象,所以相等,直接走if
                
		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;
	    }
	}

	...
    // 最后将结果返回给上一级调用者(父view,我们这里的话父View是ConstraintLayout),之后也是一样,跳出递归,层层返回
    return handled;
}

ACTION_MOVE

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 我们还是设定当前已经递归到了 MyViewgroup,当前ViewGroup 为 MyViewgroup
    ...
    boolean handled = false;
    // 过滤事件,如果window没有被遮挡,就继续事件分发
    if (onFilterTouchEventForSecurity(ev)) {
	final int action = ev.getAction();
    
	final int actionMasked = action & MotionEvent.ACTION_MASK;

	// Handle an initial down.
        // 现在是 ACTION_MOVE 事件,这里不走
	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.
	final boolean intercepted;
        // 这里是 ACTION_MOVE,mFirstTouchTarget 不为null,因为在 ACTION_DOWN 时,
        // MyViewgroup 的 mFirstTouchTarget 已经赋了值,就是 MyView 的包装TouchTarget 对象,所以这里的if还是要走的
	if (actionMasked == MotionEvent.ACTION_DOWN
		|| mFirstTouchTarget != null) {
	    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
	    if (!disallowIntercept) {
                // 如果该 ViewGroup 的child没有调用requestDisallowInterceptTouchEvent,判断是否要拦截
		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;
	}

	// If intercepted, start normal event dispatch. Also if there is already
	// a view that is handling the gesture, do normal event dispatch.
	if (intercepted || mFirstTouchTarget != null) {
	    ev.setTargetAccessibilityFocus(false);
	}

	// Check for cancelation.
        // 检查当前是否是取消事件,当前是 ACTION_MOVE ,canceled为false
	final boolean canceled = resetCancelNextUpFlag(this)
			|| actionMasked == MotionEvent.ACTION_CANCEL;

	// Update list of touch targets for pointer down, if needed.
        // 把事件拆分,因为可能是多点触控,需要将事件拆分给不同的 TouchTargets,这里split为true
	final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
	TouchTarget newTouchTarget = null;
	boolean alreadyDispatchedToNewTouchTarget = false;
	if (!canceled && !intercepted) {

            // 不是取消事件并且没有拦截,才会走到这里
            // 这里 childWithAccessibilityFocus 为 null
	    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
		    ? findChildWithAccessibilityFocus() : null;
            
            // if语句块里的作用是在 ACTION_DOWN 的时候找到消费事件的子view
            // 这里是 ACTION_MOVE 不走if
	    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
                // idBitsToAssign = 1
		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 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);

			// 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.
                        // 这里 childWithAccessibilityFocus 为 null
			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();
			    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,没有找到的话,这里 mFirstTouchTarget 才为null,我们在 ACTION_DOWN 时 已经找到了:MyView
        // 注意:按照我们当前的视图
        // DecorView 的 mFirstTouchTarget 是 LinearLayout
        // LinearLayout 的 mFirstTouchTarget 是 FrameLayout
        // FrameLayout 的 mFirstTouchTarget 是 ActionBarOverlayLayout
        // ActionBarOverlayLayout 的 mFirstTouchTarget 是 ContentFrameLayout
        // ContentFrameLayout 的 mFirstTouchTarget 是 ConstraintLayout
        // ConstraintLayout 的 mFirstTouchTarget 是 MyViewGroup
        // MyViewGroup 的 mFirstTouchTarget 是 MyView
	if (mFirstTouchTarget == null) {
	    // No touch targets so treat this as an ordinary view.
	    handled = dispatchTransformedTouchEvent(ev, canceled, null,
		    TouchTarget.ALL_POINTER_IDS);
	} else {
            
            // 如果已经有了消费的子view,那就分发给这个子view
	    // 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;
                // alreadyDispatchedToNewTouchTarget 为false,newTouchTarget 为null,
                // 因为现在是 ACTION_MOVE 事件分发流程,上面该赋值的地方压根没走
		if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
		    handled = true;
		} else {
        
                    // 走这里
                    // 一般当 ViewGroup 拦截了事件,cancelChild 为true,同时之前消费事件的子view会收到 ACTION_CANCEL 事件,
                    // 这里 cancelChild 为 false,因为我们没有拦截
		    final boolean cancelChild = resetCancelNextUpFlag(target.child)
			    || intercepted;
                    // 这里进行的真正事件分发,第三个参数就是 ACTION_DOWN 时记录的 TouchTargets,我们这里是单点触摸,所以就是 mFirstTouchTarget
                    // 所以就有那句结论:在 ACTION_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;
	    }
	}

	...
    // 最后将结果返回给上一级调用者(父view,我们这里父View是ConstraintLayout)
    return handled;
}

ACTION_UP 类似

分析dispatchTransformedTouchEvent

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();
        // cancel 相关
        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.
        // 多点触控相关,这里 oldPointerIdBits = 1, desiredPointerIdBits = 1,newPointerIdBits = 1,说明事件不需要拆分,因为这里没有多点触控
        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()) {
                // 如果当前 ViewGroup 没有子 view ,则交给自己处理,调用父类 dispatchTouchEvent 方法
                // 也即 View 的 dispatchTouchEvent 方法
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    // 调用子 view 的 dispatchTouchEvent 方法,这里是进入下一级递归的入口,
                    // 若该子 view 是 ViewGroup 则继续递归调用,若该子 view 是 View,则此调用将是最后一级递归
                    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.
        // 若上面未进行return,这里将转化过的 transformedEvent 再继续分发
        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;
}

关于TouchTarget

可参考 ViewGroup事件分发总结-TouchTarget

总结

分发事件其实就是通过 dispatchTouchEvent 方法,onInterceptTouchEvent onTouchEvent 只不过是用来处理事件的,通过递归调用,来确定事件分发对象。

现在结合本例,再来看前文的几个结论的由来:

  • 如果一个 view 在 ACTION_DOWN 时没有消费事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 都不会交与此view处理

比如 MyView 在 ACTION_DOWN 时没有消费事件,那么递归调用返回到 MyViewgroup 时,MyViewgroup 的 mFirstTouchTarget 就为null,在 ACTION_MOVE 到来时,会直接判断 mFirstTouchTarget 为null,走到 if(mFirstTouchTarget == null) 语句中,交给自己处理,这还是在 MyViewgroup 处理了 ACTION_DOWN 的前提下,如果 ACTION_DOWN 时,没有一个 view 消费,那么之后的 ACTION_MOVE ACTION_UP 到 DecorView 就停止分发了,因为没人消费 ACTION_DOWN 致使 每一层 ViewGroup 的 mFirstTouchTarget 都为null,当 ACTION_MOVE 到 DecorView 时,由于 mFirstTouchTarget == null ,还是会交给自己处理,所以这些事件根本就不会到达下面的 view了

  • 如果一个 view 在 ACTION_DOWN 时消费了事件,那么同一事件序列之后的 ACTION_MOVE ACTION_UP 就直接交给此 view 处理

与上面的相反,走 if(mFirstTouchTarget == null)的 else 语句快,并且在分发时,dispatchTransformedTouchEvent 第三个参数直接传的是记录的 mFirstTouchTarget

  • 如果一个 ViewGroup 在一定条件下,拦截了 ACTION_MOVE 事件,那么此前消费事件的子 view 会收到一个 ACTION_CANCEL 事件,同时 onInteceptTouchEvent 在此事件序列中再不会被调用

参考上一篇文章

  • ...

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们