转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315),请尊重他人的辛勤劳动成果,谢谢!

今天这篇文章主要分析的是Android的事件分发机制,采用例子加源码的方式让大家深刻的理解Android事件分发的具体情况,虽然网上很多Android的事件分发的文章,有些还写的不错,但是我还是决定写这篇文章,用我自己的思维方式来帮助大家理解Android事件分发,Android事件分发到底有多重要呢?相信很多Android开发者都明白吧,这个我就不介绍了,我也写了很多篇文章里面涉及到Android的事件处理的问题,可能不理解Android事件分发的朋友会有点难理解吧,不过没关系,相信看了这篇文章的你会对Android事件分发有进一步的理解。我这篇文章分析的源码是Android 2.2的源码, 也许你会说,干嘛不去分析最新的源码呢?我这里要解释一下,Android 2.2的源码跟最新的源码在功能效果方面是一样的,只不过最新的源码相对于Android 2.2来说更加健壮一些, Android 2.2的事件处理的代码几乎都写在一个方法体里面,而最新的源码分了很多个方法写,如果用最新的源码调用方法会绕来绕去的,相信你看的也会晕,出于这个考虑,我就拿Android 2.2的源码来给大家分析。


ViewGroup的事件分发机制

我们用手指去触摸Android手机屏幕,就会产生一个触摸事件,但是这个触摸事件在底层是怎么分发的呢?这个我还真不知道,这里涉及到操作硬件(手机屏幕)方面的知识,也就是Linux内核方面的知识,我也没有了解过这方面的东西,所以我们可能就往上层来分析分析,我们知道Android中负责与用户交互,与用户操作紧密相关的四大组件之一是Activity, 所以我们有理由相信Activity中存在分发事件的方法,这个方法就是dispatchTouchEvent(),我们先看其源码吧

[java] view plain copy print ?
  1. publicbooleandispatchTouchEvent(MotionEventev){
  2. //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法
  3. //是个空的方法,我们直接跳过这里看下面的实现
  4. if(ev.getAction()==MotionEvent.ACTION_DOWN){
  5. onUserInteraction();
  6. }
  7. if(getWindow().superDispatchTouchEvent(ev)){
  8. returntrue;
  9. }
  10. //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity
  11. //来处理,Activity的onTouchEvent()方法直接返回了false
  12. returnonTouchEvent(ev);
  13. }
这个方法中我们还是比较关心getWindow()的superDispatchTouchEvent()方法,getWindow()返回当前Activity的顶层窗口Window对象,我们直接看Window API的superDispatchTouchEvent()方法

[java] view plain copy print ?
  1. /**
  2. *Usedbycustomwindows,suchasDialog,topassthetouchscreenevent
  3. *furtherdowntheviewhierarchy.Applicationdevelopersshould
  4. *notneedtoimplementorcallthis.
  5. *
  6. */
  7. publicabstractbooleansuperDispatchTouchEvent(MotionEventevent);
这个是个抽象方法,所以我们直接找到其子类来看看superDispatchTouchEvent()方法的具体逻辑实现,Window的唯一子类是PhoneWindow,我们就看看PhoneWindow的superDispatchTouchEvent()方法

[java] view plain copy print ?
  1. publicbooleansuperDispatchTouchEvent(KeyEventevent){
  2. returnmDecor.superDispatcTouchEvent(event);
  3. }
里面直接调用DecorView类的superDispatchTouchEvent()方法,或许很多人不了解DecorView这个类,DecorView是PhoneWindow的一个final的内部类并且继承FrameLayout的,也是Window界面的最顶层的View对象,这是什么意思呢?别着急,我们接着往下看


我们先新建一个项目,取名AndroidTouchEvent,然后直接用模拟器运行项目, MainActivity的布局文件为

[html] view plain copy print ?
  1. <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. tools:context=".MainActivity">
  6. <TextView
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:layout_centerHorizontal="true"
  10. android:layout_centerVertical="true"
  11. android:text="@string/hello_world"/>
  12. </RelativeLayout>
利用hierarchyviewer工具来查看下MainActivity的View的层次结构,如下图

Android(Java):Android 事件分发机制_第1张图片

我们看到最顶层就是PhoneWindow$DecorView,接着DecorView下面有一个LinearLayout, LinearLayout下面有两个FrameLayout

