Android中级第十一讲之MotionEvent的分发、拦截机制分析

最件看到事件分发机制,一方面在看内核剖析,一方面找测试小例子,最终找到,内容如下,——下载地址

咱们就借这个小例子来讲一些问题

一开始只关注了onTouch事件,应用于Window层,用来判断一些操作;后来研究到手势解锁,也只是onTouch的Down、Move、Up事件,根据移动的坐标确定点中的圆点;最后应用到PullToRefresh里Scrollview嵌套PageView,PagerView又嵌套Listview,涉及到父View分发事件到子View、孙View的复杂问题,所以需要明确dispatchTouchEvent(),onInterceptTouchEvent,onTouchEvent之间的关系

要描述三者之间的逻辑,还是用伪代码来演示更明了了吧:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //首先进入分发环节  
  2. dispatchTouchEvent()  
  3. //进入dispatchTouchEvent方法  
  4.     //判断当前对象的返回值,true - 进行分发(拦截、处理);false - 不进行分发,直接丢弃  
  5.     if(dispatch== true)  
  6.     {//进入onInterceptTouchEvent方法  
  7.         //其次判断当前onInterceptTouchEvent的返回值(是否被拦截)  
  8.         if(Intercept== false)  
  9.         {  
  10.             //没被拦截,得到此ViewGroup的全部子类:  
  11.             for (int i = count - 1; i >= 0; i--){  
  12.             //获取子View(Group)对象,对其进行分发检查  
  13.                 final View child = children[i];  
  14.             child.dispatchTouchEvent(this){···};  
  15.             }  
  16.             }  else  {//进入onTouchEvent方法  
  17.             //如果ViewGroup被打断(onInterceptTouchEvent返回true),或者当前为最内(顶)层的纯View  
  18.             onTouchEvent();  
  19.         }  
  20.     }  else  {  
  21.     //将返回标志false的MotionEvent对象统统丢弃  
  22.     }  
  23.  }  


小结:

MotionEvent对象首先流经dispatch,直接决定该对象分发、处理的必要性;dispatch返回true,才进入本层面的intercept拦截检查;拦截检查返回true的对象直接进入本层面的onTouch进行处理;拦截返回false的对象,将继续从底到上,从外到内传递给子类迭代这个分发、拦截、处理过程。

严正声明

上述观点大部分源于对开源知识的总结,小部分为个人通过Demo调试、分析获得,因此文章内容仅供参考,如有异议,小生洗耳恭听,在技术认知上求同存异、共同提高。下面是个人Demo的介绍。

yaongDemo主界面

提醒:本人习惯上把宿主(基本视图)相对于寄生者(内嵌视图)叫做(下),不适应的请转换一下思考角度。

本视图包含三层(View):

深褐色区域-自定义LinearLayout--MainView

墨绿色区域-自定义LinearLayout--InnerView

灰白色区域-自定义Button--BtnView

粉红色文字-仅作提示之用

操作方法:

触摸上半屏可以拦截该层以上(inner、btn)的Action_Move;

触摸下半屏可以拦截该层以上(btn)的的Action_Move

