天天看點

android事件分發

1). android對事件分發的順序為:Activity--->PhoneWindow--->DecorView--->yourView;

2). android控件對事件處理的優先級:onTouch--->onTouchEvent--->onClick

  Android既然可以對事件進行攔截,肯定有某個方法對事件進行的傳遞或者分發,完成事件分發功能是由Activity的dispatchTouchEvent(MotionEvent ev)l來負責:

1 public boolean dispatchTouchEvent(MotionEvent ev) {
 2         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
 3        //該方法為空方法,有使用者互動的時候的回調,注意隻在ACTION_DOWN中回調
 4             onUserInteraction();
 5         }
 6 
 7       //交由phoneWindow來處理
 8         if (getWindow().superDispatchTouchEvent(ev)) {
 9             return true;
10         }
11 
12      //否則,調用Activity的onTouchEvent方法 
13         return onTouchEvent(ev);
14     }      

上述分發事件的方法dispatchTouchEvent, 先把事件分發給Window, 通過之前寫的部落格知道這個Window其實就是PhoneWindow,那就看看PhoneWindow方法都做了些什麼:

1    @Override
2     public boolean superDispatchTouchEvent(MotionEvent event) {
3         return mDecor.superDispatchTouchEvent(event);
4     }       

很簡單把此次事件直接很光棍的傳給DecorView,這個View是所有視圖的根視圖,Activity界面中你能見到的各個View都是DecorView的子View。到此為止事件已經分發到View上面,View擷取到事件後有兩個選擇:處理和不處理該事件,如果處理該事件那事件就不會繼續向其子View分發下去;否則就繼續分發下去交給子View對該事件做同樣的判斷,其實就是個遞歸的過程。下面是DecorView裡面的調用方法

1 public boolean superDispatchTouchEvent(MotionEvent event) {
2         return super.dispatchTouchEvent(event);
3 }      

上面的代碼通過super.dispatchTouchEvent(ev)調用了DecorView的父類FrameLayout,該類沒有重寫dispatchTouchEvent而是由它的父類ViewGroup實作:在分析ViewGroup分發事件之前還得說兩結論:

1)ViewGroup永遠不會對攔截,因為他的onInterceptTouchEvent(MotionEvent ev)始終傳回的是false!這樣DecorView對到來的事件MotionEvent就隻有分發到子View并由子View進行攔截和處理此事件了.

2)View包括直接繼承于View的子類因為其父類View沒有onInterceptTouchEvent方法,是以沒法對事件進行攔截,如果這種View擷取到了事件,那麼就會執行onTouchEvent方法(當然這也是有條件的,這個前提條件在對下面onTouch方法作用的時候會有說明)。

A) View類對事件的處理:

比如Button的直接父類TextVew的父類為View,那麼當Button擷取事件的時候其執行分發和處理的時候調用dispatchTouchEvent,就先分析一下View的這個方法都做了什麼以供部落格後面的篇幅用:

1 public boolean dispatchTouchEvent(MotionEvent event) {
 2         // If the event should be handled by accessibility focus first.
 3         if (event.isTargetAccessibilityFocus()) {
 4             // We don't have focus or no virtual descendant has it, do not handle the event.
 5             if (!isAccessibilityFocusedViewOrHost()) {
 6                 return false;
 7             }
 8             // We have focus and got the event, then use normal event dispatch.
 9             event.setTargetAccessibilityFocus(false);
10         }
11 
12         boolean result = false;
13 
14         if (mInputEventConsistencyVerifier != null) {
15             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
16         }
17 
18         final int actionMasked = event.getActionMasked();
19         if (actionMasked == MotionEvent.ACTION_DOWN) {
20             // Defensive cleanup for new gesture
21             stopNestedScroll();
22         }
23 
24         if (onFilterTouchEventForSecurity(event)) {
25             if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
26                 result = true;
27             }
28             //noinspection SimplifiableIfStatement
29             ListenerInfo li = mListenerInfo;
30             if (li != null && li.mOnTouchListener != null
31                     && (mViewFlags & ENABLED_MASK) == ENABLED
32                     && li.mOnTouchListener.onTouch(this, event)) {
            //如果這裡傳回true的話,那麼也表明此次事件被處理了
33                 result = true;
34             }
35 
36             if (!result && onTouchEvent(event)) {
37                 result = true;
38             }
39         }
40 
41         if (!result && mInputEventConsistencyVerifier != null) {
42             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
43         }
44 
45         // Clean up after nested scrolls if this is the end of a gesture;
46         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
47         // of the gesture.
48         if (actionMasked == MotionEvent.ACTION_UP ||
49                 actionMasked == MotionEvent.ACTION_CANCEL ||
50                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
51             stopNestedScroll();
52         }
53 
54         return result;
55     }      