上面那个FrameLayout是用来显示标题栏的,这个Demo中是一个TextView,当然我们还可以定制我们的标题栏,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是我们自定义标题栏的布局XML文件
下面的FrameLayout是用来装载ContentView的,也就是我们在Activity中利用setContentView()方法设置的View,现在我们知道了,原来我们利用setContentView()设置Activity的View的外面还嵌套了这么多的东西

我们来理清下思路,Activity的最顶层窗体是PhoneWindow,而PhoneWindow的最顶层View是DecorView,接下来我们就看DecorView类的superDispatchTouchEvent()方法

[java] view plain copy print ?
  1. publicbooleansuperDispatchTouchEvent(MotionEventevent){
  2. returnsuper.dispatchTouchEvent(event);
  3. }
在里面调用了父类FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并没有dispatchTouchEvent()方法,所以我们直接看ViewGroup的dispatchTouchEvent()方法 [java] view plain copy print ?
  1. /**
  2. *{@inheritDoc}
  3. */
  4. @Override
  5. publicbooleandispatchTouchEvent(MotionEventev){
  6. finalintaction=ev.getAction();
  7. finalfloatxf=ev.getX();
  8. finalfloatyf=ev.getY();
  9. finalfloatscrolledXFloat=xf+mScrollX;
  10. finalfloatscrolledYFloat=yf+mScrollY;
  11. finalRectframe=mTempRect;
  12. //这个值默认是false,然后我们可以通过requestDisallowInterceptTouchEvent(booleandisallowIntercept)方法
  13. //来改变disallowIntercept的值
  14. booleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
  15. //这里是ACTION_DOWN的处理逻辑
  16. if(action==MotionEvent.ACTION_DOWN){
  17. //清除mMotionTarget,每次ACTION_DOWN都很设置mMotionTarget为null
  18. if(mMotionTarget!=null){
  19. mMotionTarget=null;
  20. }
  21. //disallowIntercept默认是false,就看ViewGroup的onInterceptTouchEvent()方法
  22. if(disallowIntercept||!onInterceptTouchEvent(ev)){
  23. ev.setAction(MotionEvent.ACTION_DOWN);
  24. finalintscrolledXInt=(int)scrolledXFloat;
  25. finalintscrolledYInt=(int)scrolledYFloat;
  26. finalView[]children=mChildren;
  27. finalintcount=mChildrenCount;
  28. //遍历其子View
  29. for(inti=count-1;i>=0;i--){
  30. finalViewchild=children[i];
  31. //如果该子View是VISIBLE或者该子View正在执行动画,表示该View才
  32. //可以接受到Touch事件
  33. if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE
  34. ||child.getAnimation()!=null){
  35. //获取子View的位置范围
  36. child.getHitRect(frame);
  37. //如Touch到屏幕上的点在该子View上面
  38. if(frame.contains(scrolledXInt,scrolledYInt)){
  39. //offsettheeventtotheview'scoordinatesystem
  40. finalfloatxc=scrolledXFloat-child.mLeft;
  41. finalfloatyc=scrolledYFloat-child.mTop;
  42. ev.setLocation(xc,yc);
  43. child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
  44. //调用该子View的dispatchTouchEvent()方法
  45. if(child.dispatchTouchEvent(ev)){
  46. //如果child.dispatchTouchEvent(ev)返回true表示
  47. //该事件被消费了,设置mMotionTarget为该子View
  48. mMotionTarget=child;
  49. //直接返回true
  50. returntrue;
  51. }
  52. //Theeventdidn'tgethandled,trythenextview.
  53. //Don'tresettheevent'slocation,it'snot
  54. //necessaryhere.
  55. }
  56. }
  57. }
  58. }
  59. }
  60. //判断是否为ACTION_UP或者ACTION_CANCEL
  61. booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
  62. (action==MotionEvent.ACTION_CANCEL);
  63. if(isUpOrCancel){
  64. //如果是ACTION_UP或者ACTION_CANCEL,将disallowIntercept设置为默认的false
  65. //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true
  66. //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false
  67. //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false
  68. mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
  69. }
  70. //Theeventwasn'tanACTION_DOWN,dispatchittoourtargetif
  71. //wehaveone.
  72. finalViewtarget=mMotionTarget;
  73. //mMotionTarget为null意味着没有找到消费Touch事件的View,所以我们需要调用ViewGroup父类的
  74. //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法
  75. if(target==null){
  76. //Wedon'thaveatarget,thismeanswe'rehandlingthe
  77. //eventasaregularview.
  78. ev.setLocation(xf,yf);
  79. if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
  80. ev.setAction(MotionEvent.ACTION_CANCEL);
  81. mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
  82. }
  83. returnsuper.dispatchTouchEvent(ev);
  84. }
  85. //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE
  86. //ACTION_UP才会走到这里,假如在ACTION_MOVE或者ACTION_UP拦截的
  87. //Touch事件,将ACTION_CANCEL派发给target,然后直接返回true
  88. //表示消费了此Touch事件
  89. if(!disallowIntercept&&onInterceptTouchEvent(ev)){
  90. finalfloatxc=scrolledXFloat-(float)target.mLeft;
  91. finalfloatyc=scrolledYFloat-(float)target.mTop;
  92. mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
  93. ev.setAction(MotionEvent.ACTION_CANCEL);
  94. ev.setLocation(xc,yc);
  95. if(!target.dispatchTouchEvent(ev)){
  96. }
  97. //clearthetarget
  98. mMotionTarget=null;
  99. //Don'tdispatchthiseventtoourownview,becausewealready
  100. //sawitwhenintercepting;wejustwanttogivethefollowing
  101. //eventtothenormalonTouchEvent().
  102. returntrue;
  103. }
  104. if(isUpOrCancel){
  105. mMotionTarget=null;
  106. }
  107. //finallyoffsettheeventtothetarget'scoordinatesystemand
  108. //dispatchtheevent.
  109. finalfloatxc=scrolledXFloat-(float)target.mLeft;
  110. finalfloatyc=scrolledYFloat-(float)target.mTop;
  111. ev.setLocation(xc,yc);
  112. if((target.mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
  113. ev.setAction(MotionEvent.ACTION_CANCEL);
  114. target.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
  115. mMotionTarget=null;
  116. }
  117. //如果没有拦截ACTION_MOVE,ACTION_DOWN的话,直接将Touch事件派发给target
  118. returntarget.dispatchTouchEvent(ev);
  119. }