MainView.java如下(InnerView、BtnView的主体与此相同不再列出,只是对标签加以区分,便于分析日志):

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.yaong.myview;  
  2.   
  3. public class MainView extends LinearLayout {  
  4.   
  5.     private final String TAG = "111";  
  6.     private int iStart = 0 ;  
  7.     public MainView(Context context) {  
  8.         super(context);  
  9.         // TODO Auto-generated constructor stub  
  10.     }  
  11.   
  12.     public MainView(Context context, AttributeSet attrs) {  
  13.         super(context, attrs);  
  14.         // TODO Auto-generated constructor stub  
  15.     }  
  16.   
  17.     @SuppressLint("NewApi")  
  18.     public MainView(Context context, AttributeSet attrs, int defStyle) {  
  19.         super(context, attrs, defStyle);  
  20.         // TODO Auto-generated constructor stub  
  21.     }  
  22.   
  23.     @SuppressLint("NewApi")  
  24.     @Override  
  25.     public boolean onInterceptTouchEvent(MotionEvent event) {  
  26.         // TODO Auto-generated method stub  
  27.         switch (event.getAction()) {  
  28.         case MotionEvent.ACTION_DOWN:  
  29.             Log.v(TAG, "III    DDD");  
  30.             break;  
  31.         case MotionEvent.ACTION_MOVE:  
  32.             Log.v(TAG, "III    MMM");  
  33.             //触摸屏幕上半边,拦截该View的所有ActionMove对象  
  34.             if (event.getY()<Constant.iCenterY) {  
  35.                 return true;  
  36.             }  
  37.             break ;  
  38.         case MotionEvent.ACTION_CANCEL:  
  39.             Log.v(TAG, "III    CCC");  
  40.             break;  
  41.         case MotionEvent.ACTION_UP:  
  42.             Log.v(TAG, "III    UUU");  
  43.             break;  
  44.         default:  
  45.             break;  
  46.         }  
  47.         return super.onInterceptTouchEvent(event) ;  
  48.     }  
  49.   
  50.     @Override  
  51.     public boolean onTouchEvent(MotionEvent event) {  
  52.         // TODO Auto-generated method stub  
  53.         switch (event.getAction()) {  
  54.         case MotionEvent.ACTION_DOWN:  
  55.             Log.v(TAG, "TTT    DDD");  
  56.             break;  
  57.         case MotionEvent.ACTION_MOVE:  
  58.             Log.v(TAG, "TTT    MMM");  
  59.             break;  
  60.         case MotionEvent.ACTION_CANCEL:  
  61.             Log.v(TAG, "TTT    CCC");  
  62.             break;  
  63.         case MotionEvent.ACTION_UP:  
  64.             Log.v(TAG, "TTT    UUU");  
  65.             break;  
  66.         default:  
  67.             break;  
  68.         }  
  69.         return super.onTouchEvent(event);  
  70.     }  
  71.   
  72.     @Override  
  73.     public boolean dispatchTouchEvent(MotionEvent event) {  
  74.         switch (event.getAction()) {  
  75.         case MotionEvent.ACTION_DOWN:  
  76.             Log.v(TAG, "DDD    DDD");  
  77.             break ;  
  78.         case MotionEvent.ACTION_MOVE:  
  79.             Log.v(TAG, "DDD    MMM");  
  80.             break ;  
  81.         case MotionEvent.ACTION_CANCEL:  
  82.             Log.v(TAG, "DDD    CCC");  
  83.         case MotionEvent.ACTION_UP:  
  84.             Log.v(TAG, "DDD    UUU");  
  85.             break;  
  86.         default:  
  87.             break;  
  88.         }  
  89.         return super.dispatchTouchEvent(event);  
  90.     }  
  91. }<span style="font-size:18px;">  
  92. </span>  

布局文件:activity_main.xml如下:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;"><com.yaong.myview.MainView xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/main_view"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:background="#44F00F00"  
  7.     android:orientation="vertical"  
  8.     android:paddingBottom="@dimen/activity_vertical_margin"  
  9.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  10.     android:paddingRight="@dimen/activity_horizontal_margin"  
  11.     android:paddingTop="@dimen/activity_vertical_margin"  
  12.     tools:context=".MainActivity" >  
  13.   
  14.     <com.yaong.myview.ViewInner1  
  15.         android:id="@+id/inner_view"  
  16.         android:layout_width="match_parent"  
  17.         android:layout_height="match_parent"  
  18.         android:background="#4400FF00"  
  19.         android:orientation="vertical"  
  20.         android:paddingBottom="@dimen/activity_vertical_margin"  
  21.         android:paddingLeft="@dimen/activity_horizontal_margin"  
  22.         android:paddingRight="@dimen/activity_horizontal_margin"  
  23.         android:paddingTop="@dimen/activity_vertical_margin" >  
  24.   
  25.   
  26.         <com.yaong.myview.View_MyButton  
  27.             android:id="@+id/btn_view"  
  28.             android:layout_width="match_parent"  
  29.             android:layout_height="match_parent"  
  30.             android:text="ABCABC"  
  31.             android:textColor="@color/clr3"  
  32.              >  
  33.         </com.yaong.myview.View_MyButton>  
  34.   
  35.     </com.yaong.myview.ViewInner1>  
  36.   
  37. </com.yaong.myview.MainView></span>  
打印日志说明

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 标签Tag       111     代表MainView  
  2.         222         代表InnerView  
  3.         333或444     代表BtnView或TxtView  
  4. Text前半段     DDD         代表dispatch方法内  
  5.         III         代表intercept方法内  
  6.         TTT         代表touch方法内  
  7. Text后半段     DDD         代表Action_Down  
  8.         MMM         代表Action_Move  
  9.         CCC         代表Action_Cancel  
  10.         UUU         代表Action_Up  

罗列部分日志供分析参考、分析:

情况一:轻点目标MainView一下,TAG=111,

[sql] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 12:25:32.159: V/111(15128): DDD    DDD  
  2. 01-23 12:25:32.159: V/111(15128): III    DDD  
  3. 01-23 12:25:32.159: V/111(15128): TTT    DDD  
  4. 01-23 12:25:32.279: V/111(15128): DDD    UUU  
  5. 01-23 12:25:32.279: V/111(15128): TTT    UUU  
  6. 01-23 12:25:32.279: E/MainAct(15128): main click<span style="font-size:18px;">  
  7. </span>  
点击最底层红褐色区域,

Action_Down流程 dispatch(111)-->intercept(111)-->touch(111)

Action_Up流程 dispatch(111)-->touch(111)

分析:触摸事件的终点便是MainView,虽然在布局中内部嵌套了子View,但触摸与上层子View无关,只能由被点击View消费该事件,而Up事件作为Down的后续事件不必再进行拦截检测。

情况二:轻点目标InnerView一下,TAG=222,

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 13:34:52.559: V/111(17377): DDD    DDD  
  2. 01-23 13:34:52.559: V/111(17377): III    DDD  
  3. 01-23 13:34:52.559: I/222(17377): DDD    DDD  
  4. 01-23 13:34:52.559: I/222(17377): III    DDD  
  5. 01-23 13:34:52.559: I/222(17377): TTT    DDD  
  6. 01-23 13:34:52.649: V/111(17377): DDD    UUU  
  7. 01-23 13:34:52.649: V/111(17377): III    UUU  
  8. 01-23 13:34:52.649: I/222(17377): DDD    UUU  
  9. 01-23 13:34:52.649: I/222(17377): TTT    UUU  
  10. 01-23 13:34:52.649: E/MainAct(17377): inner click  
点击墨绿色环形区域,
Action_Down流程 dispatch(111)-->intercept(111)-->dispatch(222)-->intercept(222)-->touch(222)
Action_Up流程 dispatch(111)-->intercept(111)-->dispatch(222)-->touch(222)
分析:触摸事件的目标View是InnerView,触摸事件必然从父ViewGroup(111)传到子View(222),111中的拦截检测默认返回false,触摸事件继续向子View内传递,由于目标是InnerView,触摸事件将被222消费掉。

情况三:轻点目标BtnView一下,TAG=333,

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 13:46:14.679: V/111(17377): DDD    DDD  
  2. 01-23 13:46:14.679: V/111(17377): III    DDD  
  3. 01-23 13:46:14.679: I/222(17377): DDD    DDD  
  4. 01-23 13:46:14.679: I/222(17377): III    DDD  
  5. 01-23 13:46:14.679: E/333(17377): DDD    DDD  
  6. 01-23 13:46:14.679: E/333(17377): TTT    DDD  
  7. 01-23 13:46:14.769: V/111(17377): DDD    UUU  
  8. 01-23 13:46:14.769: V/111(17377): III    UUU  
  9. 01-23 13:46:14.769: I/222(17377): DDD    UUU  
  10. 01-23 13:46:14.769: I/222(17377): III    UUU  
  11. 01-23 13:46:14.769: E/333(17377): DDD    UUU  
  12. 01-23 13:46:14.769: E/333(17377): TTT    UUU  
  13. 01-23 13:46:14.769: E/MainAct(17377): btn click  


点击中间白色区域,
Action_Down流程 dispatch(111)-->intercept(111)-->dispatch(222)-->intercept(222)-->dispatch(333)-->touch(333)
Action_Up流程 dispatch(111)-->intercept(111)-->dispatch(222)-->intercept(222)-->dispatch(333)-->touch(333)
分析:触摸事件的目标View是BtnView,触摸事件必然途径111、222,再传到333中,111、222中拦截状态皆是false,直到对象传至333中被消费掉。

