本质
实现方式为 递归 + 责任链
**
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
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return window.getActivity().dispatchTouchEvent(ev)
}
// 4.执行ViewGroup 的 dispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
// 2.将事件分发给Window
public class Activity {
public boolean dispatchTouchEvent(MotionEvent ev) {
return getWindow().superDispatchTouchEvent(ev);
}
}
// 3.将事件再次分发给DecorView
public class PhoneWindow extends Window {
@Override
public 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