这个方法相对来说还是蛮长,不过所有的逻辑都写在一起,看起来比较方便,接下来我们就具体来分析一下


我们点击屏幕上面的TextView来看看Touch是如何分发的,先看看ACTION_DOWN

在DecorView这一层会直接调用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都会将mMotionTarget设置为null,mMotionTarget是什么?我们先不管,继续看代码,走到25行, disallowIntercept默认为false,我们再看ViewGroup的onInterceptTouchEvent()方法

[java] view plain copy print ?
  1. publicbooleanonInterceptTouchEvent(MotionEventev){
  2. returnfalse;
  3. }
直接返回false, 继续往下看,循环遍历DecorView里面的Child,从上面的MainActivity的层次结构图我们可以看出,DecorView里面只有一个Child那就是LinearLayout, 第43行判断Touch的位置在不在LinnearLayout上面,这是毫无疑问的,所以直接跳到51行, 调用LinearLayout的dispatchTouchEvent()方法,LinearLayout也没有dispatchTouchEvent()这个方法,所以也是调用ViewGroup的dispatchTouchEvent()方法,所以这个方法卡在51行没有继续下去,而是去先执行LinearLayout的dispatchTouchEvent()

LinearLayout调用dispatchTouchEvent()的逻辑跟DecorView是一样的,所以也是遍历LinearLayout的两个FrameLayout,判断Touch的是哪个FrameLayout,很明显是下面那个,调用下面那个FrameLayout的dispatchTouchEvent(), 所以LinearLayout的dispatchTouchEvent()卡在51也没继续下去

继续调用FrameLayout的dispatchTouchEvent()方法,和上面一样的逻辑,下面的FrameLayout也只有一个Child,就是RelativeLayout,FrameLayout的dispatchTouchEvent()继续卡在51行,先执行RelativeLayout的dispatchTouchEvent()方法

执行RelativeLayout的dispatchTouchEvent()方法逻辑还是一样的,循环遍历RelativeLayout里面的孩子,里面只有一个TextView, 所以这里就调用TextView的dispatchTouchEvent(), TextView并没有dispatchTouchEvent()这个方法,于是找TextView的父类View,在看View的dispatchTouchEvent()的方法之前,我们先理清下上面这些ViewGroup执行dispatchTouchEvent()的思路,我画了一张图帮大家理清下(这里没有画出onInterceptTouchEvent()方法)