情况四:触划上半屏BtnView(MainVIew将拦截该层的所有ActionMove对象)

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 14:49:31.189: V/111(18763): DDD    DDD  
  2. 01-23 14:49:31.189: V/111(18763): III    DDD  
  3. 01-23 14:49:31.189: I/222(18763): DDD    DDD  
  4. 01-23 14:49:31.189: I/222(18763): III    DDD  
  5. 01-23 14:49:31.189: E/333(18763): DDD    DDD  
  6. 01-23 14:49:31.189: E/333(18763): TTT    DDD  
  7. 01-23 14:49:31.259: V/111(18763): DDD    MMM  
  8. 01-23 14:49:31.259: V/111(18763): III    MMM  
  9. 01-23 14:49:31.259: I/222(18763): DDD    CCC  
  10. 01-23 14:49:31.259: I/222(18763): DDD    UUU  
  11. 01-23 14:49:31.259: I/222(18763): III    CCC  
  12. 01-23 14:49:31.259: E/333(18763): DDD    CCC  
  13. 01-23 14:49:31.259: E/333(18763): TTT    CCC  
  14. 01-23 14:49:31.289: V/111(18763): DDD    MMM  
  15. 01-23 14:49:31.289: V/111(18763): TTT    MMM  
  16. //此处省略无限多111的Move状态日志······  
  17. 01-23 14:49:31.399: V/111(18763): DDD    UUU  
  18. 01-23 14:49:31.399: V/111(18763): TTT    UUU  
由于在MainView的onInterceptTouchEvent种对Action_Move进行了拦截,那么本应该传到Btn的Touch中的Action_Move对象将被拦截在111中,除了底层111的onTouch能接收Action_Move,其嵌套的InnerView、BtnView都将接收不到Move事件,也就是上面日志中随着移动手指,111将产生无限多Move事件,而另外两者则一直沉默。不过,困扰我的是日志中粉色背景色的周围的日志,ActionMove被拦截在111中后,222中产生一个ActionCancel事件,然后演变成UP事件,但222有Down记录,也再此产生了Up记录,但并没有产生一个对222的click事件。

情况五:触划下半屏BtnView(InnerVIew将拦截该层的所有ActionMove对象)

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 15:26:12.739: V/111(18763): DDD    DDD  
  2. 01-23 15:26:12.739: V/111(18763): III    DDD  
  3. 01-23 15:26:12.739: I/222(18763): DDD    DDD  
  4. 01-23 15:26:12.739: I/222(18763): III    DDD  
  5. 01-23 15:26:12.739: E/333(18763): DDD    DDD  
  6. 01-23 15:26:12.739: E/333(18763): TTT    DDD  
  7. 01-23 15:26:12.819: V/111(18763): DDD    MMM  
  8. 01-23 15:26:12.819: V/111(18763): III    MMM  
  9. 01-23 15:26:12.819: I/222(18763): DDD    MMM  
  10. 01-23 15:26:12.819: I/222(18763): III    MMM  
  11. 01-23 15:26:12.819: E/333(18763): DDD    CCC  
  12. 01-23 15:26:12.819: E/333(18763): TTT    CCC  
  13. 01-23 15:26:12.839: V/111(18763): DDD    MMM  
  14. 01-23 15:26:12.839: V/111(18763): III    MMM  
  15. 01-23 15:26:12.839: I/222(18763): DDD    MMM  
  16. 01-23 15:26:12.839: I/222(18763): TTT    MMM  
  17. //此处省略无限多111、222的Move状态日志······  
  18. 01-23 15:26:12.849: V/111(18763): DDD MMM  
  19. 01-23 15:26:12.849: V/111(18763): III MMM  
  20. 01-23 15:26:12.849: I/222(18763): DDD MMM  
  21. 01-23 15:26:12.849: I/222(18763): TTT MMM  
  22. 01-23 15:26:12.899: V/111(18763): DDD UUU  
  23. 01-23 15:26:12.899: V/111(18763): III UUU  
  24. 01-23 15:26:12.899: I/222(18763): DDD UUU  
  25. 01-23 15:26:12.899: I/222(18763): TTT UUU  
在该测试中,触划按钮,InnerView将对ActionMove对象进行拦截,只不过比情况四拦截的晚一个阶段,有日志信息可知Move事件在222被拦截住后,再333中产生了一个Cancel事件,该Cancel在BtnView的onTouchEvent中消费完后就陷入了沉默,而111、222依然在很有规律的打印Move信息。
情况六:更改222拦截位置到ActionDown,触划BtnView(InnerVIew将拦截该层的所有ActionDown对象)
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 15:43:36.129: V/111(23276): DDD    DDD  
  2. 01-23 15:43:36.129: V/111(23276): III    DDD  
  3. 01-23 15:43:36.129: I/222(23276): DDD    DDD  
  4. 01-23 15:43:36.129: I/222(23276): III    DDD  
  5. 01-23 15:43:36.129: I/222(23276): TTT    DDD  
  6. 01-23 15:43:36.329: V/111(23276): DDD    MMM  
  7. 01-23 15:43:36.329: V/111(23276): III    MMM  
  8. 01-23 15:43:36.329: I/222(23276): DDD    MMM  
  9. 01-23 15:43:36.329: I/222(23276): TTT    MMM  
  10. //此处省略无限多111、222的Move状态日志······  
  11. 01-23 15:43:36.689: V/111(23276): DDD UUU  
  12. 01-23 15:43:36.689: V/111(23276): III UUU  
  13. 01-23 15:43:36.689: I/222(23276): DDD UUU  
  14. 01-23 15:43:36.699: I/222(23276): TTT UUU  
  15. 01-23 15:43:36.699: E/MainAct(23276): inner click  