通過上面可以知道onTouchListener的onTouch方法優先于onTouchEvent執行,這個onTouch是否能實行取決于你的View有沒有調用setOnTouchListener方法設定OnTouchListener。如果onTouch方法傳回的true,那麼View的dispatchTouchEvent方法就傳回true而結束執行,onTouchEvent方法就不會得到執行;因為onClick方法通過下面的分析也知道是在onTouchEvent方法中執行的,是以此時onClick方法也不會執行了。

1 public boolean onTouchEvent(MotionEvent event) {
  2         final float x = event.getX();
  3         final float y = event.getY();
  4         final int viewFlags = mViewFlags;
  5         final int action = event.getAction();
  6 
  7         final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
  8                 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
  9                 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
 10 
 11         if ((viewFlags & ENABLED_MASK) == DISABLED) {
 12             if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
 13                 setPressed(false);
 14             }
 15             mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
 16             // A disabled view that is clickable still consumes the touch
 17             // events, it just doesn't respond to them.
 18             return clickable;
 19         }
 20         if (mTouchDelegate != null) {
 21             if (mTouchDelegate.onTouchEvent(event)) {
 22                 return true;
 23             }
 24         }
 25 
 26         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
 27             switch (action) {
 28                 case MotionEvent.ACTION_UP:
 29                     mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
 30                     if ((viewFlags & TOOLTIP) == TOOLTIP) {
 31                         handleTooltipUp();
 32                     }
 33                     if (!clickable) {
 34                         removeTapCallback();
 35                         removeLongPressCallback();
 36                         mInContextButtonPress = false;
 37                         mHasPerformedLongPress = false;
 38                         mIgnoreNextUpEvent = false;
 39                         break;
 40                     }
 41                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
 42                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
 43                         // take focus if we don't have it already and we should in
 44                         // touch mode.
 45                         boolean focusTaken = false;
 46                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
 47                             focusTaken = requestFocus();
 48                         }
 49 
 50                         if (prepressed) {
 51                             // The button is being released before we actually
 52                             // showed it as pressed.  Make it show the pressed
 53                             // state now (before scheduling the click) to ensure
 54                             // the user sees it.
 55                             setPressed(true, x, y);
 56                         }
 57 
 58                         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
 59                             // This is a tap, so remove the longpress check
 60                             removeLongPressCallback();
 61 
 62                             // Only perform take click actions if we were in the pressed state
 63                             if (!focusTaken) {
 64                                 // Use a Runnable and post this rather than calling
 65                                 // performClick directly. This lets other visual state
 66                                 // of the view update before click actions start.
 67                                 if (mPerformClick == null) {
 68                                     mPerformClick = new PerformClick();
 69                                 }
 70                                 if (!post(mPerformClick)) {
 71                                     performClick();
 72                                 }
 73                             }
 74                         }
 75 
 76                         if (mUnsetPressedState == null) {
 77                             mUnsetPressedState = new UnsetPressedState();
 78                         }
 79 
 80                         if (prepressed) {
 81                             postDelayed(mUnsetPressedState,
 82                                     ViewConfiguration.getPressedStateDuration());
 83                         } else if (!post(mUnsetPressedState)) {
 84                             // If the post failed, unpress right now
 85                             mUnsetPressedState.run();
 86                         }
 87 
 88                         removeTapCallback();
 89                     }
 90                     mIgnoreNextUpEvent = false;
 91                     break;
 92 
 93                 case MotionEvent.ACTION_DOWN:
 94                     if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
 95                         mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
 96                     }
 97                     mHasPerformedLongPress = false;
 98 
 99                     if (!clickable) {
100                         checkForLongClick(0, x, y);
101                         break;
102                     }
103 
104                     if (performButtonActionOnTouchDown(event)) {
105                         break;
106                     }
107 
108                     // Walk up the hierarchy to determine if we're inside a scrolling container.
109                     boolean isInScrollingContainer = isInScrollingContainer();
110 
111                     // For views inside a scrolling container, delay the pressed feedback for
112                     // a short period in case this is a scroll.
113                     if (isInScrollingContainer) {
114                         mPrivateFlags |= PFLAG_PREPRESSED;
115                         if (mPendingCheckForTap == null) {
116                             mPendingCheckForTap = new CheckForTap();
117                         }
118                         mPendingCheckForTap.x = event.getX();
119                         mPendingCheckForTap.y = event.getY();
120                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
121                     } else {
122                         // Not inside a scrolling container, so show the feedback right away
123                         setPressed(true, x, y);
124                         checkForLongClick(0, x, y);
125                     }
126                     break;
127 
128                 case MotionEvent.ACTION_CANCEL:
129                     if (clickable) {
130                         setPressed(false);
131                     }
132                     removeTapCallback();
133                     removeLongPressCallback();
134                     mInContextButtonPress = false;
135                     mHasPerformedLongPress = false;
136                     mIgnoreNextUpEvent = false;
137                     mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
138                     break;
139 
140                 case MotionEvent.ACTION_MOVE:
141                     if (clickable) {
142                         drawableHotspotChanged(x, y);
143                     }
144 
145                     // Be lenient about moving outside of buttons
146                     if (!pointInView(x, y, mTouchSlop)) {
147                         // Outside button
148                         // Remove any future long press/tap checks
149                         removeTapCallback();
150                         removeLongPressCallback();
151                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
152                             setPressed(false);
153                         }
154                         mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
155                     }
156                     break;
157             }
158 
159             return true;
160         }
161 
162         return false;
163     }      
1 public boolean performClick() {
 2         final boolean result;
 3         final ListenerInfo li = mListenerInfo;
 4         if (li != null && li.mOnClickListener != null) {
 5             playSoundEffect(SoundEffectConstants.CLICK);
 6             li.mOnClickListener.onClick(this);
 7             result = true;
 8         } else {
 9             result = false;
10         }
11 
12         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
13 
14         notifyEnterOrExitForAutoFillIfNeeded(true);
15 
16         return result;
17     }      

