我们知道,事件传递机制是从Activity开始的,也是终止于Activity的,我们看一下,发现这里面和View相似,只有两个方法,没有ViewGroup中的 onInterceptTouchEvent 拦截方法,我们看一下代码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
代码看着很简单, onUserInteraction() 是个空方法,让子类重写。Activity 的意思很明显,dispatchTouchEvent 方法分发事件,如果 getWindow().superDispatchTouchEvent(ev) 消费了,则返回 true,消费了,否则就传递给自己的 onTouchEvent 方法。这里有个疑问,如果down的时候 dispatchTouchEvent 方法中接收到值是false,还会有后续的move和up事件吗?
答案是肯定的,我们可以写个代码打印日志试一试
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean is = super.dispatchTouchEvent(ev);
Log.e("TouTestView", "activity dispatchTouchEvent: " + ev.getAction() +" " + is);
return is;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean is = super.onTouchEvent(event);
Log.e("TouTestView", "activity onTouchEvent: " + event.getAction()+" " + is);
return is;
}
activity 的xml布局里面是个空的FrameLayout,点击屏幕
E/TouTestView: activity onTouchEvent: 0 false
E/TouTestView: activity dispatchTouchEvent: 0 false
E/TouTestView: activity onTouchEvent: 1 false
E/TouTestView: activity dispatchTouchEvent: 1 false
手指在屏幕上滑动
E/TouTestView: activity onTouchEvent: 0 false
E/TouTestView: activity dispatchTouchEvent: 0 false
E/TouTestView: activity onTouchEvent: 2 false
E/TouTestView: activity dispatchTouchEvent: 2 false
E/TouTestView: activity onTouchEvent: 2 false
E/TouTestView: activity dispatchTouchEvent: 2 false
E/TouTestView: activity onTouchEvent: 1 false
E/TouTestView: activity dispatchTouchEvent: 1 false
说明down的时候即使是false,activity也是分发完整的事件传递,这里注意,打印log日志是先打印了 activity onTouchEvent 后打印了 activity dispatchTouchEvent,这是因为我们 dispatchTouchEvent(MotionEvent ev) 方法中,我把log打印放在了 boolean is = super.dispatchTouchEvent(ev); 后面,为了拿到 super 的值,如果我们把log日志放在 super.dispatchTouchEvent(ev) 代码的上面,就会发现打印日志顺序就颠倒过来了。不论是原理还是日志,都说明了事件分发机制是从activity开始的。
我们开始看源码,从 dispatchTouchEvent 方法入手,第一行就不用看了,我们重点看 getWindow().superDispatchTouchEvent(ev) 这行代码, getWindow() 方法是指 mWindow,mWindow 是 Window类型,在这里是指 PhoneWindow, 我们看一下 PhoneWindow 的 superDispatchTouchEvent(MotionEvent event) 方法,
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor 是个根节点的布局,它是 DecorView 类型, DecorView 是FrameLayout 的子类,我们看一下它的简化源码
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
...
return false;
}
}
我们发现 DecorView 是个内部类,并且是 FrameLayout 的子类,说明它也是个ViewGroup,也就是我们xml布局里的根节点,布局中的顶级ViewGroup类,其他view都是在它的容器里。我们来看一下,superDispatchTouchEvent(event) 方法,调用的是 super.dispatchTouchEvent(event) ,也就是说调用的是ViewGroup 的 dispatchTouchEvent(event) 方法,关于ViewGroup 的事件分发我们上一章讲了,View 的事件分发上上章也讲了,这里继续关注,ViewGroup 的 dispatchTouchEvent 方法会对事件做分发,我们就按照上一章思路,比如说
第一种情况:Down 的时候,DecorView 没有拦截事件,DecorView中的子View 也都没有消耗事件,那么就把事件传递给自身的 onTouchEvent(MotionEvent event) 方法,它里面的代码是调用onInterceptTouchEvent(event) 方法,既然没有拦截,说明它的值是false,也就是说 DecorView 的 superDispatchTouchEvent 方法返回的值是false,从而 Activity 中的getWindow().superDispatchTouchEvent(ev) 值为false,所以就继续把事件往下传,交给 Activity 的 onTouchEvent(MotionEvent event) 方法,默认它里面返回的是false,不消费。Down 事件到此结束,下面就是 Move 事件了,也是从 Activity 中传入到 DecorView 中,调用了 ViewGroup 的 dispatchTouchEvent(event) 方法。上一章分析了,如果viewGroup中子View没有消费 Down 事件,那么接下来,move 和 up 都不会传递给子View了,而是直接调用基类的 dispatchTouchEvent(event) 方法 ,传递给自己的 onTouchEvent(MotionEvent event),最终这个值还是返回给 activity 中的 getWindow().superDispatchTouchEvent(ev),根据它的值,事件传递给 activity 的 onTouchEvent(MotionEvent event)。
第二种情况:Down 的时候,DecorView 没有拦截事件, DecorView中的子View 消费了事件,这个子View 可能是 ViewGroup,也可能是 View,如果是ViewGroup的话,就是嵌套关系,事件继续下传,这样就与上一章接壤了,在看一下第一种情况,就能明白上一章中说的 非顶级ViewGroup, down 为false时,后续的 move 和 up 事件就不会传递给它了。
第三种情况:Down 的时候,DecorView 拦截事件,没有传递下去,则直接交给自己的 onTouchEvent(MotionEvent event) 方法处理,把值返回给 activity,决定是否调用activity 的 onTouchEvent 方法。 这种情况基本不存在,应为如果是这样的或,我们展示界面上的控件都获取不到焦点和点击事件了,这个目前没见过。
事件传递是层层传递,由 Activity - ViewGroup - View ,如果子View 不消费,则重新一层层往上传。 事件分发机制,读懂了View 的传递机制,就明白了将近一半,这个是基础;读懂了 ViewGrou 的传递机制,就又明白了一半,这个是中间部分,承上启下,比较重要;读懂了 Activity 的机制,联系 DecorView ,能把 Activity - ViewGroup - View给串联起来,基本就全明白了。剩下的就是一些控件的事件分发的细节和个人感悟应用了。