android 事件分发机制
参考资料
原理
- 分发事件 的起始点:
从 Activity 开始,Activity 源码
Activity 有两个方法 dispatchTouchEvent 和 onTouchEvent
Activity—dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow().superDispathTouchEvent 就是用来分发事件到
DecorView 中。如果整个 ViewTree 没有消费事件,会调用
Activity 的 onTouchEvent。
源码中:
protected void onUserLeaveHint() {
}
Activity—onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
- 分发事件 的终点 也是 Activity 从源码可知
应为 分发的方法是 acticity 自己定义的方法
dispatchTouchEvent,所有最后只有他自己来处理该事件。
- 分发事件的过程
主要涉及 View 和 ViewGroup (在 xml 中 设置)
View 只有 onTouchEvent 和 dispatchTouchEvent 两个方法。
ViewGroup 有 onTouchEvent / dispatchTouchEvent 和 onInterceptTouchEvent 三个方法。
- 注意事项:
View 或 ViewGroup 有两个核心的行为:拦截(intercept) 和 消费(consume)。这两者是相互独立的,拦截不一定消费。是否要拦截看 onIntercepTouchEvent。是否要消费看 onTouchEvent。
- 方法理解
dispatchTouchEvent,该方法封装了事件分发的整个过程。是事
件分发的 调度者 和 指挥官 。的核心过程均在该方法中。下面的
onInterceptTouchEvent 和 onTouchEvent
的回调的调用就在该方法体中。是否传递事件到
onInterceptTouchEvent 和 onTouchEvent 由
dispatchTouchEvent 决定。
onInterceptTouchEvent,该方法决定了是否拦截事件。只有
ViewGroup 有该回调。返回 true 表示拦截,返回 false
表示不拦截。自定义 View
的时候,可以重载该方法,通过一些特定的逻辑来决定是否拦截
事件。如果拦截,接下来会调用该 ViewGroup 的 onTouchEvent
来处理事件。
onTouchEvent,该方法处理了事件,并决定是否继续消费后续
事件。该方法调用的前置条件:
该 View 拦截了事件
子 View 都不消费事件
没有子 View
该方法正式处理 MotionEvent。返回 true 表示消费,返回
false 不消费。如果消费,接下来的事件还会传递到该 View 的
dispatchTouchEvent 中;如果不消费,后面的事件不会再传过来。
onTouchListener 的 onTouch 回调,和 onTouchEvent
一样,优先级比 onTouchEvent 高,如果有设置该监听,并且
onTouch 返回 true,就不会再调用 onTouchEvent 了。如果返回
false,事件还是会传递到 onTouchEvent 中。
dispatchTransformedTouchEvent 关键部分
// Perform any necessary transformations and dispatch.
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);
}
也就是 dispatchTransformTouchEvent 完成了分发的最后过程:
a. 传入的 child 不为空,转化坐标为 child 的坐标系,调用
child.dispatchTouchEvent 向 child 分发事件
b. 传入的 child 为空,调用 super.dispatchTouchEvent
分发事件到 onTouchEvent 中
- 特殊情况 子 View –requestDisallowInterceptTouchEvent
比较特殊的情况有,子 View 可以使用 requestDisallowInterceptTouchEvent 影响去父 View
的分发,可以决定父 View 是否要调用 onInterceptTouchEvent
。比如,requestDisallowInterceptTouchEvent(true),父 View
就不用调用 onInterceptTouchEvent
来判断拦截,而就是不拦截。
该方法可以用来解决手势冲突。比如子 View
先消费了事件,但是后面父 View
也满足了手势触发的条件而拦截事件,导致子 View
手势执行一半后无法继续响应。可以使用
requestDisallowInterceptTouchEvent(true),这样后面的事
件,父 View 不会走 onInterceptTouchEvent
回调来判断是否要拦截事件,而是直接把事件继续传下来。