转载自:http://blog.csdn.net/chenzujie/article/details/13277081


直接进入正题,在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

[java]view plaincopy

  1. publicbooleandispatchTouchEvent(MotionEventev){

  2. finalintaction=ev.getAction();

  3. finalfloatxf=ev.getX();

  4. finalfloatyf=ev.getY();

  5. finalfloatscrolledXFloat=xf+mScrollX;

  6. finalfloatscrolledYFloat=yf+mScrollY;

  7. finalRectframe=mTempRect;

  8. booleandisallowIntercept=(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. finalintscrolledXInt=(int)scrolledXFloat;

  16. finalintscrolledYInt=(int)scrolledYFloat;

  17. finalView[]children=mChildren;

  18. finalintcount=mChildrenCount;

  19. for(inti=count-1;i>=0;i--){

  20. finalViewchild=children[i];

  21. if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE

  22. ||child.getAnimation()!=null){

  23. child.getHitRect(frame);

  24. if(frame.contains(scrolledXInt,scrolledYInt)){

  25. finalfloatxc=scrolledXFloat-child.mLeft;

  26. finalfloatyc=scrolledYFloat-child.mTop;

  27. ev.setLocation(xc,yc);

  28. child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;

  29. if(child.dispatchTouchEvent(ev)){

  30. mMotionTarget=child;

  31. returntrue;

  32. }

  33. }

  34. }

  35. }

  36. }

  37. }

  38. booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||

  39. (action==MotionEvent.ACTION_CANCEL);

  40. if(isUpOrCancel){

  41. mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;

  42. }

  43. finalViewtarget=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. returnsuper.dispatchTouchEvent(ev);

  51. }

  52. if(!disallowIntercept&&onInterceptTouchEvent(ev)){

  53. finalfloatxc=scrolledXFloat-(float)target.mLeft;

  54. finalfloatyc=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. returntrue;

  62. }

  63. if(isUpOrCancel){

  64. mMotionTarget=null;

  65. }

  66. finalfloatxc=scrolledXFloat-(float)target.mLeft;

  67. finalfloatyc=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. returntarget.dispatchTouchEvent(ev);

  75. }


此段代码叫长,挑重点看,第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

[java]view plaincopy

  1. publicbooleandispatchTouchEvent(MotionEventevent){

  2. if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&

  3. mOnTouchListener.onTouch(this,event)){

  4. returntrue;

  5. }

  6. returnonTouchEvent(event);

  7. }


非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!


首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了

[java]view plaincopy

  1. publicvoidsetOnTouchListener(OnTouchListenerl){

  2. mOnTouchListener=l;

  3. }


显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。


if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。

而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。

继续看下view的onTouchEvent

