Touch事件分发源码解析
Posted wlrhnh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }
以上是关于Touch事件分发源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Android 事件分发事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )(代码片段