Android(Java):Android 事件分发机制_第2张图片

上面的ViewGroup的Touch事件分发就告一段落先,因为这里要调用TextView(也就是View)的dispatchTouchEvent()方法,所以我们先分析View的dispatchTouchEvent()方法在将上面的继续下去


View的Touch事件分发机制

我们还是先看View的dispatchTouchEvent()方法的源码

[java] view plain copy print ?
  1. publicbooleandispatchTouchEvent(MotionEventevent){
  2. if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
  3. mOnTouchListener.onTouch(this,event)){
  4. returntrue;
  5. }
  6. returnonTouchEvent(event);
  7. }
在这个方法里面,先进行了一个判断

第一个条件mOnTouchListener就是我们调用View的setTouchListener()方法设置的

第二个条件是判断View是否为enabled的, View一般都是enabled,除非你手动设置为disabled

第三个条件就是OnTouchListener接口的onTouch()方法的返回值了,如果调用了setTouchListener()设置OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,否则就执行View的onTouchEvent() 并返回View的onTouchEvent()的值
现在你了解了View的onTouchEvent()方法和onTouch()的关系了吧,为什么Android提供了处理Touch事件onTouchEvent()方法还要增加一个OnTouchListener接口呢?我觉得OnTouchListener接口是对处理Touch事件的屏蔽和扩展作用吧,屏蔽作用我就不举例介绍了,看上面的源码就知道了,我就说下扩展吧,比如我们要打印View的Touch的点的坐标,我们可以自定义一个View如下

[java] view plain copy print ?
  1. publicclassCustomViewextendsView{
  2. publicCustomView(Contextcontext,AttributeSetattrs){
  3. super(context,attrs);
  4. }
  5. publicCustomView(Contextcontext,AttributeSetattrs,intdefStyle){
  6. super(context,attrs,defStyle);
  7. }
  8. @Override
  9. publicbooleanonTouchEvent(MotionEventevent){
  10. Log.i("tag","X的坐标="+event.getX()+"Y的坐标="+event.getY());
  11. returnsuper.onTouchEvent(event);
  12. }
  13. }
也可以直接对View设置OnTouchListener接口,在return的时候调用下v.onTouchEvent()

[java] view plain copy print ?
  1. view.setOnTouchListener(newOnTouchListener(){
  2. @Override
  3. publicbooleanonTouch(Viewv,MotionEventevent){
  4. Log.i("tag","X的坐标="+event.getX()+"Y的坐标="+event.getY());
  5. returnv.onTouchEvent(event);
  6. }
  7. });
这样子也实现了我们所需要的功能,所以我认为OnTouchListener是对onTouchEvent()方法的一个屏蔽和扩展作用,假如你有不一样的理解,你也可以告诉我下,这里就不纠结这个了。

我们再看View的onTouchEvent()方法