以上簡單的說下onTouch和onTouchEvent和onClick的執行順序以及onTouch的傳回值對onTouchEvent和onClick的影響。

 到此為止,關于View對事件的分發和處理算是簡單的分析完畢,也可以得到一個結論:如果View類的的dispatchTouchEvent傳回true的話,就表明有某個View對該起事件負責(進行處理)。

通過View類的onTouchEvent方法我們也很容易得到如下兩個結論,該結論主要有上方代碼的如下語句得來的

  if (((viewFlags & CLICKABLE) == CLICKABLE ||  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))   

這句代碼是說如果這個view是可點選的(clickable=true或者longClickable=true),那麼這樣的View最終都會消耗目前事件而讓View的dispatchTouchEvent傳回true,這樣的View就是下面将要說到的消耗事件的target對象!否則,即如果一個View既不是clickable也不是longClickable的話,那麼這個View不會消耗該事件。

 B)ViewGroup類對事件分發的處理:

下面繼續分析在ViewGroup的dispatchTouchEvent方法,該方法中開始有這麼一段:

1 @Override
  2     public boolean dispatchTouchEvent(MotionEvent ev) {
  3         if (mInputEventConsistencyVerifier != null) {
  4             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  5         }
  6 
  7         // If the event targets the accessibility focused view and this is it, start
  8         // normal event dispatch. Maybe a descendant is what will handle the click.
  9         if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
 10             ev.setTargetAccessibilityFocus(false);
 11         }
 12 
 13         boolean handled = false;
 14         if (onFilterTouchEventForSecurity(ev)) {
 15             final int action = ev.getAction();
 16             final int actionMasked = action & MotionEvent.ACTION_MASK;
 17 
 18             // Handle an initial down.
 19             if (actionMasked == MotionEvent.ACTION_DOWN) {
 20                 // Throw away all previous state when starting a new touch gesture.
 21                 // The framework may have dropped the up or cancel event for the previous gesture
 22                 // due to an app switch, ANR, or some other state change.
 23                 cancelAndClearTouchTargets(ev);
 24                 resetTouchState();
 25             }
 26 
 27             // Check for interception.
 28             final boolean intercepted;
 29             if (actionMasked == MotionEvent.ACTION_DOWN
 30                     || mFirstTouchTarget != null) {
 31                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 32                 if (!disallowIntercept) {
 33                     intercepted = onInterceptTouchEvent(ev);
 34                     ev.setAction(action); // restore action in case it was changed
 35                 } else {
 36                     intercepted = false;
 37                 }
 38             } else {
 39                 // There are no touch targets and this action is not an initial down
 40                 // so this view group continues to intercept touches.
 41                 intercepted = true;
 42             }
 43 
 
               //如果不允許目前View攔截該事件或者沒有攔截該事件,就讓目前View的子類去分發和攔截,遞歸過程      
44             // If intercepted, start normal event dispatch. Also if there is already
 45             // a view that is handling the gesture, do normal event dispatch.
 46             if (intercepted || mFirstTouchTarget != null) {
 47                 ev.setTargetAccessibilityFocus(false);
 48             }
 49 
 50             // Check for cancelation.
 51             final boolean canceled = resetCancelNextUpFlag(this)
 52                     || actionMasked == MotionEvent.ACTION_CANCEL;
 53 
 54             // Update list of touch targets for pointer down, if needed.
 55             final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 56             TouchTarget newTouchTarget = null;
 57             boolean alreadyDispatchedToNewTouchTarget = false;
 58             if (!canceled && !intercepted) {
 59 
 60                 // If the event is targeting accessiiblity focus we give it to the
 61                 // view that has accessibility focus and if it does not handle it
 62                 // we clear the flag and dispatch the event to all children as usual.
 63                 // We are looking up the accessibility focused host to avoid keeping
 64                 // state since these events are very rare.
 65                 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
 66                         ? findChildWithAccessibilityFocus() : null;
 67 
 68                 if (actionMasked == MotionEvent.ACTION_DOWN
 69                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 70                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 71                     final int actionIndex = ev.getActionIndex(); // always 0 for down
 72                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
 73                             : TouchTarget.ALL_POINTER_IDS;
 74 
 75                     // Clean up earlier touch targets for this pointer id in case they
 76                     // have become out of sync.
 77                     removePointersFromTouchTargets(idBitsToAssign);
 78 
 79                     final int childrenCount = mChildrenCount;
 80                     if (newTouchTarget == null && childrenCount != 0) {
 81                         final float x = ev.getX(actionIndex);
 82                         final float y = ev.getY(actionIndex);
 83                         // Find a child that can receive the event.
 84                         // Scan children from front to back.
 85                         final ArrayList<View> preorderedList = buildTouchDispatchChildList();
 86                         final boolean customOrder = preorderedList == null
 87                                 && isChildrenDrawingOrderEnabled();
 88                         final View[] children = mChildren;
 89                         for (int i = childrenCount - 1; i >= 0; i--) {
 90                             final int childIndex = getAndVerifyPreorderedIndex(
 91                                     childrenCount, i, customOrder);
 92                             final View child = getAndVerifyPreorderedView(
 93                                     preorderedList, children, childIndex);
 94 
 95                             // If there is a view that has accessibility focus we want it
 96                             // to get the event first and if not handled we will perform a
 97                             // normal dispatch. We may do a double iteration but this is
 98                             // safer given the timeframe.
 99                             if (childWithAccessibilityFocus != null) {
100                                 if (childWithAccessibilityFocus != child) {
101                                     continue;
102                                 }
103                                 childWithAccessibilityFocus = null;
104                                 i = childrenCount - 1;
105                             }
106 
107                             if (!canViewReceivePointerEvents(child)
108                                     || !isTransformedTouchPointInView(x, y, child, null)) {
109                                 ev.setTargetAccessibilityFocus(false);
110                                 continue;
111                             }
112 
113                             newTouchTarget = getTouchTarget(child);
114                             if (newTouchTarget != null) {
115                                 // Child is already receiving touch within its bounds.
116                                 // Give it the new pointer in addition to the ones it is handling.
117                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
118                                 break;
119                             }
120 
121                             resetCancelNextUpFlag(child);
122                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
123                                 // Child wants to receive touch within its bounds.
124                                 mLastTouchDownTime = ev.getDownTime();
125                                 if (preorderedList != null) {
126                                     // childIndex points into presorted list, find original index
127                                     for (int j = 0; j < childrenCount; j++) {
128                                         if (children[childIndex] == mChildren[j]) {
129                                             mLastTouchDownIndex = j;
130                                             break;
131                                         }
132                                     }
133                                 } else {
134                                     mLastTouchDownIndex = childIndex;
135                                 }
136                                 mLastTouchDownX = ev.getX();
137                                 mLastTouchDownY = ev.getY();
138                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
139                                 alreadyDispatchedToNewTouchTarget = true;
140                                 break;
141                             }
142 
143                             // The accessibility focus didn't handle the event, so clear
144                             // the flag and do a normal dispatch to all children.
145                             ev.setTargetAccessibilityFocus(false);
146                         }
147                         if (preorderedList != null) preorderedList.clear();
148                     }
149 
150                     if (newTouchTarget == null && mFirstTouchTarget != null) {
151                         // Did not find a child to receive the event.
152                         // Assign the pointer to the least recently added target.
153                         newTouchTarget = mFirstTouchTarget;
154                         while (newTouchTarget.next != null) {
155                             newTouchTarget = newTouchTarget.next;
156                         }
157                         newTouchTarget.pointerIdBits |= idBitsToAssign;
158                     }
159                 }
160             }
161 
162             // Dispatch to touch targets.
163             if (mFirstTouchTarget == null) {
164                 // No touch targets so treat this as an ordinary view.
165                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
166                         TouchTarget.ALL_POINTER_IDS);
167             } else {
168                 // Dispatch to touch targets, excluding the new touch target if we already
169                 // dispatched to it.  Cancel touch targets if necessary.
170                 TouchTarget predecessor = null;
171                 TouchTarget target = mFirstTouchTarget;
172                 while (target != null) {
173                     final TouchTarget next = target.next;
174                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
175                         handled = true;
176                     } else {
177                         final boolean cancelChild = resetCancelNextUpFlag(target.child)
178                                 || intercepted;
179                         if (dispatchTransformedTouchEvent(ev, cancelChild,
180                                 target.child, target.pointerIdBits)) {
181                             handled = true;
182                         }
183                         if (cancelChild) {
184                             if (predecessor == null) {
185                                 mFirstTouchTarget = next;
186                             } else {
187                                 predecessor.next = next;
188                             }
189                             target.recycle();
190                             target = next;
191                             continue;
192                         }
193                     }
194                     predecessor = target;
195                     target = next;
196                 }
197             }
198 
199             // Update list of touch targets for pointer up or cancel, if needed.
200             if (canceled
201                     || actionMasked == MotionEvent.ACTION_UP
202                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
203                 resetTouchState();
204             } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
205                 final int actionIndex = ev.getActionIndex();
206                 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
207                 removePointersFromTouchTargets(idBitsToRemove);
208             }
209         }
210 
211         if (!handled && mInputEventConsistencyVerifier != null) {
212             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
213         }
214         return handled;
215     }      
  1.           //如果不允許目前View攔截該事件或者沒有攔截該事件  
  2.           //就讓目前View的子類去分發和攔截,遞歸過程  
  3.           if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  4.               // reset this event's action (just to protect ourselves)  
  5.               ev.setAction(MotionEvent.ACTION_DOWN);  
  6.               // We know we want to dispatch the event down, find a child  
  7.               // who can handle it, start with the front-most child.  
  8.               final int scrolledXInt = (int) scrolledXFloat;  
  9.               final int scrolledYInt = (int) scrolledYFloat;  
  10.               final View[] children = mChildren;  
  11.               final int count = mChildrenCount;  
  12.               //周遊ViewGroup的子View,在此為周遊DecorView的子View  
  13.               for (int i = count - 1; i >= 0; i--) {  
  14.                   final View child = children[i];  
  15.                   if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  16.                           || child.getAnimation() != null) {  
  17.                       child.getHitRect(frame);  
  18.                       if (frame.contains(scrolledXInt, scrolledYInt)) {  
  19.                            .........  
  20.                           //交給子View的dispatchTouchEvent進行對此事件的分發攔截。  
  21.                           //如果傳回true說明child處理了此事件,遞歸調用  
  22.                            if (child.dispatchTouchEvent(ev))  {  
  23.                               // Event handled, we have a target now.  
  24.                               //說明已經找到處理此次事件的子View,用mMotionTarget記錄  
  25.                               mMotionTarget = child;  
  26.                                //找到目标,此次down事件結束。  
  27.                                return true;  
  28.                           }  
  29.                           // The event didn't get handled, try the next view.  
  30.                           // Don't reset the event's location, it's not  
  31.                           // necessary here.  
  32.                       }  
  33.                   }  
  34.               }  
  35.           }  
  36.       }  

