Android事件分发机制全面解析
事件分发机制
事件分发机制的两个阶段:
- 分发:事件从父视图往子视图分发,被拦截后不再传递,进入回溯阶段
- 回溯:事件从子视图往父视图回溯,被消费后不再回溯
关键方法:
- ViewGroup.dispatchTouchEvent 往子视图分发事件
- ViewGroup.onInterceptTouchEvent 返回 true 表示拦截分发事件,不再传递,进入当前视图 onTouchEvent
- View.dispatchTouchEvent 默认事件分发,调用 onTouchEvent
- View.onTouchEvent 通常重载此方法处理事件,返回 true 表示消费事件,不再传递,返回 false 往上回溯
- ViewParent.requestDisallowInterceptTouchEvent(true) 可以确保事件分发到子视图前不被拦截
假设视图层次为 A.B.C.D,事件分发回溯默认过程为:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent B.onTouchEvent A.onTouchEvent
假设 B 拦截了事件:
A.dispatchTouchEvent B.dispatchTouchEvent -> B.onInterceptTouchEvent B.onTouchEvent A.onTouchEvent
假设 C.onTouchEvent 消费了事件:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent
事件分发机制伪代码:
class Activity { fun dispatchTouchEvent(ev) { if (parent.dispatchTouchEvent(ev)) { return true } return onTouchEvent(ev) } fun onTouchEvent(ev):Boolean {...} } class ViewGroup : View { fun dispatchTouchEvent(ev) { var handled = false if (!onInterceptTouchEvent(ev)) { handled = child.dispatchTouchEvent(ev) } return handled || super.dispatchTouchEvent(ev) } fun onInterceptTouchEvent(ev):Boolean {...} fun onTouchEvent(ev):Boolean {...} } class View { fun dispatchTouchEvent(ev) { var result = false if (handleScrollBarDragging(ev)) { result = true } if (!result && mOnTouchListener.onTouch(ev)) { result = true } if (!result && onTouchEvent(ev)) { result = true } return result } fun onTouchEvent(ev):Boolean {...} }
ViewGroup.dispatchTouchEvent 源码分析
1.开始:ACTION_DOWN 事件开始一个新的事件序列,清除之前触摸状态
2.拦截:
2.1. 非 ACTION_DOWN 事件如果当前没有子视图消费事件,表示事件序列已被拦截
2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
3.分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标
4.分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
5.回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
注:触摸目标(ViewGourp.TouchTarget) 描述一个被触摸的子视图和它捕获的指针ids
public boolean dispatchTouchEvent(MotionEvent ev) { // 省略代码 ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { if (actionMasked == MotionEvent.ACTION_DOWN) { // 1. `ACTION_DOWN` 事件开始一个新的事件序列,清除之前触摸状态 ... } // 省略代码 ... final boolean intercepted; // 2. 拦截 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件 intercepted = onInterceptTouchEvent(ev); // 省略代码 ... } else { intercepted = false; } } else { // 2.1. 非 `ACTION_DOWN` 事件如果当前没有子视图消费事件,表示事件序列已被拦截 intercepted = true; } // 省略代码 ... if (!canceled && !intercepted) { // 省略代码 ... // 3. 分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标 for (int i = childrenCount - 1; i >= 0; i--) { // 省略代码 ... newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它 // 省略代码 ... break; } if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 省略代码 ... // 3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // 省略代码 ... } if (newTouchTarget == null && mFirstTouchTarget != null) { // 3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标 newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } // 省略代码 ... } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // 5. 回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 省略代码 ... // 4. 分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截) TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; // 省略代码 ... if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } // 省略代码 ... target = next; } } // 省略代码 ... } // 省略代码 ... return handled; }
View.dispatchTouchEvent 和 View.onTouchEvent 源码分析
- 滚动条消费鼠标事件
- OnTouchListener 消费触摸事件
- onTouchEvent 消费触摸事件
TouchDelegate 消费触摸事件
public boolean dispatchTouchEvent(MotionEvent event) { // 省略代码 ... boolean result = false; // 省略代码 ... if (onFilterTouchEventForSecurity(event)) { // 滚动条消费鼠标事件 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } // OnTouchListener 消费触摸事件 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // View默认的事件处理逻辑,事件可能在其中被设置的 TouchDelegate 消费 if (!result && onTouchEvent(event)) { result = true; } } // 省略代码 ... return result; } public boolean onTouchEvent(MotionEvent event) { // 省略代码 ... if (mTouchDelegate != null) { // TouchDelegate 消费触摸事件 if (mTouchDelegate.onTouchEvent(event)) { return true; } } // 省略代码 ... return false; }