[java] view plain copy print ?
  1. publicbooleanonTouchEvent(MotionEventevent){
  2. finalintviewFlags=mViewFlags;
  3. if((viewFlags&ENABLED_MASK)==DISABLED){
  4. return(((viewFlags&CLICKABLE)==CLICKABLE||
  5. (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));
  6. }
  7. //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null
  8. if(mTouchDelegate!=null){
  9. if(mTouchDelegate.onTouchEvent(event)){
  10. returntrue;
  11. }
  12. }
  13. //如果View是clickable或者longClickable的onTouchEvent就返回true,否则返回false
  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. booleanfocusTaken=false;
  21. if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
  22. focusTaken=requestFocus();
  23. }
  24. if(!mHasPerformedLongPress){
  25. removeLongPressCallback();
  26. if(!focusTaken){
  27. if(mPerformClick==null){
  28. mPerformClick=newPerformClick();
  29. }
  30. if(!post(mPerformClick)){
  31. performClick();
  32. }
  33. }
  34. }
  35. if(mUnsetPressedState==null){
  36. mUnsetPressedState=newUnsetPressedState();
  37. }
  38. if(prepressed){
  39. mPrivateFlags|=PRESSED;
  40. refreshDrawableState();
  41. postDelayed(mUnsetPressedState,
  42. ViewConfiguration.getPressedStateDuration());
  43. }elseif(!post(mUnsetPressedState)){
  44. mUnsetPressedState.run();
  45. }
  46. removeTapCallback();
  47. }
  48. break;
  49. caseMotionEvent.ACTION_DOWN:
  50. if(mPendingCheckForTap==null){
  51. mPendingCheckForTap=newCheckForTap();
  52. }
  53. mPrivateFlags|=PREPRESSED;
  54. mHasPerformedLongPress=false;
  55. postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
  56. break;
  57. caseMotionEvent.ACTION_CANCEL:
  58. mPrivateFlags&=~PRESSED;
  59. refreshDrawableState();
  60. removeTapCallback();
  61. break;
  62. caseMotionEvent.ACTION_MOVE:
  63. finalintx=(int)event.getX();
  64. finalinty=(int)event.getY();
  65. //当手指在View上面滑动超过View的边界,
  66. intslop=mTouchSlop;
  67. if((x<0-slop)||(x>=getWidth()+slop)||
  68. (y<0-slop)||(y>=getHeight()+slop)){
  69. //Outsidebutton
  70. removeTapCallback();
  71. if((mPrivateFlags&PRESSED)!=0){
  72. removeLongPressCallback();
  73. mPrivateFlags&=~PRESSED;
  74. refreshDrawableState();
  75. }
  76. }
  77. break;
  78. }
  79. returntrue;
  80. }
  81. returnfalse;
  82. }
这个方法也是比较长的,我们先看第4行,如果一个View是disabled, 并且该View是Clickable或者longClickable, onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件,如果一个enabled的View,并且是clickable或者longClickable的,onTouchEvent()会执行下面的代码逻辑并返回true,综上,一个clickable或者longclickable的View是一直消费Touch事件的,而一般的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP)Button是clickable的,可以消费Touch事件,但是我们可以通过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。当然还可以通过重写View的onTouchEvent()方法来控制Touch事件的消费与否

我们在看57行的ACTION_DOWN, 新建一个CheckForTap,我们看看CheckForTap是什么

[java] view plain copy print ?
  1. privatefinalclassCheckForTapimplementsRunnable{
  2. publicvoidrun(){
  3. mPrivateFlags&=~PREPRESSED;
  4. mPrivateFlags|=PRESSED;
  5. refreshDrawableState();
  6. if((mViewFlags&LONG_CLICKABLE)==LONG_CLICKABLE){
  7. postCheckForLongClick(ViewConfiguration.getTapTimeout());
  8. }
  9. }
  10. }
原来是个Runnable对象,然后使用Handler的post方法延时ViewConfiguration.getTapTimeout()执行CheckForTap的run()方法,在run方法中先判断view是否longClickable的,一般的View都是false,postCheckForLongClick(ViewConfiguration.getTapTimeout())这段代码就是执行长按的逻辑的代码,只有当我们设置为longClickble才会去执行postCheckForLongClick(ViewConfiguration.getTapTimeout()),这里我就不介绍了

由于考虑到文章篇幅的问题,我就不继续分析View的长按事件和点击事件了,在这里我直接得出结论吧

长按事件是在ACTION_DOWN中执行,点击事件是在ACTION_UP中执行,要想执行长按事件,这个View必须是longclickable的, 也许你会纳闷,一般的View不是longClickable为什么也会执行长按事件呢?我们要执行长按事件必须要调用setOnLongClickListener()设置OnLongClickListener接口,我们看看这个方法的源码

[java] view plain copy print ?
  1. publicvoidsetOnLongClickListener(OnLongClickListenerl){
  2. if(!isLongClickable()){
  3. setLongClickable(true);
  4. }
  5. mOnLongClickListener=l;
  6. }
看到没有,如果这个View不是longClickable的,我们就调用setLongClickable(true)方法设置为longClickable的,所以才会去执行长按方法onLongClick();

要想执行点击事件,这个View就必须要消费ACTION_DOWN和ACTION_MOVE事件,并且没有设置OnLongClickListener的情况下,如果设置了OnLongClickListener的情况下,需要onLongClick()返回false才能执行到onClick()方法,也许你又会纳闷,一般的View默认是不消费touch事件的,这不是和你上面说的相违背嘛,我们要向执行点击事件必须要调用setOnClickListener()来设置OnClickListener接口,我们看看这个方法的源码就知道了

