这是一篇很早之前就应该写完的东西,这是一篇很早之前就应该写完的东西,这是一篇很早之前就应该写完的东西。其实触摸事件说简单也不复杂,说复杂也不简单。如果只是简单的从爹传给儿子,儿子不处理再传给爹,这样确实没什么意思。还是去代码里面看看是怎么回事。

View的触摸事件

dispatchTouchEvent

首先都知道触摸事件是从dispatchTouchEvent方法开始的,那就先去View的这个方法看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
.....

先是判断没有被遮挡,然后就有了ListenerInfo,这个类是用来存放各种Listener,重点看这句话的判断,首先li一般不是空,后面的是通过view的setOnTouchListener设置进来的,后面的是View是不是ENABLED,默认都是true,再后面就是去看前面设置的ENABLEDonTouch方法返回值。也就是说如果View设置了setOnTouchListener并且View的状态是ENABLED并且返回true,那么就直接返回true,否则还得去下面的if (!result && onTouchEvent(event))里面判断,如果view的onTouchEvent返回true,那么就整体返回true,否则就是false.
总结一下就是:如果设置了setOnTouchListener,那么要去先执行setOnTouchListeneronTouch方法,根据这个方法的返回值判断是不是要去执行下面的判断,如果返回true,意味着在onTouch方法中view已经消耗完了事件,就不会再去调用view的onTouchEvent方法。如果没有设置setOnTouchListener,或者onTouch返回false,意味着还没消耗完事件,再需要去onTouchEvent方法里面转一圈,然后再去决定返回值。下面再来说说view的onTouchEvent方法。

onTouchEvent

首先是这个

1
2
3
4
5
6
7
8
9
10
11
12
...
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...

如果是DISABLED并且是CLICKABLE,那直接返回true,如果不是CLICKABLE那就直接返回false,这两个都是可以在XML或者代码里面设置的。那么如果不是是DISABLED并且是CLICKABLE,就进入下面的switch语句,在down和move里面主要就是设置和重置之类的,主要在up的时候,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
case MotionEvent.ACTION_UP:
//是不是按过了
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
//尝试获取焦点
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//不是长按的
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//这里执行点击事件
performClick();
}
}
}
。。。
break;

这里可以看到,我们设置的onClickListener是在up的时候触发的。关于dispatchTouchEvent的返回值以及后面的事情,下面会说到。

ViewGroup的dispatchTouchEvent

这是一个非常长的一个方法,就不贴那么多代码了,找些重要的说一下,首先

1
2
3
4
5
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//重置 重要的是把mFirstTouchTarget置为空
resetTouchState();
}

然后就是去判断是否拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final boolean intercepted;
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;
}

这样说一下,当是down或者mFirstTouchTarget != null的时候才有机会执行onInterceptTouchEvent(ev)方法,而前面的disallowIntercept参数是根据我们在外面调用disallowIntercept方法决定的。意思就是如果是down或者mFirstTouchTarget != null(找到处理事件的view了),并且外部没有请求不去拦截事件,那么就去调用onInterceptTouchEvent,如果外部请求拦截了,那intercepted = false;否则就是true(不是down并且没有找到处理事件的view)。下面就是if (!canceled && !intercepted),不是取消事件并且没拦截,就进入下面的判断if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {这里可以理解为down的判断,然后就是倒序遍历整个的childView,倒序是为了显示在最上面的view最先被调用。下面就是

1
2
3
4
5
6
7
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

如果找到了接收Touch事件的子View,就直接break,刚开始应该是没有的,那就进入下面的if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))这是一个很重要的判断,如果返回true,意味着子view消耗掉了事件,接下来就是给newTouchTarget赋值,给alreadyDispatchedToNewTouchTarget赋值为true,执行break,因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了就跳出该外层for循环。如果返回false,就不会执行上面的,也就没法给mFirstTouchTarget赋值,所以如果子view不去处理down事件,那么其余的事件是不会传给它的。回到前面这个重要的判断,进去是递归调用了dispatchTouchEvent()方法,如果child==null.也就是viewgroup,就去执行super.dispatchTouchEvent(event);也就是view的dispatchTouchEvent方法,当做一个正常的view进行分发。如果child不是Null,那就执行child.dispatchTouchEvent(event);,进入下一级的分发。down结束了,下面就是move事件,在down结束的时候,mFirstTouchTarget可能是Null,没找到处理的view,不为空,找到了处理的view,把剩下的事件分发给这个view就好了。

1
2
3
4
5
6
7
if (mFirstTouchTarget == null) {
//没有处理的view move的时候再来执行一次
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
....分发给对应的view
}

剩下的结论就不写了,再去想想那些大神的总结,是不是更加有道理了。其实最重要的判断就是view的onTouchEvent是不是返回true,也就是有没有去消耗事件,决定了整个事件的走向。