最後,我們來看一下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便不會處理該事件。