[java] view plain copy print ?
  1. publicvoidsetOnClickListener(OnClickListenerl){
  2. if(!isClickable()){
  3. setClickable(true);
  4. }
  5. mOnClickListener=l;
  6. }
所以说一个enable的并且是clickable的View是一直消费touch事件的,所以才会执行到onClick()方法



对于View的Touch事件的分发机制算是告一段落了,从上面我们可以得出TextView的dispatchTouchEvent()的返回false的,即不消费Touch事件。我们就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,由于TextView.dispatchTouchEvent()为false, 导致mMotionTarget没有被赋值,还是null, 继续往下走执行RelativeLayout的dispatchTouchEvent()方法, 来到第84行, 判断target是否为null,这个target就是mMotionTarget,满足条件,执行92行的 super.dispatchTouchEvent(ev)代码并返回, 这里调用的是RelativeLayout父类View的dispatchTouchEvent()方法,由于RelativeLayout没有设置onTouchListener, 所以这里直接调用RelativeLayout(其实就是View, 因为RelativeLayout没有重写onTouchEvent())的onTouchEvent()方法 由于RelativeLayout既不是clickable的也是longClickable的,所以其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,这里就执行完了RelativeLayout的dispatchTouchEvent()方法

继续执行FrameLayout的dispatchTouchEvent()的第51行,由于RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的逻辑是一样的, 也是执行到92行的super.dispatchTouchEvent(ev)代码并返回,然后执行FrameLayout的onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,所以FrameLayout的dispatchTouchEvent()方法返回false,执行完毕FrameLayout的dispatchTouchEvent()方法

在上面的我就不分析了,大家自行分析一下,跟上面的逻辑是一样的,我直接画了个图来帮大家理解下(这里没有画出onInterceptTouchEvent()方法)

Android(Java):Android 事件分发机制_第3张图片

所以我们点击屏幕上面的TextView的事件分发流程是上图那个样子的,表示Activity的View都不消费ACTION_DOWN事件,所以就不能在触发ACTION_MOVE, ACTION_UP等事件了,具体是为什么?我还不太清楚,毕竟从Activity到TextView这一层是分析不出来的,估计是在底层实现的。


但如果将TextView换成Button,流程是不是还是这个样子呢?答案不是,我们来分析分析一下,如果是Button , Button是一个clickable的View,onTouchEvent()返回true, 表示他一直消费Touch事件,所以Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,满足条件,进入到if方法体,设置mMotionTarget为Button,然后直接返回true, RelativeLayout的dispatchTouchEvent()方法执行完毕,不会调用到RelativeLayout的onTouchEvent()方法

然后到FrameLayout的dispatchTouchEvent()方法的51行,由于RelativeLayout.dispatchTouchEvent()返回true, 满足条件,进入if方法体,设置mMotionTarget为RelativeLayout,注意下,这里的mMotionTarget跟RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一个哦,因为他们是不同的方法中的,然后返回true

同理FrameLayout的dispatchTouchEvent()也是返回true,DecorView的dispatchTouchEvent()方法也返回true, 还是画一个流程图(这里没有画出onInterceptTouchEvent()方法)给大家理清下

Android(Java):Android 事件分发机制_第4张图片

从上面的流程图得出一个结论,Touch事件是从顶层的View一直往下分发到手指按下的最里面的View,如果这个View的onTouchEvent()返回false,即不消费Touch事件,这个Touch事件就会向上找父布局调用其父布局的onTouchEvent()处理,如果这个View返回true,表示消费了Touch事件,就不调用父布局的onTouchEvent()


接下来我们用一个自定义的ViewGroup来替换RelativeLayout,自定义ViewGroup代码如下

[java] view plain copy print ?
  1. packagecom.example.androidtouchevent;
  2. importandroid.content.Context;
  3. importandroid.util.AttributeSet;
  4. importandroid.view.MotionEvent;
  5. importandroid.widget.RelativeLayout;
  6. publicclassCustomLayoutextendsRelativeLayout{
  7. publicCustomLayout(Contextcontext,AttributeSetattrs){
  8. super(context,attrs,0);
  9. }
  10. publicCustomLayout(Contextcontext,AttributeSetattrs,intdefStyle){
  11. super(context,attrs,defStyle);
  12. }
  13. @Override
  14. publicbooleanonTouchEvent(MotionEventevent){
  15. returnsuper.onTouchEvent(event);
  16. }
  17. @Override
  18. publicbooleanonInterceptTouchEvent(MotionEventev){
  19. returntrue;
  20. }
  21. }

