本质
实现方式为 递归 + 责任链
**
UI 层的事件分发机制指从 ViewRootImpl$ViewPostImeInputStage 开始到 DecorView,再到整个视图树的其它节点。
模型
MotionEvent —— 事件序列
每个事件都对视图树进行遍历是耗性能对,因此在 MotionEvent 中使用 Action 来描述该事件的行为。一个事件序列以 ACTION_DOWN 开始,经历若干个 ACTION_MOVE ,最终以 ACTION_UP 结束。
父节点开始不拦截 ACTION_DOWN 事件,然后在 ACTION_MOVE 的时候,进行拦截,此时 子节点的 dispatchTouchEvent 会收到 ACTION_CANCEL 事件。
TouchTarget —— ViewGroup#mFirstTouchTarget
链表结构,用于保存 ViewGroup 下一级事件的分发目标。责任链模式的标准实现方式,使用链表保存处理器。
拦截
父节点拦截
onInterceptTouchEvent 方法
ViewGroup 中的方法,父节点是否要拦截事件序列
子节点不允许拦截
requestDisallowInterceptTouchEvent方法
子节点调用 getParent.requestDisallowInterceptTouchEvent 来阻止父节点的拦截,内部实现是添加 flag 标志位,但如果该事件序列在最开始便被父节点拦截(这意味子节点没机会调用该方法),则子节点没机会拦截了
实现
ViewRootImpl 到普通 ViewGroup/View 的流程
UI 层的事件分从 ViewRootImpl$ViewPostImeInputStage 开始:
上图中的 mView 为 ViewRootImpl 中的属性,它代表着 DecorView 对象,即整个视图树的根。ViewRootImpl 与 DecorView 的关系可以参考 View 绘制:
🎨 View 绘制
上述流程会调用到 DecorView#dispatchTouchEvent 方法中,由于 DecorView 重写了 dispatchTouchEvent 方法,因此该过程与普通的 View 的事件分发操作略有不同(可以认为 DecorView 为自定义 View,因为 DecorView 是 FrameLayout 的子类)。
DecorView 自定义后的流程为,如果 DecorView#mWindow 的 getCallback() 的返回值不为 null 且符合条件,则调用 callBack 的 dispatchTouchEvent 方法,否则调用 super:
而此处的 Window.CallBack 的实现类为 Activity 和 Dialog,后续的逻辑是这样:
public class DecorView extends FrameLayout {// 1.将事件分发给Activity@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {return window.getActivity().dispatchTouchEvent(ev)}// 4.执行ViewGroup 的 dispatchTouchEventpublic boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}}// 2.将事件分发给Windowpublic class Activity {public boolean dispatchTouchEvent(MotionEvent ev) {return getWindow().superDispatchTouchEvent(ev);}}// 3.将事件再次分发给DecorViewpublic class PhoneWindow extends Window {@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}}
上面伪代码摘自 却把青梅嗅:反思|Android 事件分发机制的设计与实现
这样做的目的是允许开发者通过重写 Activity/Dialog.onTouchEvent()方法 在最后的归流程进行一些逻辑处理
ViewGroup 流程
boolean dispatchTouchEvent(MotionEvent ev)
如果是第一次按下,清空 touchTarget 和之前的触摸状态
判断父节点是否拦截(为 intercepted 赋值)
- actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
该分支意味着可能是新的事件序列,或者当前序列已经被子节点处理过
- 子节点允许拦截(`!disallowIntercept`)
intercepted = onInterceptTouchEvent(ev); // 判断父节点是否要拦截
- 子节点不允许拦截
intercepted = false;
- else
走这个分支代表当前不是一个新的事件序列且子节点没能力处理,则 父节点拦截,因为即使发给子节点 也没能力处理
intercepted = true;
- 判断取消(为 canceled 赋值)
- canceled **= resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;**
mPrivateFlags 中是否有 PFLAG_CANCEL_NEXT_UP_EVENT 或是 ACTION_CANCEL
既没取消,也没拦截(
!canceled && !intercepted)- for 循环当前子节点
- 如果子节点 不能接受事件(不可见或正在执行动画)或 不在点击区域内,跳过
- newTouchTarget = getTouchTarget(child) ,根据 child 寻找其对应的 touchTarget
- if (dispatchTransformedTouchEvent)
- 证明该 child 下有能力处理该事件,为 mFirstTouchTarget 赋值
- for 循环当前子节点
分发给 touchTarget(为 handled 赋值)
- mFirstTouchTarget == null
- handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)
- mFirstTouchTarget == null
child 参数传 null,意味着内部调用 super.onDispatchTouchEvent
- else
分发给 mFirstTouchTarget
- **alreadyDispatchedToNewTouchTarget && target == newTouchTarget**
如果已经分发过了则过滤掉 handled = true;
- **else **- cancelChild = resetCancelNextUpFlag(target.child) || intercepted;- if (**dispatchTransformedTouchEvent**(ev, cancelChild, **target**.child, **target**.pointerIdBits))
handled = true;
- return handled
boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)

View 流程
boolean dispatchTouchEvent(MotionEvent ev)

如果设置了 onTouchListener优先执行 onTouchListener.onTouch 方法 如果 onTouch 返回 false,则 result 仍为 false 此时会走底部分支,onTouchEvent 则有机会执行,后续执行 onClickListener
