Android的touch事件分发机制,涉及到每一层级的处理和传递,比较复杂,本文是在参考以下日志和Android源码的基础上总结的,在此表示感谢:
http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/sinyu890807/article/details/9153747
1.touch事件传递过程
touch事件经过Android内核层的处理,最终会传递到activity的dispatchTouchEvent方法,由此开始一层层往下传递。即touch事件是从顶层开始一级级往下传的,从Activity传到触摸到的viewgroup,再从viewgroup一层层往下传,直到最底层的view,传递到每一层的都是dispatchTouchEvent方法。
更为关键的是,touch操作分为action_down以及后续的action_move、action_cancel或action_up。这一系列事件被作为一个整体,如果某一层的dispatchTouchEvent返回了false,则后续的move等事件则不会再传递到该层(具体见下文的源码分析)。
2.view默认dispatchTouchEvent方法
从1的分析可知,如果上层viewgroup均不消费touch事件,最终会调用子view的dispatchTouchEvent方法,view类中该方法源码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
从第九行代码可以看出,当该view存在onTouchListener时,dispatchTouchEvent会先触发该listener,并且当onTouchListener中的ontouch方法返回true时,dispatchTouchEvent直接返回true,表示该view消费了该事件。如果不存在onTouchListener或者ontouch方法返回false,会继续调用view中的onTouchEvent方法,如果该方法返回true,dispatchTouchEvent返回true,否则返回false。
由此可以看出,在view中,onTouchListener的优先级高于onTouchEvent,如果onTouchListener消费了该事件,则不会去调用onTouchEvent。
接下来我们看一下view默认的onTouchEvent实现:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));//view为disabled,但clickable时,仍返回true
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
。。。
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //触发view的onclick事件
}
}
}
。。。
}
break;
case MotionEvent.ACTION_DOWN:
。。。
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
。。。
break;
}
return true; //如果该view是clickable,必然会返回true
}
return false;
}
代码比较长,为了便于理解,我已经略去了一些。重点是该方法的返回值,从代码可以看出,只要该view是clickable,则该方法必然返回true,从而使dispatchTouchEvent方法返回true(前提是能走到该方法)。因此,如果是一个button控件,由于其默认是clickable的,所以即使未添加onclick事件,仍然会消费touch事件,而imageview由于默认非clickable,所以如果不做处理,touch事件在ontouch_down之后,由于
dispatchTouchEvent返回false,后续的move、up事件则不会传到该view。
3.ViewGroup的dispatchTouchEvent方法
ViewGroup中dispatchTouchEvent要相对复杂一些,其主要流程如下:
判断是否允许拦截touch事件,如果允许,则调用onInterceptTouchEvent方法,如果onInterceptTouchEvent返回true,代表拦截该事件,dispatchTouchEvent直接返回,否则继续往下走;
寻找包含touch的坐标点的子view(可能为view也可能为viewgroup),然后将touch事件传到该view的dispatchTouchEvent方法。如果某一个子view的dispatchTouchEvent返回true,则将该view标记为targetview,后续的move、up等事件会直接传到该view,如果未找到这样的子view,则调用该ViewGroup的super.dispatchTouchEvent方法,ViewGroup的父类即View,View的dispatchTouchEvent方法我们已经在上面分析过了,由此可见,如果子view未能消费该事件,则会去调用ViewGroup的ontouch或onTouchEvent方法。
这里有一个需要注意的地方:onInterceptTouchEvent的调用时机。源码如下:
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 no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
前两行是说,如果该事件是
ACTION_DOWN
事件,或者已经找到了上面说的targetview。第三行是在判断是否允许拦截,该flag通过requestDisallowInterceptTouchEvent设置。通过前两行我们可以看出,在默认允许拦截的情况下,ACTION_DOWN事件是一定会走onInterceptTouchEvent的,并且如果未能找到targetview,在后续的move、up中,是不会走onInterceptTouchEvent。通过实验,我发现当ACTION_DOWN找到targetview后,如果在ACTION_MOVE中将该事件拦截,则后续的up事件不会去调onInterceptTouchEvent,猜测可能在viewgroup拦截事件后将targetview置空。