1. Activity对点击事件的分发过程
点击事件用 MotionEvent 来表示,当一个点击操作发生时,事件最先传递给当前 Activity ,由 Activity 的 dispatchTouchEvent 来进行事件派发,具体的工作是由 Activity 内部的 Window 来完成的。Window 会将事件传递给 decor view,decor view 一般就是当前界面的底层容器(即 setContentView 所设置的 View 的父容器),通过 Activity.getWindow.getDecorView()可以获得
public boolean dispatchTouchEvent(MotionEvent ev){
if (ev.getAction() == MotionEvent.ACTION_DOWN ) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent (ev)) {
return true;
}
return onTouchEvent (ev);
}
代码分析
事件首先 Activity 所附属的 Window 进行分发,如果返回 true,整个事件循环就结束了,返回 false 意味着事件没人处理,所有的 onTouchEvent 都返回了 false,那么 Activity 的 onTouchEvent 就会被调用。
Window 是如何将事件传递给 ViewGroup 的
//Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent evrnt);
通过源码知道,Window 是一个抽象类,而 Windows 的 superDispatchTouchEvent 方法也是个抽象方法,所以必须先找到 Window 的实现类(PhoneWindow),在 Window 的说明中,有一段话
Window 类可以控制顶级 View 的外观和行为策略,它的唯一实现位于 android.policy.PhoneWindow中,当你要实例化这个 Window 类的时候,你并不知道它的细节,因为这个类会被重构,只有一个工厂方法可以使用,尽管这看起来有点模糊,不过我们可以看一下 android.policy.PhoneWindow 这个类,尽管实例化的时候此类会被重构,仅是重构而已,功能是类似的。
//PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return mecor.superDispatchTouchEvent(event);
}
代码分析
从上面的源码可以看出,PhoneWindow 将事件直接传递给了 DecorView
而这个 DecorView 又是什么呢?
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
//This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public final View getDecorView() {
if (mDecor == null) {
insallDecor();
}
return mDecor;
}
通过以上代码可以知道,((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)这种方式就可以获取 Activity 所设置的 View,这个 mDecor 显然就是 getWindow().getDecorView() 返回的 View ,而我们通过 setContentView 设置的 View 是它的一个子 View 。目前事件传递到了 DecorView 这里,由于 DecorView 继承自 FrameLayout 且是父 View,所以最终事件会传递给 View 。
换句话说,事件肯定会传递到 View ,不然应用如何响应点击事件呢?
不过这不是重点,重点是事件到了 View 以后应该如何传递,这对我们更有用。
从这里开始,事件已经传递到顶级 View 了,记在 Activity 中通过 setContentView 所设置的 View ,另外顶级 View 也叫根 View,顶级 View 一般来说都是 ViewGroup。
顶级 View 对点击事件的分发过程
点击事件达到顶级 View(一般是一个 ViewGroup)以后,会调用 ViewGroup 的 dispatchTouchEvent 方法。
逻辑
如果顶级 ViewGroup 拦截事件即 OnInterceptTouchEvent 返回 true ,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。
也就是说,如果都提供, onTouch 就会屏蔽掉 onTouchEvent 。在 onTouchEvent 中,如果设置了mOnClickListener,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View ,这时子 View 的 dispatchTouchEvent 会被调用。
到此为止,事件以及从顶级 View 传递给了下一层 View ,接下来的传递过程和顶级 View 是一致的,如此循环,完成了整个事件的分发
在 ViewGroup 对点击事件的分发过程,其主要实现在 ViewGroup 的 dispatchTouchEvent 方法中
比如下面一段代码很显然描述的是当前 View 是否拦截点击事件的逻辑
//check for intersection.
final boolean intersection;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if(!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); //restore action in case it was changed
} else {
intercepted = false;
}
} else {
//There are touch targets and this action is not an initial down
//so this view group continues to intercept touches.
intercepted = true;
}