[java]view plaincopy

  1. publicbooleanonTouchEvent(MotionEventevent){

  2. finalintviewFlags=mViewFlags;

  3. if((viewFlags&ENABLED_MASK)==DISABLED){

  4. //Adisabledviewthatisclickablestillconsumesthetouch

  5. //events,itjustdoesn'trespondtothem.

  6. return(((viewFlags&CLICKABLE)==CLICKABLE||

  7. (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));

  8. }

  9. if(mTouchDelegate!=null){

  10. if(mTouchDelegate.onTouchEvent(event)){

  11. returntrue;

  12. }

  13. }

  14. if(((viewFlags&CLICKABLE)==CLICKABLE||

  15. (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){

  16. switch(event.getAction()){

  17. caseMotionEvent.ACTION_UP:

  18. booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;

  19. if((mPrivateFlags&PRESSED)!=0||prepressed){

  20. //takefocusifwedon'thaveitalreadyandweshouldin

  21. //touchmode.

  22. booleanfocusTaken=false;

  23. if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){

  24. focusTaken=requestFocus();

  25. }

  26. if(!mHasPerformedLongPress){

  27. //Thisisatap,soremovethelongpresscheck

  28. removeLongPressCallback();

  29. //Onlyperformtakeclickactionsifwewereinthepressedstate

  30. if(!focusTaken){

  31. //UseaRunnableandpostthisratherthancalling

  32. //performClickdirectly.Thisletsothervisualstate

  33. //oftheviewupdatebeforeclickactionsstart.

  34. if(mPerformClick==null){

  35. mPerformClick=newPerformClick();

  36. }

  37. if(!post(mPerformClick)){

  38. performClick();

  39. }

  40. }

  41. }

  42. if(mUnsetPressedState==null){

  43. mUnsetPressedState=newUnsetPressedState();

  44. }

  45. if(prepressed){

  46. mPrivateFlags|=PRESSED;

  47. refreshDrawableState();

  48. postDelayed(mUnsetPressedState,

  49. ViewConfiguration.getPressedStateDuration());

  50. }elseif(!post(mUnsetPressedState)){

  51. //Ifthepostfailed,unpressrightnow

  52. mUnsetPressedState.run();

  53. }

  54. removeTapCallback();

  55. }

  56. break;

  57. caseMotionEvent.ACTION_DOWN:

  58. if(mPendingCheckForTap==null){

  59. mPendingCheckForTap=newCheckForTap();

  60. }

  61. mPrivateFlags|=PREPRESSED;

  62. mHasPerformedLongPress=false;

  63. postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());

  64. break;

  65. caseMotionEvent.ACTION_CANCEL:

  66. mPrivateFlags&=~PRESSED;

  67. refreshDrawableState();

  68. removeTapCallback();

  69. break;

  70. caseMotionEvent.ACTION_MOVE:

  71. finalintx=(int)event.getX();

  72. finalinty=(int)event.getY();

  73. //Belenientaboutmovingoutsideofbuttons

  74. intslop=mTouchSlop;

  75. if((x<0-slop)||(x>=getWidth()+slop)||

  76. (y<0-slop)||(y>=getHeight()+slop)){

  77. //Outsidebutton

  78. removeTapCallback();

  79. if((mPrivateFlags&PRESSED)!=0){

  80. //Removeanyfuturelongpress/tapchecks

  81. removeLongPressCallback();

  82. //Needtoswitchfrompressedtonotpressed

  83. mPrivateFlags&=~PRESSED;

  84. refreshDrawableState();

  85. }

  86. }

  87. break;

  88. }

  89. returntrue;

  90. }

  91. returnfalse;

  92. }


这段代码同样很长,同样挑重点看。


第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick

[java]view plaincopy

  1. publicbooleanperformClick(){

  2. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

  3. if(mOnClickListener!=null){

  4. playSoundEffect(SoundEffectConstants.CLICK);

  5. mOnClickListener.onClick(this);

  6. returntrue;

  7. }

  8. returnfalse;

  9. }



没错,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来消费。


最后感谢Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

两篇文章的作者,我就是在看了这两篇文章后,再看了些资料才了解了android手势分发事件


更多相关文章

  1. 蓝牙API
  2. android中锁屏后代码不运行的问题
  3. Android再按一次返回键退出程序的实现
  4. Android学习系列(43)--使用事件总线框架EventBus和Otto
  5. android 异步加载
  6. Android(安卓)View事件派发机制详解与源码分析
  7. Android之动态申请存储权限
  8. Android(安卓)Studio——点击事件监听
  9. Android(安卓)中文 API (29) —— CompoundButton

随机推荐

  1. Android控件GridView的使用
  2. Android 自定义TextView 实现文本间距
  3. Bing Maps Android SDK Released
  4. android 2.2 的新功能 ndk-gdb调试.
  5. Android设计模式系列(8)--SDK源码之工厂
  6. 关于Android 横竖屏切换(避免Activity在转
  7. Android实现数据存储技术
  8. 【流媒體】Android 实时视频采集/编码/传
  9. Android 之 退出的2种方式
  10. Android中的Surface和SurfaceView