上訴代碼的for循環很詳細的說明了View對事件進行分發的過程,當然for循環之是以能得以執行使用兩個前提條件的:

1)事件為ACTION_DOWN事件,在Down事件中才去進行分攔截發事件并尋找消耗事件的target View,!

2)disallowIntercept   ,這個屬性可通過requestDisallowInterceptTouchEvent(boolean disallowIntercept  )來設定,通過觀察期具體實作可以知道該方法的作用就是子View幹預父View的事件分發過程,當然對于ACTION_DOWN事件子類是不能幹預父類的,因為if條件為(disallowIntercept ||!onInterceptTouchEvent(ev))為或操作;或者目前的View沒有攔截成功該事件。如果disallowIntercept 為true,那麼就說明目前ViewGroup的某個子View不允許其父View對目前事件進行攔截,反之就是某個子View允許其父View對目前事件進行攔截,當然這并不等于父View一定能攔截成功目前事件,如果父View此時攔截不成功(即父View的onInterceptTouchEvent傳回false),那麼仍然交給子view進行分發攔截邏輯

:

[java] view plain copy

  1. @Override  
  2.    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {  
  3.         //省略部分代碼  
  4.        // Pass it up to our parent  
  5.        if (mParent != null) {  
  1.     //目前View幹預其父View對後續事件的分發  
  2.     mParent.requestDisallowInterceptTouchEvent(disallowIntercept);  
  3. }  

如果目前的ViewGroup(為了友善再次稱之為ParentView)允許對此次事件進行攔截或者ParentView沒有對此事件攔截成功(ParentView的onInterceptTouchEvent傳回false)簡而言之就是如果ParentView不攔截這處理該事件,就把該事件分發到ParentView的若幹子類中去,循環周遊它的子類,來尋找是否有某個子類對處理該事件。可以參照上面的僞代碼和流程圖來了解之。

如果找到了這樣的View,就對該View用一個變量mMotionTarget進行辨別。如果在目前的ParentView的子View中沒有找到處理該事件的子View會怎麼辦呢?在ViewGroup裡面的dispatchTouchEvent在上面的for循環之後有如下代碼:

  1. //如果沒有找到處理事件的View  
  2. if (target == null) {  
  3.             // We don't have a target, this means we're handling the  
  4.             // event as a regular view.  
  5.             ev.setLocation(xf, yf);  
  6.             if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  7.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
  8.                 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  9.             }  
  10.             //ViewGroup調用View的事件分發方法,之是以能進入目前的View是因為目前的View的父View沒有攔截成功此事件。  
  11.             return super.dispatchTouchEvent(ev);  
  12.         }  

