最件看到事件分发机制,一方面在看内核剖析,一方面找测试小例子,最终找到,内容如下,——下载地址
咱们就借这个小例子来讲一些问题
一开始只关注了onTouch事件,应用于Window层,用来判断一些操作;后来研究到手势解锁,也只是onTouch的Down、Move、Up事件,根据移动的坐标确定点中的圆点;最后应用到PullToRefresh里Scrollview嵌套PageView,PagerView又嵌套Listview,涉及到父View分发事件到子View、孙View的复杂问题,所以需要明确dispatchTouchEvent(),onInterceptTouchEvent,onTouchEvent之间的关系
要描述三者之间的逻辑,还是用伪代码来演示更明了了吧:
-
- dispatchTouchEvent()
- {
-
- if(dispatch== true)
- {
-
- if(Intercept== false)
- {
-
- for (int i = count - 1; i >= 0; i--){
-
- final View child = children[i];
- child.dispatchTouchEvent(this){···};
- }
- } else {
-
- onTouchEvent();
- }
- } else {
-
- }
- }
小结:
MotionEvent对象首先流经dispatch,直接决定该对象分发、处理的必要性;dispatch返回true,才进入本层面的intercept拦截检查;拦截检查返回true的对象直接进入本层面的onTouch进行处理;拦截返回false的对象,将继续从底到上,从外到内传递给子类迭代这个分发、拦截、处理过程。
严正声明
上述观点大部分源于对开源知识的总结,小部分为个人通过Demo调试、分析获得,因此文章内容仅供参考,如有异议,小生洗耳恭听,在技术认知上求同存异、共同提高。下面是个人Demo的介绍。
提醒:本人习惯上把宿主(基本视图)相对于寄生者(内嵌视图)叫做外(下),不适应的请转换一下思考角度。
本视图包含三层(View):
深褐色区域-自定义LinearLayout--MainView
墨绿色区域-自定义LinearLayout--InnerView
灰白色区域-自定义Button--BtnView
粉红色文字-仅作提示之用
操作方法:
触摸上半屏可以拦截该层以上(inner、btn)的Action_Move;
触摸下半屏可以拦截该层以上(btn)的的Action_Move。
MainView.java如下(InnerView、BtnView的主体与此相同不再列出,只是对标签加以区分,便于分析日志):
- package com.yaong.myview;
-
- public class MainView extends LinearLayout {
-
- private final String TAG = "111";
- private int iStart = 0 ;
- public MainView(Context context) {
- super(context);
-
- }
-
- public MainView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- }
-
- @SuppressLint("NewApi")
- public MainView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.v(TAG, "III DDD");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.v(TAG, "III MMM");
-
- if (event.getY()<Constant.iCenterY) {
- return true;
- }
- break ;
- case MotionEvent.ACTION_CANCEL:
- Log.v(TAG, "III CCC");
- break;
- case MotionEvent.ACTION_UP:
- Log.v(TAG, "III UUU");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(event) ;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.v(TAG, "TTT DDD");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.v(TAG, "TTT MMM");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.v(TAG, "TTT CCC");
- break;
- case MotionEvent.ACTION_UP:
- Log.v(TAG, "TTT UUU");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.v(TAG, "DDD DDD");
- break ;
- case MotionEvent.ACTION_MOVE:
- Log.v(TAG, "DDD MMM");
- break ;
- case MotionEvent.ACTION_CANCEL:
- Log.v(TAG, "DDD CCC");
- case MotionEvent.ACTION_UP:
- Log.v(TAG, "DDD UUU");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(event);
- }
- }<span style="font-size:18px;">
- </span>
布局文件:activity_main.xml如下:
- <span style="font-size:14px;"><com.yaong.myview.MainView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/main_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#44F00F00"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity" >
-
- <com.yaong.myview.ViewInner1
- android:id="@+id/inner_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#4400FF00"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin" >
-
-
- <com.yaong.myview.View_MyButton
- android:id="@+id/btn_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="ABCABC"
- android:textColor="@color/clr3"
- >
- </com.yaong.myview.View_MyButton>
-
- </com.yaong.myview.ViewInner1>
-
- </com.yaong.myview.MainView></span>
打印日志说明:
- 标签Tag 111 代表MainView
- 222 代表InnerView
- 333或444 代表BtnView或TxtView
- Text前半段 DDD 代表dispatch方法内
- III 代表intercept方法内
- TTT 代表touch方法内
- Text后半段 DDD 代表Action_Down
- MMM 代表Action_Move
- CCC 代表Action_Cancel
- UUU 代表Action_Up
罗列部分日志供分析参考、分析:
情况一:轻点目标MainView一下,TAG=111,
- 01-23 12:25:32.159: V/111(15128): DDD DDD
- 01-23 12:25:32.159: V/111(15128): III DDD
- 01-23 12:25:32.159: V/111(15128): TTT DDD
- 01-23 12:25:32.279: V/111(15128): DDD UUU
- 01-23 12:25:32.279: V/111(15128): TTT UUU
- 01-23 12:25:32.279: E/MainAct(15128): main click<span style="font-size:18px;">
- </span>
点击最底层红褐色区域,
Action_Down流程 dispatch(111)-->intercept(111)-->touch(111)
Action_Up流程 dispatch(111)-->touch(111)
分析:触摸事件的终点便是MainView,虽然在布局中内部嵌套了子View,但触摸与上层子View无关,只能由被点击View消费该事件,而Up事件作为Down的后续事件不必再进行拦截检测。
情况二:轻点目标InnerView一下,TAG=222,
- 01-23 13:34:52.559: V/111(17377): DDD DDD
- 01-23 13:34:52.559: V/111(17377): III DDD
- 01-23 13:34:52.559: I/222(17377): DDD DDD
- 01-23 13:34:52.559: I/222(17377): III DDD
- 01-23 13:34:52.559: I/222(17377): TTT DDD
- 01-23 13:34:52.649: V/111(17377): DDD UUU
- 01-23 13:34:52.649: V/111(17377): III UUU
- 01-23 13:34:52.649: I/222(17377): DDD UUU
- 01-23 13:34:52.649: I/222(17377): TTT UUU
- 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,
- 01-23 13:46:14.679: V/111(17377): DDD DDD
- 01-23 13:46:14.679: V/111(17377): III DDD
- 01-23 13:46:14.679: I/222(17377): DDD DDD
- 01-23 13:46:14.679: I/222(17377): III DDD
- 01-23 13:46:14.679: E/333(17377): DDD DDD
- 01-23 13:46:14.679: E/333(17377): TTT DDD
- 01-23 13:46:14.769: V/111(17377): DDD UUU
- 01-23 13:46:14.769: V/111(17377): III UUU
- 01-23 13:46:14.769: I/222(17377): DDD UUU
- 01-23 13:46:14.769: I/222(17377): III UUU
- 01-23 13:46:14.769: E/333(17377): DDD UUU
- 01-23 13:46:14.769: E/333(17377): TTT UUU
- 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对象)
- 01-23 14:49:31.189: V/111(18763): DDD DDD
- 01-23 14:49:31.189: V/111(18763): III DDD
- 01-23 14:49:31.189: I/222(18763): DDD DDD
- 01-23 14:49:31.189: I/222(18763): III DDD
- 01-23 14:49:31.189: E/333(18763): DDD DDD
- 01-23 14:49:31.189: E/333(18763): TTT DDD
- 01-23 14:49:31.259: V/111(18763): DDD MMM
- 01-23 14:49:31.259: V/111(18763): III MMM
- 01-23 14:49:31.259: I/222(18763): DDD CCC
- 01-23 14:49:31.259: I/222(18763): DDD UUU
- 01-23 14:49:31.259: I/222(18763): III CCC
- 01-23 14:49:31.259: E/333(18763): DDD CCC
- 01-23 14:49:31.259: E/333(18763): TTT CCC
- 01-23 14:49:31.289: V/111(18763): DDD MMM
- 01-23 14:49:31.289: V/111(18763): TTT MMM
-
- 01-23 14:49:31.399: V/111(18763): DDD UUU
- 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对象)
- 01-23 15:26:12.739: V/111(18763): DDD DDD
- 01-23 15:26:12.739: V/111(18763): III DDD
- 01-23 15:26:12.739: I/222(18763): DDD DDD
- 01-23 15:26:12.739: I/222(18763): III DDD
- 01-23 15:26:12.739: E/333(18763): DDD DDD
- 01-23 15:26:12.739: E/333(18763): TTT DDD
- 01-23 15:26:12.819: V/111(18763): DDD MMM
- 01-23 15:26:12.819: V/111(18763): III MMM
- 01-23 15:26:12.819: I/222(18763): DDD MMM
- 01-23 15:26:12.819: I/222(18763): III MMM
- 01-23 15:26:12.819: E/333(18763): DDD CCC
- 01-23 15:26:12.819: E/333(18763): TTT CCC
- 01-23 15:26:12.839: V/111(18763): DDD MMM
- 01-23 15:26:12.839: V/111(18763): III MMM
- 01-23 15:26:12.839: I/222(18763): DDD MMM
- 01-23 15:26:12.839: I/222(18763): TTT MMM
-
- 01-23 15:26:12.849: V/111(18763): DDD MMM
- 01-23 15:26:12.849: V/111(18763): III MMM
- 01-23 15:26:12.849: I/222(18763): DDD MMM
- 01-23 15:26:12.849: I/222(18763): TTT MMM
- 01-23 15:26:12.899: V/111(18763): DDD UUU
- 01-23 15:26:12.899: V/111(18763): III UUU
- 01-23 15:26:12.899: I/222(18763): DDD UUU
- 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对象)
- 01-23 15:43:36.129: V/111(23276): DDD DDD
- 01-23 15:43:36.129: V/111(23276): III DDD
- 01-23 15:43:36.129: I/222(23276): DDD DDD
- 01-23 15:43:36.129: I/222(23276): III DDD
- 01-23 15:43:36.129: I/222(23276): TTT DDD
- 01-23 15:43:36.329: V/111(23276): DDD MMM
- 01-23 15:43:36.329: V/111(23276): III MMM
- 01-23 15:43:36.329: I/222(23276): DDD MMM
- 01-23 15:43:36.329: I/222(23276): TTT MMM
-
- 01-23 15:43:36.689: V/111(23276): DDD UUU
- 01-23 15:43:36.689: V/111(23276): III UUU
- 01-23 15:43:36.689: I/222(23276): DDD UUU
- 01-23 15:43:36.699: I/222(23276): TTT UUU
- 01-23 15:43:36.699: E/MainAct(23276): inner click
这种情况与情况五相似,而其差别在于,一旦Down事件被拦截,BtnView将不可能受到任何MotionEvent对象,也未在333中发生 收不到Move事件,莫名产生一个Cancel事件的情况,而且222产生了一个完整的Click事件。
情况七:更改222的dispatch方法,在ActionMove事件后返回标志false(不分发Move事件)
- 01-23 15:55:01.849: V/111(23981): DDD DDD
- 01-23 15:55:01.849: V/111(23981): III DDD
- 01-23 15:55:01.849: I/222(23981): DDD DDD
- 01-23 15:55:01.849: I/222(23981): III DDD
- 01-23 15:55:01.849: E/333(23981): DDD DDD
- 01-23 15:55:01.849: E/333(23981): TTT DDD
- 01-23 15:55:01.899: V/111(23981): DDD MMM
- 01-23 15:55:01.899: V/111(23981): III MMM
- 01-23 15:55:01.899: I/222(23981): DDD MMM
- 01-23 15:55:01.929: V/111(23981): DDD MMM
- 01-23 15:55:01.929: V/111(23981): III MMM
- 01-23 15:55:01.929: I/222(23981): DDD MMM
- 01-23 15:55:01.949: V/111(23981): DDD MMM
- 01-23 15:55:01.949: V/111(23981): III MMM
- 01-23 15:55:01.949: I/222(23981): DDD MMM
- 01-23 15:55:01.959: V/111(23981): DDD MMM
- 01-23 15:55:01.959: V/111(23981): III MMM
- 01-23 15:55:01.959: I/222(23981): DDD MMM
- 01-23 15:55:01.979: V/111(23981): DDD MMM
- 01-23 15:55:01.979: V/111(23981): III MMM
- 01-23 15:55:01.979: I/222(23981): DDD MMM
- 01-23 15:55:01.999: V/111(23981): DDD MMM
- 01-23 15:55:01.999: V/111(23981): III MMM
- 01-23 15:55:01.999: I/222(23981): DDD MMM
- 01-23 15:55:02.069: V/111(23981): DDD UUU
- 01-23 15:55:02.069: V/111(23981): III UUU
- 01-23 15:55:02.069: I/222(23981): DDD UUU
- 01-23 15:55:02.069: I/222(23981): III UUU
- 01-23 15:55:02.069: E/333(23981): DDD UUU
- 01-23 15:55:02.069: E/333(23981): TTT UUU
- 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,当然自己写一个也不费啥事,文中不实之处,还望指正,共同完善。
参考:http://blog.csdn.net/ya0ng/article/details/18658385