事件分发机制 dispatchTouchEvent ViewGroup 的 dispatchTouchEvent 方法 伪代码 1 2 3 4 5 6 7 8 9 10 11 public boolean dispatchTouchEvent () { boolean consume = false ; if (onInterceptTouchEvent(ev)){ consume = super .dispatchTouchEvent(); }else { consume = child.dispatchTouchEvent(ev); if (!consume) consume = super .dispatchTouchEvent(ev) } return consume; }
解释: 在 dispatchTouchEvent 方法中,onInterceptTouchEvent 方法会判断当前 ViewGroup 是否会拦截,若拦截,则交给自己处理,若不拦截,则交给子 view。若子 view 不能处理,则还是由自己来处理。 上面的只是一个概述,还有很多细节要去深究
源代码 dispatchTouchEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } 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); } else { intercepted = false ; } } else { intercepted = true ; } .... final boolean canceled = resetCancelNextUpFlag(this ) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 ;TouchTarget newTouchTarget = null ; boolean alreadyDispatchedToNewTouchTarget = false ;if (!canceled && !intercepted) { ... if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { ... for (int i = childrenCount - 1 ; i >= 0 ; i--) { ... newTouchTarget = getTouchTarget(child); if (newTouchTarget != null ) { newTouchTarget.pointerIdBits |= idBitsToAssign; break ; } ... if (dispatchTransformedTouchEvent(ev, false , child, idBitsToAssign)) { .... newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true ; break ; } ... } } ... } ... if (mFirstTouchTarget == null ) { handled = dispatchTransformedTouchEvent(ev, canceled, null , TouchTarget.ALL_POINTER_IDS); } else { 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 ; } ... target = next; } } ... } return handled;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null ) { handled = super .dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ... if (child == null ) { handled = super .dispatchTouchEvent(transformedEvent); } else { ... handled = child.dispatchTouchEvent(transformedEvent); } ... }
mFirstTarget mFirstTouchTarget 是一个 TouchTarget 对象,通过注释的说明:”触摸目标的链接列表中的第一个触摸目标”,可以得出:mFirstTouchTarget 是”触摸目标”链表的头部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0 ) { final ArrayList<View> preorderedList = buildTouchDispatchChildList(); for (int i = childrenCount - 1 ; i >= 0 ; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); if (dispatchTransformedTouchEvent(ev, false , child, idBitsToAssign)) { ... newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true ; break ; } } } ... } } if (mFirstTouchTarget == null ) { handled = dispatchTransformedTouchEvent(ev, canceled, null , TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null ; TouchTarget target = mFirstTouchTarget; while (target != null ) { 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; } }
addTouchTarget(..) 方法是 mFirstTouchTarget 赋值的地方。
我们看 dispatchTouchEvent 中 mFirstTouchTarget 的使用。发现它只在 MotionEvent.ACTION_DOWN 事件的时候赋值。
然后所有的事件都会判断 mFirstTouchTarget 是否为 null ,为 null 则有 ViewGroup 处理, 不为 null 则由 mFirstTouchTarget 对应的 子view 处理。 由此也就可以得出结论:
如果 子view 消费了 TOUCH_DOWN 事件,后续的事件,如果 ViewGroup 不拦截,则都交给这个 子View 尝试消费。因为mFirstTouchTarget只有在TOUCH_DOWN事件才会赋值。 如果这个 子view 对后续的某个事件不消费(onTouchEvent返回false) , 那 ViewGroup 也不会尝试消费这个事件, dispatchTouchEvent 直接返回 false
如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null
成立,事件序列的后续事件只会给这个子view或者ViewGroup处理,而不可能给其他的子view
如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null
成立,事件序列的后续事件会经过 ViewGroup 的 onInterceptTouchEvent(ev)
事件判断,如果 ViewGroup 要拦截。则子view会执行 mFirstTouchTarget=next
。单点触摸条件下,next为null,那实际上触发 mFirstTouchTarget = null
,ViewGroup 直接拦截,这个事件序列的后续事件都直接交给 ViewGroup 处理
addTouchTarget mFirstTarget 赋值的地方
1 2 3 4 5 6 private TouchTarget addTouchTarget (@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
需要注意的点 ActionDown
ActionDown 事件是一个事件序列的开始。当 ActionDown 事件被 ViewGroup 拦截,则这个事件剩下的事件将不会通过 onInterceptTouchEvent 方法,而是直接交给 ViewGroup 处理
ActionDown 事件一定会经过 onInterceptTouchEvent 方法, 并且在 dispatchTouchEvent 方法中每次都会触发重置标志位
ActionMove、ActionUp 等其他事件
若 ActionDown 没有被 ViewGroup 拦截, 而是被子 view 消费, 则这个事件序列的其他事件每次都会经过 ViewGroup 的 onInterceptTouchEvent 方法判断是否拦截
若 ActionDown 没有被 ViewGroup 拦截, 也没有被子 View 消费, 则这个事件序列的其他事件不会经过 onInterceptTouchEvent 方法, 而是直接交给 ViewGroup 处理。 ( 因为mFirstTouchTarget==null)
(以上结论是分析源代码, onInterceptTouchEvent 的执行判断条件, 推断得到)
requestDisallowInterceptTouchEvent(true)
子 View 可以调用父 ViewGroup 的 requestDisallowInterceptTouchEvent(true)屏蔽父 ViewGroup 的拦截(会添加 FLAG_DISALLOW_INTERCEPT 标志)。
但是,这个这种操作对 ActionDown 事件无效。因为每次 ActionDown 事件来到 ViewGruop 的 dispatch 时,会 reset 所有的标志位
mFirstTouchTarget
如果 子view 消费了 TOUCH_DOWN 事件,后续的事件,如果 ViewGroup 不拦截,则都交给这个 子View 尝试消费。因为mFirstTouchTarget只有在TOUCH_DOWN事件才会赋值。 如果这个 子view 对后续的某个事件不消费(onTouchEvent返回false) , 那 ViewGroup 也不会尝试消费这个事件, dispatchTouchEvent 直接返回 false
如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null
成立,事件序列的后续事件只会给这个子view或者ViewGroup处理,而不可能给其他的子view
如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null
成立,事件序列的后续事件会经过 ViewGroup 的 onInterceptTouchEvent(ev)
事件判断,如果 ViewGroup 要拦截。则子view会执行 mFirstTouchTarget=next
。单点触摸条件下,next为null,那实际上触发 mFirstTouchTarget = null
,ViewGroup 直接拦截,这个事件序列的后续事件都直接交给 ViewGroup 处理
ActionCancel 事件 上一点的第3条,原本由子view处理事件被ViewGroup拦截,则自动生成 ActionCancel 事件传递给子view
View 的 dispatchTouchEvent 方法 如果有 onTouchListener,优先执行 onTouch 方法,如果 onTouch 方法返回 true,则不执行 onTouchEvent 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean dispatchTouchEvent (MotionEvent event) { ... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this , event)) { result = true ; } if (!result && onTouchEvent(event)) { result = true ; } ... return result; }