public boolean dispatchTouchEvent(MotionEvent ev) {
/**
- 首先按下的触发的是 ACTION_DOWN 事件
/
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
/* - 拿到当前 Window 调用 superDispatchTouchEvent 方法
/
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
/* - 如果所有的 View 都没有处理,那么最终会执行到 Activity onTouchEvent 方法中。
*/
return onTouchEvent(ev);
}
通过上面的代码我们知道首先执行的是 ACTION_DOWN 按下事件执行 onUserInteraction 空方法,然后调用 getWindow() 的 superDispatchTouchEvent 方法,这里的 getWindow 其实就是它的唯一子类 PhoneWindow 我们看它的具体调用实现,代码如下:
//PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
…
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
…
}
复制代码
在 PhoneWindow 的 superDispatchTouchEvent 函数中又交于了 DecorView 来处理,那么 DecorView 是什么呢?
//DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
…
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
…
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
}
…
}
我们看到 DecorView 它其实就是继承的 FrameLayout ,我们知道在 Activity 中我们可以通过 getWindow().getDecorView().findViewById() 拿到对应在 XML 中的 View 对象 , 那么 DecorView 又是什么时候进行实例化呢?还有 PhoneWindow 又是何时进行实例化的呢?因为这些不是咱们今天讲解的主要内容,感兴趣的可以看我之前对 Activity 启动源码分析该篇中有讲过 它们其实都是在 Activity 启动的时候进行各自的实例化。好了,DecorView 实例化就讲到这里。目前事件传递到了 DecorView 这里,我们看它的内部源码实现,代码如下:
// DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
复制代码
我们看到内部又调用了父类 dispatchTouchEvent 方法, 所以最终是交给 ViewGroup 顶级 View 来处理分发了。
- 顶级 View 对点击事件的分发过程
在上一小节中我们知道了一个事件的传递流程,这里我们就大致在回顾一下。首先点击事件到达顶级 ViewGroup 之后,会调用自身的 dispatchTouchEvent 方法,之后如果自身的拦截方法 onInterceptTouchEvent 返回 true ,则事件不会继续下发给子类,如果自身设置了 mOnTouchListener 监听,则 onTouch 会被调用,否则 onTouchEvent 会被调用,如果 onTouchEvent 中设置了 mOnClickListener 那么 onClick 会调用。如果 ViewGroup 的 onInterceptTouchEvent 返回 false,则事件会传递到所点击的子 View 中,这时子 View 的 dispatchTouchEvent 会被调用。到此为止,事件已经从顶级 View 传递给了下一层 View ,接下来的传递过程和顶级 ViewGroup 一样,如此循环就完成了整个事件的分发。
在该小节的第一点中我们知道,在 DecorView 中的 superDispatchTouchEvent 方法内部调用了父类的 dispatchTouchEvent 方法,我们看它的实现,代码如下:
//ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
…
if (actionMasked == MotionEvent.ACTION_DOWN) {
//这里主要是在新事件开始时处理完上一个事件
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/** 检查事件拦截,表示事件是否拦截*/
final boolean intercepted;
/**
- 1. 判断当前是否是按下
*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//2. 子类可以通过 requestDisallowInterceptTouchEvent 方法来设置父类不要拦截
if (!disallowIntercept) {
//3
intercepted = onInterceptTouchEvent(ev);
//恢复事件防止其改变
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
…
}
从上面代码我们可以看出如果 actionMasked == MotionEvent.ACTION_DOWN 或者 mFirstTouchTarget != null 成立的话会执行注释 2 的判断(mFirstTouchTarget 的意思如果当前事件被子类消费了,就不成立,后面会提高),disallowIntercept 可以在子类中通过调用父类的 requestDisallowInterceptTouchEvent(true) 请求父类不要拦截分发事件,也就是阻止执行注释 3 的拦截子类接收按下的事件,反之执行 onInterceptTouchEvent(ev); 如果返回 true 说明拦截了事件 。
上面介绍了注释 1,2,3 onInterceptTouchEvent 返回 true 的情况,说明拦截了事件,下面我们来讲解 intercepted = false 当前 ViewGroup 不拦截事件的时候,事件会下发给它的子 View 进行处理,下面看子 View 处理的源码,代码如下:
//ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
…
if (!canceled && !intercepted) {