1.前言
事件分发这个东西嘛,大家一直都在讲,但总有人觉得吃不透。为什么呢?因为事件分发是多维的,有好多条思维分岔路口,而文章基本上只能用一维的方式从左到右,从上到下进行表达,所以基本不可能让普通智力的人从入门到精通。我们所要做的,就是踏踏实实打开源码,自己多琢磨,多整理。才能彻底理解这些多维的知识点。
下面内容请配合源码食用!不然基本上索然无味!
2.Touch与Click的前生今世
首先,我们先来做点前戏,搞清楚setOnTouchListener
、setOnClickListener
以及onTouchEvent
之间的关系。
2.1 setOnTouchListener
因为这一系列操作都是针对View的,所以我们直接看其源码,精准定位到dispatchTouchEvent()
方法。
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
这段代码非常简单,直接将一切都暴露了出来。
result
变量十分关键,它是用来控制Touch与Click执行流程的。最开始result
为false,如果我们通过setOnTouchListener()
为某个View设置了touch监听,并且在监听的onTouch()
方法中返回true,那么result
变量就会被赋值为true,此时dispatchTouchEvent()
执行完毕,就不会执行接下来View本身的onTouchEvent()
方法。
2.2 onTouchEvent
相反,如果我们没有为View设置touch监听,或者设置了touch监听但是在监听的onTouch()
方法中返回false,那么result
依旧为false,就会执行View本身的onTouchEvent()
方法。我们来看看onTouchEvent()
做了什么,由于只是热身运动,所以只贴出了与其有关的部分代码。
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();
}
}
}
...
}
可以看到,在View本身的onTouchEvent()
方法中,先去判断了该View是否可以被点击,接着判断触摸事件的类型,如果是ACTION_UP
类型,则执行performClick()
方法。
2.3 setOnClickListener
performClick()
这个方法比较短,直接展示出来。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
显而易见,如果通过setOnClickListener
为当前View设置了Click监听,此时就会去执行监听中onClick()
方法。
2.4 小结
到此为止,前戏就算结束了。我们总结下,setOnTouchListener
与setOnClickListener
是程序员可以设置的,而onTouchEvent
是View本身的方法,在onTouchEvent
中会去执行setOnClickListener
中设置的OnClick
方法。而在View的dispatchTouchEvent()
中,首先会去判断是否设置了OnTouchListener
并且其OnTouch
方法返回为true,如果是,则不会执行View本身的onTouchEvent
方法,如果不是,则会执行onTouchEvent
进而执行OnClick
方法。
我个人是这样记住他们的关系的:Touch是触摸,Click是点击,从逻辑上来说,触摸包含了点击。所以如果设置了触摸的监听,那么其必定包含点击,于是点击的监听也就没什么必要了。
3.事件分发
3.1 事件分发的开始
下面进入正题,在使用安卓手机时,我们用手指触摸了屏幕,物理设备就会一层层将触摸事件传递出来,这是底层的活儿,我们暂且不去了解。属于Android开发的故事,从Activity的dispatchTouchEvent()
方法开始。请注意,这是Activity的dispatchTouchEvent()
,不要和View的dispatchTouchEvent()
混淆起来。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
分析一波,首先判断触摸事件,如果是ACTION_DOWN
,则调用onUserInteraction()
,这是一个空方法,专门用来让用户重写的,可以用于在事件发生前做一些操作。
接着getWindow().superDispatchTouchEvent(ev)
就比较重要了。一路跟来的同学肯定知道Activity中的Window就是PhoneWindw,不知道的传送门在这里。我们直接看其superDispatchTouchEvent
方法。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
显而易见,这里调用了DecorView中的superDispatchTouchEvent
方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView是PhoneWindow的内部类,我们去看看他的父类是谁。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
可见,FrameLayout 是DecorView的父类,所以就会调用FrameLayout的dispatchTouchEvent
方法,遗憾的是,FrameLayout并没有这个方法,所以还要去找FrameLayout 的父类ViewGroup。ViewGroup中的dispatchTouchEvent
是本篇最大的高潮,我们下一节专门来讲。在此,我们回到Activity的dispatchTouchEvent
方法中
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
看最后一行代码——年轻的程序员啊,请记住,只要外层ViewGroup的dispatchTouchEvent
返回为true,那么就代表事件被消耗了,此时连Activity中的onTouchEvent