1. 程式人生 > >Touch事件分發原始碼解析

Touch事件分發原始碼解析

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 以下原始碼基於Gingerbread 2.3.7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1、先看ViewGroup的dispatchOnTouchEvent(MotionEvent e)的原始碼

1.1 主要是獲取一些座標值,留備後用

 1     @Override
 2     public boolean dispatchTouchEvent(MotionEvent ev) {
 3         if (!onFilterTouchEventForSecurity(ev)) {
4 return false; 5 } 6 7 final int action = ev.getAction(); 8 final float xf = ev.getX(); 9 final float yf = ev.getY(); 10 final float scrolledXFloat = xf + mScrollX; 11 final float scrolledYFloat = yf + mScrollY; 12 final Rect frame = mTempRect;
 

1.2 先處理DOWN事件

 1         //TODO 1、判斷是不是子View不允許老子攔截Touch事件
 2         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 3 
 4         if (action == MotionEvent.ACTION_DOWN) {
 5             if (mMotionTarget != null) {
 6                 // this is weird, we got a pen down, but we thought it was
7 // already down! 8 // XXX: We should probably send an ACTION_UP to the current 9 // target. 10 mMotionTarget = null; 11 } 12 // If we're disallowing intercept or if we're allowing and we didn't 13 // intercept 14 //TODO 2、如果子View不允許,或者老子自己不想攔截,則進入這裡 15 if (disallowIntercept || !onInterceptTouchEvent(ev)) { 16 // reset this event's action (just to protect ourselves) 17 ev.setAction(MotionEvent.ACTION_DOWN); 18 // We know we want to dispatch the event down, find a child 19 // who can handle it, start with the front-most child. 20 final int scrolledXInt = (int) scrolledXFloat; 21 final int scrolledYInt = (int) scrolledYFloat; 22 final View[] children = mChildren; 23 final int count = mChildrenCount; 24 //TODO 3、根據使用者落指的座標,找到應該響應該Touch事件的子View或者子ViewGroup 25 for (int i = count - 1; i >= 0; i--) { 26 final View child = children[i]; 27 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 28 || child.getAnimation() != null) { 29 child.getHitRect(frame); 30 //TODO 4、很明顯,根據範圍來判斷的 31 if (frame.contains(scrolledXInt, scrolledYInt)) { 32 // offset the event to the view's coordinate system 33 final float xc = scrolledXFloat - child.mLeft; 34 final float yc = scrolledYFloat - child.mTop; 35 ev.setLocation(xc, yc); 36 child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 37 //TODO 5、找到子View,將事件交給它,如果它願意消費DOWN事件,則記錄下來這個龜兒子,並返回 39 if (child.dispatchTouchEvent(ev)) { 40 // Event handled, we have a target now. 41 mMotionTarget = child; 42 return true; 43 } 44 // The event didn't get handled, try the next view. 45 // Don't reset the event's location, it's not 46 // necessary here. 47 } 48 } 49 } 50 } 51 }

看見上面5條註釋了吧,夠用了

1.3 沒找到子View、子View不願消費(註釋5)、或者不是DOWN事件,如下

 1         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
 2                 (action == MotionEvent.ACTION_CANCEL);
 3 
 4         if (isUpOrCancel) {
 5             // Note, we've already copied the previous state to our local
 6             // variable, so this takes effect on the next event
 7             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
 8         }
 9 
10         // The event wasn't an ACTION_DOWN, dispatch it to our target if
11         // we have one.
12         // TODO 1、如果是沒有子View,不管是DOWN還是其他事件,都交給自己來處理,包括後續的MOVE、UP事件。
13         //      因為這個方法有點類似遞迴呼叫,所以如果自己也不想處理,那麼父類會分發給自己
14         final View target = mMotionTarget;
15         if (target == null) {
16             // We don't have a target, this means we're handling the
17             // event as a regular view.
18             ev.setLocation(xf, yf);
19             if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
20                 ev.setAction(MotionEvent.ACTION_CANCEL);
21                 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
22             }
23             // TODO 2、呼叫View的dispatchTouchEvent,會呼叫到自己的onTouchEvent()方法
24             return super.dispatchTouchEvent(ev);
25         }

這段程式碼就一個意思,不管啥事件,沒有子View,老子親自處理~

1.4 找到子View,繼續往下走

 1         // TODO 1、找到子View,子View讓老子攔截,或者老子自己想攔截,給龜孫一個CANCEL,
 2         //      並告訴父ViewGroup,老子要了,而且將mMotionTarget置為null,這樣後續事件直接走上面的判斷,直接找老子處理
 3         // if have a target, see if we're allowed to and want to intercept its
 4         // events
 5         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
 6             final float xc = scrolledXFloat - (float) target.mLeft;
 7             final float yc = scrolledYFloat - (float) target.mTop;
 8             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
 9             ev.setAction(MotionEvent.ACTION_CANCEL);
10             ev.setLocation(xc, yc);
11             if (!target.dispatchTouchEvent(ev)) {
12                 // target didn't handle ACTION_CANCEL. not much we can do
13                 // but they should have.
14             }
15             // clear the target
16             mMotionTarget = null;
17             // Don't dispatch this event to our own view, because we already
18             // saw it when intercepting; we just want to give the following
19             // event to the normal onTouchEvent().
20             return true;
21         }
22 
23         // TODO 2、如果是UP、CANCEL事件,則清空
24         if (isUpOrCancel) {
25             mMotionTarget = null;
26         }

這段程式碼,龜孫之前說的好好的, 要自己消費事件,突然又不想了。或者老子突然想攔截了,那麼老子自己攔截,自己處理

1.5 沒有么蛾子了,交給龜孫自己處理

 1         // finally offset the event to the target's coordinate system and
 2         // dispatch the event.
 3         final float xc = scrolledXFloat - (float) target.mLeft;
 4         final float yc = scrolledYFloat - (float) target.mTop;
 5         ev.setLocation(xc, yc);
 6 
 7         if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
 8             ev.setAction(MotionEvent.ACTION_CANCEL);
 9             target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
10             mMotionTarget = null;
11         }
12 
13         // TODO 1、不管前面這幾行了,交給龜孫去處理
14         return target.dispatchTouchEvent(ev);

