接下来是onTouchEvent方法返回值的问题。
源码就不贴了,比较长,而且网上有不少分析得比较好的文章,这里就写结论吧:
我们在上文中说过,onTouchEvent方法的主要作用就是将down、move和up组合成onClick
和 onLongClick 组合事件。所以,一个clickable或者longclickable的View在onTouchEvent方法中是一定会返回true的,而一般的View既不是clickable也不是longclickable的(Button是clickable的),我们可以通过setClickable()或setLongClickable()来设置View为clickable或longClickable,或者如果我们为view设置了OnLongClickListener()或OnClickListener(),该view在onTouchEvent方法中也是会返回true的。
所以,综上所述,下列两种情况下,表示一个view对象响应了一次事件
:
一、设置了OnTouchListener,onTouch方法返回true
二、没有设置OnTouchListener或者设置了OnTouchListener但是onTouch方法返回false,并且onTouchEvent方法返回true(当View为clickable或longClickable)
在处理完view的事件响应问题之后,我们来分析android的事件分发机制
当我们点击了屏幕,就会触发Activity的dispatchTouchEvent方法,见源码:
<span style="font-family:Microsoft YaHei;font-size:14px;background-color: rgb(255, 255, 255);">public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// public void onUserInteraction(){}是一个空方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}</span>
getWindow().superDispatchTouchEvent()方法最终调用的是Window的子类PhoneWindow的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(KeyEvent event) {
return mDecor.superDispatchTouchEvent(event);
// 执行的是DecorView类的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
// 执行的是DecorView的父类FrameLayout的dispatchTouchEvent方法
return super.dispatchTouchEvent(event);
}
}
我们看到,DecorView的SuperDispatchTouchEvent方法执行的是其父类FrameLayout中的dispatchTouchEvent()方法,
而FrameLayout中并没有dispatchTouchEvent()方法,所以我们直接看ViewGroup的dispatchTouchEvent()方法
(源码经过简化,只保留大致逻辑):
接收到一个ACTION_DOWN事件时,第3行的条件判断成立,于是:
第4到第8行,将mMotionTarget 设为null
第11行,判断是否为 不允许拦截或者允许拦截但不拦截,如果成立,则:
遍历子view,如果子view满足 VISIBLE 或者 正在执行动画 两个条件中的一个,进一步判断子view是否包含触摸点坐标,如果包含,则在第22行调用子view的dispatchTouchEvent()方法。
如果遍历完都没有哪个子view的dispatchTouchEvent()方法返回true,则代表ACTION_DOWN事件没有得到任何子view的响应,在这种情况下,就不会再接收到ACTION_MOVE和ACTION_UP事件了,mMotionTarget也就为null,于是target为null,执行39行DecorView的父类view的dispatchTouchEvent()方法,上文已经分析过,如果一个view没有设置OnTouchListener或设置了OnTouchListener但是onTouch方法返回false,并且这个view既不是clickable也不是longclickable的话,执行到39行就会返回false,于是Activity的onTouchEvent方法就会执行。
如果遍历过程中有子view的dispatchTouchEvent()方法返回true,则将该子view赋值给mMotionTarget,代表找到了一个响应ACTION_DOWN事件的子view对象,然后直接返回true,表示此次事件被响应了。
于是,Activity的dispatchTouchEvent方法也就返回true,而Activity的onTouchEvent方法就不会得到执行。
紧接着,在接收到ACTION_MOVE和ACTION_UP事件时,在第35行将mMotionTarget赋值给target后直接进入到第46行的判断:
如果允许拦截并且拦截了ACTION_MOVE和ACTION_UP事件,则将ACTION_CANCEL事件分发给target,也就是之前响应ACTION_DOWN事件的子view对象,然后直接返回true,表示已经响应了该次事件。
如果ACTION_MOVE和ACTION_UP事件没有被拦截,则直接派发给target,在target的dispatchTouchEvent()方法中进行处理,并根据其返回值来决定Activity的onTouchEvent方法要不要执行。
ViewGroup的dispatchTouchEvent()方法是android事件分发机制中一个很重要的方法,我们分成两条线路来进行分析:
第一条、是我们上边已经分析过的——从Activity的dispatchTouchEvent方法到DecorView的dispatchTouchEvent()方法
第二条、我们着重分析ViewGroup的dispatchTouchEvent()方法中遍历的过程
就像文章开头的图片所展示的一样,一般来说,android中的布局都为 ViewGroup嵌套ViewGroup和ViewGroup嵌套View两种形式,而根据上边的分析我们知道,在研究android事件传递机制时,更重要的是在一个嵌套的布局中是否有clickable或是longclickable的视图存在。
就用文章开头的图片,A、B、C都为ViewGroup对象,假设D为View对象,
下边来分析D在响应和未响应事件两种情况下A、B、C、D之间的事件传递:
当我们点击了有手势标注的地方,B 的dispatchTouchEvent()方法得到执行(从DecorView到A就不分析了,道理一样的)
上图中,最左边部分的序号和代码与上文中ViewGroup的dispatchTouchEvent()方法的源码所对应,红色、蓝色和紫色箭头分别代表DOWN、MOVE和UP事件的处理流程,关于方法的执行顺序,上图标注得比较清楚了,首先DOWN事件派发到B,如果不拦截,则走C的dispatchTouchEvent()方法,如果C也不拦截,则走D的dispatchTouchEvent()方法(我们假设D为一个View对象,如果D是ViewGroup的话,在没有子view的情况下,会走到第39行,执行其父类View的dispatchTouchEvent()方法),
如果D的dispatchTouchEvent()方法返回true,则C的dispatchTouchEvent()方法也返回true,进而B的dispatchTouchEvent()方法也返回true
... ...
如果D的dispatchTouchEvent()方法返回false,那么C的dispatchTouchEvent()方法不会执行第27行,而是走第39行,执行其父类View的dispatchTouchEvent()方法,
如果返回true,则C的dispatchTouchEvent()方法返回true,B的dispatchTouchEvent()方法也就返回true
... ...
如果返回false,则C的dispatchTouchEvent()方法返回false,B的dispatchTouchEvent()方法不会执行第27行,而是走第39行,执行其父类View的dispatchTouchEvent()方法
... ... 以此类推
在上述过程中,如果D的dispatchTouchEvent()方法返回false,表示DOWN事件没有被响应,则不会再接收到后续的MOVE和UP事件。
如果D的dispatchTouchEvent()方法返回true,表示DOWN事件被D响应了,则在C的dispatchTouchEvent()方法的第26行将D赋值给C的变量mMotionTarget,在B的dispatchTouchEvent()方法的第26行将C赋值给B的变量mMotionTarget
... ... ,当MOVE和UP事件派发到B时,如果在第46行不被拦截的话,则在第61行将MOVE和UP事件派发给target — C处理,当然,如果C不拦截的话,又会在第61行将MOVE和UP事件派发给target
— D处理 ... ...
从上文的分析可以看出,android事件分发机制的大致逻辑是:
当屏幕上接收到触屏事件后,不着急处理,
先从应用程序的根view开始遍历,将触屏事件分发给所有包含触屏点坐标的子view(代码的11到32行,当然这个过程中上层的view可以进行拦截,相关分析本文略过了),优先让最里层的子view来处理,如果最里层的子view不处理,再向外层抛,在这个过程中如果有哪一层做出了响应,则代表这次事件被消费了。
<span style="font-family:Microsoft YaHei;font-size:14px;background-color: rgb(255, 255, 255);">public boolean dispatchTouchEvent(MotionEvent ev) {
// 这里是ACTION_DOWN的处理逻辑
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// 每次ACTION_DOWN时,都将mMotionTarget 设为null
// mMotionTarget 是一个比较重要的变量,它不为null则表示找到了响应事件的view对象
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't intercept
// 如果不允许拦截或者允许拦截但不拦截,则执行下边的逻辑
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// We know we want to dispatch the event down, find a child who can handle it, start with the front-most child.
// 将down事件分发下去,遍历,找到一个可以处理事件的子view
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
// 子view必须要是VISIBLE的或者正在执行动画才可以响应事件
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 如果子view包含触摸点的坐标
if (frame.contains(scrolledXInt, scrolledYInt)) {
if (child.dispatchTouchEvent(ev)) {
// 调用子view的dispatchTouchEvent方法,如果返回true,则将child赋值给mMotionTarget
// 代表找到了一个响应事件的view对象,然后直接返回true
mMotionTarget = child;
return true;
}
// 如果执行到这里,说明ACTION_DOWN事件还没有被响应
}
}
}
}
}
final View target = mMotionTarget;
if (target == null) {
// target == null,意味着没有找到能响应事件的子view,则调用ViewGroup父类View的dispatchTouchEvent方法
return super.dispatchTouchEvent(ev);
}
// 无论 target 是否为 null ,ACTION_DOWN事件的处理都不能走到这里,在之下都是ACTION_MOVE和ACTION_UP的逻辑
// 如果执行到这里,说明有响应ACTION_DOWN事件的view对象,这就看我们是否被允许拦截和要不要拦截了
// 如果允许拦截并且拦截了ACTION_MOVE和ACTION_UP事件,则将ACTION_CANCEL事件分发给target
// 然后直接返回true,表示已经响应了该次事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_CANCEL);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do but they should have.
}
return true;
}
// 如果没有拦截ACTION_MOVE和ACTION_UP事件,则直接派发给target
return target.dispatchTouchEvent(ev);
}</span>