從上面的代碼可以看出:如果在ParentView的子類中沒有找到能處理問題的那個view,就調用parentView的父View的dispatchTouchEvent方法。我們應該知道之是以parentView能分發和攔截事件,前提是它的父類本來沒有攔截事件的能力或如本身攔截事件的方法傳回了false,是以沿着view樹最終會調用的View類的dispatchTouchEvent,那麼又回歸到本篇部落格的A)View類對事件的處理那一部分了。

注意前面講的for循環查找的重大前提是:在down事件中,且我們要明白在手指接觸螢幕到手指離開螢幕會産生一系列事件,一個down(ACTION_DOWN)事件,數個move(ACTION_MOVE)事件,和一個 UP事件。尋找到目标事件之後,之後的一些列事件都交給這個target消耗,比如move事件等。當然我們是可以通過讓target調用requestDisallowInterceptTouchEvent方法來幹預父類關于事件分發過程。或者在在适當的情況下讓target父View的onInterceptEvent傳回true或者false,來解決滑動問題事件的沖突問題:

  1. if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  2.             final float xc = scrolledXFloat - (float) target.mLeft;  
  3.             final float yc = scrolledYFloat - (float) target.mTop;  
  4.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  5.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  6.             ev.setLocation(xc, yc);  
  7.             if (!target.dispatchTouchEvent(ev)) {  
  8.                 // target didn't handle ACTION_CANCEL. not much we can do  
  9.                 // but they should have.  
  10.             // clear the target  
  11.             mMotionTarget = null;  
  12.             // Don't dispatch this event to our own view, because we already  
  13.             // saw it when intercepting; we just want to give the following  
  14.             // event to the normal onTouchEvent().  
  15.             return true;  

最後某個View(target)如果開始處理事件,在手指離開螢幕之前的什麼move事件啦,up事件啦都會交給這個View(target)來處理,因為在ViewGroup的diapatchTouchEvent代碼的最後會執行:

  1. //把事件交給目标視圖來處理  
  2.   return target.dispatchTouchEvent(ev);  

并且本View的onInterceptTouchEvent是不會調用了。也就是說如果有一個view處理該事件,那麼down之後的一系列move事件和up事件都自動交給該view處理,因為該view已經是targetView 了,是以不會對後續事件序列或者事件集合進行攔截操作,隻會調用dispatchTouchEvent和onTunchEvent來處理該事件!而onInterceptTouchEvent事件不會調用。簡單的舉個例子,如圖:

假設上圖中由D進行事件的處理,也就是說D的onInterceptTouchEvent和onTouchEvent均傳回true,D的父View A ,B ,C的這兩個方法都傳回false,那麼在這個布局中D就是上面所說的 target.在首次的Down事件中會執行查找target的操作:(注:下圖中分發事件指的是執行了dispatchTouchEvent,攔截事件指的是onInterceptTouchEvent,消耗事件為onTouchEvent方法)

  1.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.         Log.e(null, "B--分發事件");  
  3.         return super.dispatchTouchEvent(ev);  
  4.     }  
  5.     @Override  
  6.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  7.         Log.e(null, "B--攔截事件");  
  8.         return super.onInterceptTouchEvent(ev);  
  9.     public boolean onTouchEvent(MotionEvent event) {  
  10.         Log.e(null, "B--處理事件");  
  11.         return super.onTouchEvent(event);  

注意D此時的列印,D為此事件的target View,攔截和處理了該次down事件,那麼如果此時手指繼續移動的話将會列印如下的Log:

注意上圖是手指滑動後列印的log,可以發現D隻負責分發和處理該事件,而沒有像down事件那樣進行攔截,是以上面的log列印可以清晰的說明上面的結論:

1)targetView隻會對一個事件序列攔截一次,即隻攔截down事件或者說由down事件負責查找targetView

2)一旦targetView被找到,down事件之後的一些列事件都由target View負責消耗,而target并不對後續事件進行再次攔截

3)當然在down事件之後的後續事件還是會先由父View進行分發攔截,也即是說文章開頭所說的事件序列中把每一個事件單獨來看的話,都會由父View來進行攔截和分發的,隻不過到後續事件到傳到target的時候直接進行處理而少了攔截的過程而已,因為在父類查找target的時候已經攔截過一次,這點很重要,也是解決滑動沖突的關鍵點,比如滑動的時候根據合适的時機來判斷是否讓父View進行事件攔截和處理。隻不過省下了對targetView的尋找,因為在down事件中已經尋找到了target并有mMotionTarget變量進行了辨別,(通過上面的對ViewGroup的dispatchTouchEvent源碼的解析就可以表明出來)

繼續閱讀