ViewGroup沒有重寫View的onTouchEvent()方法,onInterceptTouchEvent()返回false,預設不攔截

2、 View的相關方法

2.1 dispatchTouchEvent()方法

就一點點,呼叫自己的onTouchEvent()方法,並以onTouchEvent的返回值為返回值

 1     /**
 2      * Pass the touch screen motion event down to the target view, or this
 3      * view if it is the target.
 4      *
 5      * @param event The motion event to be dispatched.
 6      * @return True if the event was handled by the view, false otherwise.
 7      */
 8     public boolean dispatchTouchEvent(MotionEvent event) {
 9         if (!onFilterTouchEventForSecurity(event)) {
10             return false;
11         }
12 
13         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
14                 mOnTouchListener.onTouch(this, event)) {
15             return true;
16         }
17         return onTouchEvent(event);
18     }

2.2 onTouchEvent()方法

這個方法比較複雜,不可點選的時候返回false,說明自己不處理。可點選的時候,根據不同的事件型別進行處理

  1     public boolean onTouchEvent(MotionEvent event) {
  2         final int viewFlags = mViewFlags;
  3 
  4         if ((viewFlags & ENABLED_MASK) == DISABLED) {
  5             // A disabled view that is clickable still consumes the touch
  6             // events, it just doesn't respond to them.
  7             return (((viewFlags & CLICKABLE) == CLICKABLE ||
  8                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  9         }
 10 
 11         if (mTouchDelegate != null) {
 12             if (mTouchDelegate.onTouchEvent(event)) {
 13                 return true;
 14             }
 15         }
 16 
 17         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 18                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 19             switch (event.getAction()) {
 20                 case MotionEvent.ACTION_UP:
 21                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
 22                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
 23                         // take focus if we don't have it already and we should in
 24                         // touch mode.
 25                         boolean focusTaken = false;
 26                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
 27                             focusTaken = requestFocus();
 28                         }
 29 
 30                         if (!mHasPerformedLongPress) {
 31                             // This is a tap, so remove the longpress check
 32                             removeLongPressCallback();
 33 
 34                             // Only perform take click actions if we were in the pressed state
 35                             if (!focusTaken) {
 36                                 // Use a Runnable and post this rather than calling
 37                                 // performClick directly. This lets other visual state
 38                                 // of the view update before click actions start.
 39                                 if (mPerformClick == null) {
 40                                     mPerformClick = new PerformClick();
 41                                 }
 42                                 if (!post(mPerformClick)) {
 43                                     performClick();
 44                                 }
 45                             }
 46                         }
 47 
 48                         if (mUnsetPressedState == null) {
 49                             mUnsetPressedState = new UnsetPressedState();
 50                         }
 51 
 52                         if (prepressed) {
 53                             mPrivateFlags |= PRESSED;
 54                             refreshDrawableState();
 55                             postDelayed(mUnsetPressedState,
 56                                     ViewConfiguration.getPressedStateDuration());
 57                         } else if (!post(mUnsetPressedState)) {
 58                             // If the post failed, unpress right now
 59                             mUnsetPressedState.run();
 60                         }
 61                         removeTapCallback();
 62                     }
 63                     break;
 64 
 65                 case MotionEvent.ACTION_DOWN:
 66                     if (mPendingCheckForTap == null) {
 67                         mPendingCheckForTap = new CheckForTap();
 68                     }
 69                     mPrivateFlags |= PREPRESSED;
 70                     mHasPerformedLongPress = false;
 71                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
 72                     break;
 73 
 74                 case MotionEvent.ACTION_CANCEL:
 75                     mPrivateFlags &= ~PRESSED;
 76                     refreshDrawableState();
 77                     removeTapCallback();
 78                     break;
 79 
 80                 case MotionEvent.ACTION_MOVE:
 81                     final int x = (int) event.getX();
 82                     final int y = (int) event.getY();
 83 
 84                     // Be lenient about moving outside of buttons
 85                     int slop = mTouchSlop;
 86                     if ((x < 0 - slop) || (x >= getWidth() + slop) ||
 87                             (y < 0 - slop) || (y >= getHeight() + slop)) {
 88                         // Outside button
 89                         removeTapCallback();
 90                         if ((mPrivateFlags & PRESSED) != 0) {
 91                             // Remove any future long press/tap checks
 92                             removeLongPressCallback();
 93 
 94                             // Need to switch from pressed to not pressed
 95                             mPrivateFlags &= ~PRESSED;
 96                             refreshDrawableState();
 97                         }
 98                     }
 99                     break;
100             }
101             return true;
102         }
103 
104         return false;
105     }