我们就重写了onInterceptTouchEvent(),返回true, RelativeLayout默认是返回false, 然后再CustomLayout布局中加一个Button ,如下图

Android(Java):Android 事件分发机制_第5张图片

我们这次不从DecorView的dispatchTouchEvent()分析了,直接从CustomLayout的dispatchTouchEvent()分析

我们先看ACTION_DOWN 来到25行,由于我们重写了onInterceptTouchEvent()返回true, 所以不走这个if里面,直接往下看代码,来到84行, target为null,所以进入if方法里面,直接调用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接调用View的onTouchEvent()方法,但是CustomLayout重写了onTouchEvent(),所以这里还是调用CustomLayout的onTouchEvent(), 这个方法返回false, 不消费Touch事件,所以不会在触发ACTION_MOVE,ACTION_UP等事件了,这里我再画一个流程图吧(含有onInterceptTouchEvent()方法的)

Android(Java):Android 事件分发机制_第6张图片


好了,就分析到这里吧,差不多分析完了,还有一种情况没有分析到,例如我将CustomLayout的代码改成下面的情形,Touch事件又是怎么分发的呢?我这里就不带大家分析了

[java] view plain copy print ?
  1. packagecom.example.androidtouchevent;
  2. importandroid.content.Context;
  3. importandroid.util.AttributeSet;
  4. importandroid.view.MotionEvent;
  5. importandroid.widget.RelativeLayout;
  6. publicclassCustomLayoutextendsRelativeLayout{
  7. publicCustomLayout(Contextcontext,AttributeSetattrs){
  8. super(context,attrs,0);
  9. }
  10. publicCustomLayout(Contextcontext,AttributeSetattrs,intdefStyle){
  11. super(context,attrs,defStyle);
  12. }
  13. @Override
  14. publicbooleanonTouchEvent(MotionEventevent){
  15. returnsuper.onTouchEvent(event);
  16. }
  17. @Override
  18. publicbooleanonInterceptTouchEvent(MotionEventev){
  19. if(ev.getAction()==MotionEvent.ACTION_MOVE){
  20. returntrue;
  21. }
  22. returnsuper.onInterceptTouchEvent(ev);
  23. }
  24. }
这篇文章的篇幅有点长,如果你想了解Touch事件的分发机制,你一定要认真看完,下面来总结一下吧

1.Activity的最顶层Window是PhoneWindow,PhoneWindow的最顶层View是DecorView

2.一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的

3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE

4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false

5.如果View设置了onTouchListener了,并且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()

6.Touch事件是从最顶层的View一直分发到手指touch的最里层的View,如果最里层View消费了ACTION_DOWN事件(设置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,如果某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理

7.Touch事件的分发过程中,如果消费了ACTION_DOWN,而在分发ACTION_MOVE的时候,某个ViewGroup拦截了Touch事件,就像上面那个自定义CustomLayout,则会将ACTION_CANCEL分发给该ViewGroup下面的Touch到的View,然后将Touch事件交给ViewGroup处理,并返回true

更多相关文章

  1. Android 4.1源码编译找不到资源文件解决办法
  2. Android中EditText 设置 imeOptions 无效问题的解决方法
  3. Android有两种方法检测USB设备插入
  4. Android消息机制Handler源码分析
  5. Android系统源码基础知识讲解
  6. android 回车键事件编程
  7. Android优秀实例源码
  8. android android屏幕禁止休眠和锁屏的方法
  9. Android向服务器传接和接收数据的方法汇总

随机推荐

  1. 使用requests爬取拉勾网python职位数据
  2. 影评分析 | 《小丑》,戴上快乐的笑脸
  3. 干货 | Bokeh交互式数据可视化快速入门
  4. Pandas 50题练习
  5. pdfkit | 自动化利器,生成PDF就靠它了
  6. numpy 100题练习 <一>
  7. 实用 | PyCharm常用快捷键整理
  8. 30例 | 一文搞懂python日期时间处理
  9. pandas_profiling :教你一行代码生成数据
  10. 知乎关注达1万,总阅读破百万,记录与分享带