zoukankan      html  css  js  c++  java
  • 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传递到最内层
  • 相关阅读:
    Web service是什么?
    SQL截取字符串
    SQL Server中使用索引性能的比较
    一个C#中webservice的初级例子(一)
    short s1 = 1; s1 = s1 + 1;有错而short s1 = 1; s1 += 1正确。为何?
    SQL索引
    ORDER BY 子句在子查询和公用表表达式中无效的一种解决办法使用表变量
    创建 索引,
    时间的重叠
    SQLServer Datetime数据类型的转换
  • 原文地址:https://www.cnblogs.com/fengju/p/6174405.html
Copyright © 2011-2022 走看看