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 }
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 }
- //如果不允许当前View拦截该事件或者没有拦截该事件
- //就让当前View的子类去分发和拦截,递归过程
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- // reset this event's action (just to protect ourselves)
- ev.setAction(MotionEvent.ACTION_DOWN);
- // We know we want to dispatch the event down, find a child
- // who can handle it, start with the front-most child.
- final int scrolledXInt = (int) scrolledXFloat;
- final int scrolledYInt = (int) scrolledYFloat;
- final View[] children = mChildren;
- final int count = mChildrenCount;
- //遍历ViewGroup的子View,在此为遍历DecorView的子View
- 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)) {
- .........
- //交给子View的dispatchTouchEvent进行对此事件的分发拦截。
- //如果返回true说明child处理了此事件,递归调用
- if (child.dispatchTouchEvent(ev)) {
- // Event handled, we have a target now.
- //说明已经找到处理此次事件的子View,用mMotionTarget记录
- mMotionTarget = child;
- //找到目标,此次down事件结束。
- return true;
- }
- // The event didn't get handled, try the next view.
- // Don't reset the event's location, it's not
- // necessary here.
- }
- }
- }
- }
- }
上诉代码的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进行分发拦截逻辑
:
- @Override
- public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- //省略部分代码
- // Pass it up to our parent
- if (mParent != null) {
- //当前View干预其父View对后续事件的分发
- mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
如果当前的ViewGroup(为了方便再次称之为ParentView)允许对此次事件进行拦截或者ParentView没有对此事件拦截成功(ParentView的onInterceptTouchEvent返回false)简而言之就是如果ParentView不拦截这处理该事件,就把该事件分发到ParentView的若干子类中去,循环遍历它的子类,来寻找是否有某个子类对处理该事件。可以参照上面的伪代码和流程图来理解之。
如果找到了这样的View,就对该View用一个变量mMotionTarget进行标识。如果在当前的ParentView的子View中没有找到处理该事件的子View会怎么办呢?在ViewGroup里面的dispatchTouchEvent在上面的for循环之后有如下代码:
- //如果没有找到处理事件的View
- if (target == null) {
- // We don't have a target, this means we're handling the
- // event as a regular view.
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- //ViewGroup调用View的事件分发方法,之所以能进入当前的View是因为当前的View的父View没有拦截成功此事件。
- return super.dispatchTouchEvent(ev);
- }
从上面的代码可以看出:如果在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,来解决滑动问题事件的冲突问题:
- 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)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- // clear the target
- mMotionTarget = null;
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
最后某个View(target)如果开始处理事件,在手指离开屏幕之前的什么move事件啦,up事件啦都会交给这个View(target)来处理,因为在ViewGroup的diapatchTouchEvent代码的最后会执行:
- //把事件交给目标视图来处理
- 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方法)
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(null, "B--分发事件");
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(null, "B--拦截事件");
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(null, "B--处理事件");
- 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源码的解析就可以表明出来)