最后,我们来看一下View的事件分发机制。
当我们对一个View进行点击时(Button也好,ImageView也好),首先会调用View的dispatchTouchEvent方法,方法的代码如下:public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
先看if中的语句,
1. mOnTouchListener != null View被设置了OnTouchListener
2. (mViewFlags & ENABLED_MASK) == ENABLED View的状态是enabled,enabled的状态表明View可以接收事件
3. mOnTouchListener.onTouch(this, event) onTouch方法返回值为true
当以上3个条件都成立时,返回值为true。否则,就执行onTouchEvent方法,返回该方法的返回值。
下面是onTouchEvent方法的具体代码:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 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));
}
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 & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
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();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
在该方法中,首先判断View是否是可点击的,或者可长点击的,如果是,进入switch判断,并在MotionEvent_UP事件中调用了performClick方法,在performClick方法中判断,如果View设置了OnClickListener,就会回调执行了onClick方法。
但要注意一点,如果View是可点击的或者是可长点击的,一旦进入了if的语句段,最终的返回值为true。这是什么意思呢?如果我们在onTouch中的返回值为false,那么只要这个View是可点击的,最终的返回值也为true。
这里就要区分一下,onTouch返回值中的含义了。如果onTouch返回为true,表明View的onTouch方法将此事件消费掉了,不再向下传递,并且将继续监听View的其他事件。由于onTouch方法将事件消费掉了,那么onClick方法和onLongClick方法都无法接受到该事件了。如果onTouch方法返回为false,表明onTouch方法没有消费该事件,事件将会向下继续传递给onClick和onLongClick方法,事件将会被onClick或者onLongClick方法消费,于是返回true,表明View的onClick和onClick方法还将继续接收事件,而由于onTouch方法接收事件在onClick和onLongClick之前,所以它也会再继续接受事件。
总结一下,这个最终的返回值为true或者false并不是由onTouch方法决定的,而是由事件是否被View所消费决定的。如果View是可点击的,那么它总能消费事件,返回值也始终为true。每个ENABLED状态的View都可接收事件,默认均可被触摸,所以要在onTouch方法中返回值来判断是否要消费该事件。此时要注意区分接收事件和消费事件的区别。
上面分析了View的事件分发机制,下面来看一看ViewGroup中的事件分发。
首先,ViewGroup中也有dispatchTouchEvent方法,我们来看一看它的具体实现。
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
注意到,代码中有这么一个判断句:
if (disallowIntercept || !onInterceptTouchEvent(ev))
if中有两个条件,只要满足其一,就会进入下面的代码段。
1. disallowIntercept ViewGroup是否禁用掉事件拦截功能,true表示禁用,false表示不禁用。
2. !onInterceptTouchEvent(ev) ViewGroup是否拦截该事件。该条件对onInterceptTouchEvent方法的返回值进行了取反操作,如果onInterceptTouchEvent方法的返回值为false,即不拦截,那么该条件成立。
总结一下,如果ViewGroup禁用掉了事件拦截功能,或者ViewGroup没有对该事件进行拦截,那么将进入下面的代码段。下面的代码段是在做什么呢?
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
看这个for循环应该就很明白了,它获取了ViewGroup中可以接收该事件的childView,将事件向下传递了。childView接收到了该事件后,该怎么办呢?下面是for循环中的if语句。
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
有if中的条件可见,如果childView将事件消费掉了,那么ViewGroup的dispatchTouchEvent方法直接返回true,ViewGroup不再处理该事件。否则,将继续执行下面的代码段,由ViewGroup来处理该事件。
总结:
1. Android中的事件传递是由ViewGroup开始在dispatchTouchEvent方法中向下传递,先传递到ViewGroup,再由ViewGroup决定是否传递到View。
2. 如果ViewGroup的onInterceptTouchEvent将事件拦截了,或者childView没有将事件消费掉,ViewGroup将处理该事件。
3. 如果childView处理了该事件,那么ViewGroup便不会处理该事件。