本帖最后由 forlinx2013 于 2014-1-24 08:59 编辑
欢迎大家来到飞凌爱板网专区,对嵌入式技术感兴趣的朋友不妨多多关注一下,我们提供了公司所有开发板的所有资料,也会更新大量技术文章,欢迎大家一块学习提高!!! |
直接进入正题,在android开发中,手势触碰是经常使用到的。这两天刚好在看这方面的资料,在这里写篇文章做个小总结。 首先必须知道五大布局Layout:LineaLayout,RelativeLayout,FrameLayout,AbsoluteLayout都继承自ViewGroup,而TableLayout继承LinearLayout,也相当于继承于ViewGroup。 而ViewGroup继承自View。 而我们最终在屏幕上显示控制的控件如TextView,Button等都直接或间接继承自View。 View中有两个方法参与到Touch事件分发 onDispatchTouchEvent(MotionEvent event)和onTouch(MotionEvent event) ViewGroup有三个方法参与到Touch事件分发 onDispatchTouchEvent(MotionEvent event),onInterceptTouchEvent(MotionEvent ev),onTouch(MotionEvent event)
当我们手指出门屏幕,手势事件最早被传递到Layout布局的dispatchTouchEvent,即ViewGroup的dispatchTouchEvent
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 final int action = ev.getAction(); 3 final float xf = ev.getX(); 4 final float yf = ev.getY(); 5 final float scrolledXFloat = xf + mScrollX; 6 final float scrolledYFloat = yf + mScrollY; 7 final Rect frame = mTempRect; 8 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 9 if (action == MotionEvent.ACTION_DOWN) { 10 if (mMotionTarget != null) { 11 mMotionTarget = null; 12 } 13 if (disallowIntercept || !onInterceptTouchEvent(ev)) { 14 ev.setAction(MotionEvent.ACTION_DOWN); 15 final int scrolledXInt = (int) scrolledXFloat; 16 final int scrolledYInt = (int) scrolledYFloat; 17 final View[] children = mChildren; 18 final int count = mChildrenCount; 19 for (int i = count - 1; i >= 0; i--) { 20 final View child = children; 21 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 22 || child.getAnimation() != null) { 23 child.getHitRect(frame); 24 if (frame.contains(scrolledXInt, scrolledYInt)) { 25 final float xc = scrolledXFloat - child.mLeft; 26 final float yc = scrolledYFloat - child.mTop; 27 ev.setLocation(xc, yc); 28 child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 29 if (child.dispatchTouchEvent(ev)) { 30 mMotionTarget = child; 31 return true; 32 } 33 } 34 } 35 } 36 } 37 } 38 boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || 39 (action == MotionEvent.ACTION_CANCEL); 40 if (isUpOrCancel) { 41 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; 42 } 43 final View target = mMotionTarget; 44 if (target == null) { 45 ev.setLocation(xf, yf); 46 if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 47 ev.setAction(MotionEvent.ACTION_CANCEL); 48 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 49 } 50 return super.dispatchTouchEvent(ev); 51 } 52 if (!disallowIntercept && onInterceptTouchEvent(ev)) { 53 final float xc = scrolledXFloat - (float) target.mLeft; 54 final float yc = scrolledYFloat - (float) target.mTop; 55 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 56 ev.setAction(MotionEvent.ACTION_CANCEL); 57 ev.setLocation(xc, yc); 58 if (!target.dispatchTouchEvent(ev)) { 59 } 60 mMotionTarget = null; 61 return true; 62 } 63 if (isUpOrCancel) { 64 mMotionTarget = null; 65 } 66 final float xc = scrolledXFloat - (float) target.mLeft; 67 final float yc = scrolledYFloat - (float) target.mTop; 68 ev.setLocation(xc, yc); 69 if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 70 ev.setAction(MotionEvent.ACTION_CANCEL); 71 target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 72 mMotionTarget = null; 73 } 74 return target.dispatchTouchEvent(ev); 75 } public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } if (disallowIntercept || !onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) { final View child = children; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); } 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)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } return target.dispatchTouchEvent(ev); }
此段代码较长,挑重点看,第9行,但是ACTION_DOWN事件时,会进入到第13行的判断,第一个参数disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。而第二个参数!onInterceptTouchEvent(ev)即是否进行拦截的判断,如果我们不复写这个方法,默认为false,就会进入if判断,并获得点击区域有的view,在第29行将手势事件传递给被点击到的view,如果view的dispatchTouchEvent(ev)返回true,表示手势事件被这个view消费了(view的dispatchTouchEvent后续分析),并把该view置给mMotionTarget后返回true。这里,正是因为有view消费了ACTION_DOWN事件,使得当后续的ACTION_MOVE ACTION_UP传递到这个ViewGroup的时候,mMotionTarget不为空,使得47行的target不为空,最后都会执行74行的target.dispatchTouchEvent(ev),即ACTION_DOWN事件被哪个view的onTouch或者onTouchEvent返回了true,那么后续的ACTION_MOVE,ACTION_UP事件将会直接传递给这个view执行。(onTouch和onTouchEvent后续分析)
继续分析这个ViewGroup的dispatchTouchEvent,如果13行的onInterceptTouchEvent被我们复写返回true代表ACTION_DOWN被这个ViewGroup拦截下来,将不会进入if判断,因此mMotionTarget将为null使得target也为null,最后执行了50行的super.dispatchTouchEvent即ViewGroup的父类View的dispatchTouchEvent。并在其中调用onTouch或者onTouchEvent。 如果我们不复写onInterceptTouchEvent让其进入if判断,但如果child的dispatchTouchEvent返回的是false,表示child没有把这个事件消费了,最终也还是调用了50行的super.dispatchTouchEvent(ev)。 现在再来看看上面分析中经常说到的View的dispatchTouchEvent 76 public boolean dispatchTouchEvent(MotionEvent event) { 77 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 78 mOnTouchListener.onTouch(this, event)) { 79 return true; 80 } 81 return onTouchEvent(event); 82 } public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!
首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了 83 public void setOnTouchListener(OnTouchListener l) { 84 mOnTouchListener = l; 85 } public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。
if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。 而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。 继续看下view的onTouchEvent 86 public boolean onTouchEvent(MotionEvent event) { 87 final int viewFlags = mViewFlags; 88 if ((viewFlags & ENABLED_MASK) == DISABLED) { 89 // A disabled view that is clickable still consumes the touch 90 // events, it just doesn't respond to them. 91 return (((viewFlags & CLICKABLE) == CLICKABLE || 92 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 93 } 94 if (mTouchDelegate != null) { 95 if (mTouchDelegate.onTouchEvent(event)) { 96 return true; 97 } 98 } 99 if (((viewFlags & CLICKABLE) == CLICKABLE || 100 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 101 switch (event.getAction()) { 102 case MotionEvent.ACTION_UP: 103 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 104 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 105 // take focus if we don't have it already and we should in 106 // touch mode. 107 boolean focusTaken = false; 108 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 109 focusTaken = requestFocus(); 110 } 111 if (!mHasPerformedLongPress) { 112 // This is a tap, so remove the longpress check 113 removeLongPressCallback(); 114 // Only perform take click actions if we were in the pressed state 115 if (!focusTaken) { 116 // Use a Runnable and post this rather than calling 117 // performClick directly. This lets other visual state 118 // of the view update before click actions start. 119 if (mPerformClick == null) { 120 mPerformClick = new PerformClick(); 121 } 122 if (!post(mPerformClick)) { 123 performClick(); 124 } 125 } 126 } 127 if (mUnsetPressedState == null) { 128 mUnsetPressedState = new UnsetPressedState(); 129 } 130 if (prepressed) { 131 mPrivateFlags |= PRESSED; 132 refreshDrawableState(); 133 postDelayed(mUnsetPressedState, 134 ViewConfiguration.getPressedStateDuration()); 135 } else if (!post(mUnsetPressedState)) { 136 // If the post failed, unpress right now 137 mUnsetPressedState.run(); 138 } 139 removeTapCallback(); 140 } 141 break; 142 case MotionEvent.ACTION_DOWN: 143 if (mPendingCheckForTap == null) { 144 mPendingCheckForTap = new CheckForTap(); 145 } 146 mPrivateFlags |= PREPRESSED; 147 mHasPerformedLongPress = false; 148 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 149 break; 150 case MotionEvent.ACTION_CANCEL: 151 mPrivateFlags &= ~PRESSED; 152 refreshDrawableState(); 153 removeTapCallback(); 154 break; 155 case MotionEvent.ACTION_MOVE: 156 final int x = (int) event.getX(); 157 final int y = (int) event.getY(); 158 // Be lenient about moving outside of buttons 159 int slop = mTouchSlop; 160 if ((x < 0 - slop) || (x >= getWidth() + slop) || 161 (y < 0 - slop) || (y >= getHeight() + slop)) { 162 // Outside button 163 removeTapCallback(); 164 if ((mPrivateFlags & PRESSED) != 0) { 165 // Remove any future long press/tap checks 166 removeLongPressCallback(); 167 // Need to switch from pressed to not pressed 168 mPrivateFlags &= ~PRESSED; 169 refreshDrawableState(); 170 } 171 } 172 break; 173 } 174 return true; 175 } 176 return false; 177 } public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
这段代码同样很长,同样挑重点看。
第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick 178 public boolean performClick() { 179 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 180 if (mOnClickListener != null) { 181 playSoundEffect(SoundEffectConstants.CLICK); 182 mOnClickListener.onClick(this); 183 return true; 184 } 185 return false; 186 } public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; } 没错,performClick确实执行了mOnClickListener.onClikc(this);就是执行我们设置的onClick事件。并且在onTouchEvent的89行返回了true。 在这部分分析中我们也发现了一个小秘密,如果我们复写了onTouchListener中的onTouch让它返回false,这样dispatchTouchEvent将不会执行到onTouchEvent方法,使得onClick事件永远得不到执行。 好了,全部分析完毕,做几点小总结 1、onTouch优先于onTouchEvent执行,且onTouch返回true将不执行onTouchEvent,并导致onTouchEvent里的点击事件得不到执行 2、手势事件从最外部的ViewGroup的dispatchTouchEvent开始分发,如果onInterceptTouchEvent拦截了该手势事件,即返回true,手势事件将不会往下分发,即不执行view的dispatchTouchEvent,而是让该ViewGroup的的父类dispatchTouchEvent来消费该手势事件。 3、如果View的dispatchTouchEvent消费了ACTION_DOWN事件,即对ACTION_DOWN返回true,后续的的ACTION_MOVE和ACTION_UP将直接被分发到这个view来消费。 |