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.         //省略部分代码  
  5.        // Pass it up to our parent  
  6.        if (mParent != null) {  
[java] view plain copy
  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循环之后有如下代码:

 

[java] view plain copy
  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,来解决滑动问题事件的冲突问题:

 

[java] view plain copy
  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.             }  
  11.             // clear the target  
  12.             mMotionTarget = null;  
  13.             // Don't dispatch this event to our own view, because we already  
  14.             // saw it when intercepting; we just want to give the following  
  15.             // event to the normal onTouchEvent().  
  16.             return true;  
  17.         }  

 

 

最后某个View(target)如果开始处理事件,在手指离开屏幕之前的什么move事件啦,up事件啦都会交给这个View(target)来处理,因为在ViewGroup的diapatchTouchEvent代码的最后会执行:

 

[java] view plain copy
  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方法)

 

[java] view plain copy
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.         Log.e(null, "B--分发事件");  
  4.         return super.dispatchTouchEvent(ev);  
  5.     }  
  6.     @Override  
  7.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  8.         Log.e(null, "B--拦截事件");  
  9.         return super.onInterceptTouchEvent(ev);  
  10.     }  
  11.     @Override  
  12.     public boolean onTouchEvent(MotionEvent event) {  
  13.         Log.e(null, "B--处理事件");  
  14.         return super.onTouchEvent(event);  
  15.     }  

 

 

 

注意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源码的解析就可以表明出来)

 

posted @ 2018-06-04 16:12  linghu_java  阅读(400)  评论(0编辑  收藏  举报