这种情况与情况五相似,而其差别在于,一旦Down事件被拦截,BtnView将不可能受到任何MotionEvent对象,也未在333中发生 收不到Move事件,莫名产生一个Cancel事件的情况,而且222产生了一个完整的Click事件。 
情况七:更改222的dispatch方法,在ActionMove事件后返回标志false(不分发Move事件)
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 01-23 15:55:01.849: V/111(23981): DDD    DDD  
  2. 01-23 15:55:01.849: V/111(23981): III    DDD  
  3. 01-23 15:55:01.849: I/222(23981): DDD    DDD  
  4. 01-23 15:55:01.849: I/222(23981): III    DDD  
  5. 01-23 15:55:01.849: E/333(23981): DDD    DDD  
  6. 01-23 15:55:01.849: E/333(23981): TTT    DDD  
  7. 01-23 15:55:01.899: V/111(23981): DDD    MMM  
  8. 01-23 15:55:01.899: V/111(23981): III    MMM  
  9. 01-23 15:55:01.899: I/222(23981): DDD    MMM  
  10. 01-23 15:55:01.929: V/111(23981): DDD    MMM  
  11. 01-23 15:55:01.929: V/111(23981): III    MMM  
  12. 01-23 15:55:01.929: I/222(23981): DDD    MMM  
  13. 01-23 15:55:01.949: V/111(23981): DDD    MMM  
  14. 01-23 15:55:01.949: V/111(23981): III    MMM  
  15. 01-23 15:55:01.949: I/222(23981): DDD    MMM  
  16. 01-23 15:55:01.959: V/111(23981): DDD    MMM  
  17. 01-23 15:55:01.959: V/111(23981): III    MMM  
  18. 01-23 15:55:01.959: I/222(23981): DDD    MMM  
  19. 01-23 15:55:01.979: V/111(23981): DDD    MMM  
  20. 01-23 15:55:01.979: V/111(23981): III    MMM  
  21. 01-23 15:55:01.979: I/222(23981): DDD    MMM  
  22. 01-23 15:55:01.999: V/111(23981): DDD    MMM  
  23. 01-23 15:55:01.999: V/111(23981): III    MMM  
  24. 01-23 15:55:01.999: I/222(23981): DDD    MMM  
  25. 01-23 15:55:02.069: V/111(23981): DDD    UUU  
  26. 01-23 15:55:02.069: V/111(23981): III    UUU  
  27. 01-23 15:55:02.069: I/222(23981): DDD    UUU  
  28. 01-23 15:55:02.069: I/222(23981): III    UUU  
  29. 01-23 15:55:02.069: E/333(23981): DDD    UUU  
  30. 01-23 15:55:02.069: E/333(23981): TTT    UUU  
  31. 01-23 15:55:02.069: E/MainAct(23981): btn click  
如果在dispatch中改动Move事件的返回标志,则每个Move对象传递到dispatch时都卡住了,不能进入本层以及内层的intercept、onTouch方法,因此归结其原因为dispatch返回false的所有对象都被丢弃了,不可能再往内层传递。因此dispatch是MotionEvent处理的重要方法,但一般不轻易在dispatch里面做手脚。

总结
经过对Demo的各种改最终就得到了上面那点理解,可能测试过程比较混乱,导致结果与预期的有所偏差,因此贴出此文以求改进,如果某位也愿意考究onTouch、onIntercept、dispatch里面的学问,可以考虑去下载我的测试Demo,当然自己写一个也不费啥事,文中不实之处,还望指正,共同完善。
测试结论:btn click ->Main dispatch down-onintercept down->Inter ……->Btn dispatch down -ontouch down->
Main dispatch up-onintercept up->Inter……->Btn dispatch up -ontouch up。
点击事件的传递是由最外层View传递到最内层
原文地址:https://www.cnblogs.com/fengju/p/6174405.html