dispatch、onIntercept、onTouch三者对MotionEvent的分发、拦截机制分析
来源:互联网 发布:178数据库7.0 编辑:程序博客网 时间:2024/06/15 03:30
近期正在测试一个通过左右触划MainView,平滑切换两侧菜单的项目,如果仅仅是触划切换也许我就不必顾虑什么dispatch、onIntercept了,估计一个onTouch就差不多搞定了,但是恼人的是MainView自身嵌套了比较复杂的可以上拉、下拉刷新的自定义PullToFreshView,而PullToFresh内部又嵌套了一个可以上下Scroll的ScrollView和GridView,MainView策划切换到菜单的手势必须与ScrollView中上下滚动Item的手势分开才行,这样必须搞一个MotionEvent拦截覆写在MainView(PullToFreshView)中,onTouch研究的并不深入,但用起来还是比较上手的,而Intercept看着眼熟,没深究过其拦截机制,恰恰再此项目中手势的拦截又写的很复杂,看来有必要把这三个方法理一理了。也许很多新手还想想不到onIntercept能有何作为,不过看看模拟的示意图,可以试着考虑:在右侧List打开状态,该如何分离一系列MotionEvent是拉回主界面的手势呢,还是上下翻动List的手势呢?
查阅关于oninterceptTouchEvent的使用方法,有很多资料,其用法就是判断当前ViewGroup是否有必要将MotionEvent继续往内(顶)层子view(Group)进行传递,而查询dispatchTouchEvent的使用方法则收获很少,经过大量翻阅,才发现自己做了很多无用功,不过好在对其略知一二了,便把这个方法的调用机制分享给那些同样在做无用功的同学们吧。
盲点一:它们的返回值决定了什么样的行为?
方法一:onTouchEvent(父(Group)View默认返回false;最外层可被点击的子View默认返回true)
onTouchEvent是最常见、最容易上手的方法,它是三兄弟中最基层的MotionEvent消费方法,处于流水线的最下游,负责着大部分MotionEvent的分析处理任务,是响应触摸指令的最直接单位。其意义在于消费一个MotionEvent对象,一旦吸收、转化了该对象,该对象就从对象流中被抹杀掉了,不会再往子View(Group)传递。
方法二:onInterceptTouchEvent(默认返回false)
onInterceptTouchEvent是初学者很少见的方法,该方法用于筛选有利于该层面的View(Group)的MotionEvent对象,根据筛选的返回值判断一个MotionEvent对象是消费在该层还是继续传递给内层嵌套的子View(Group),通常父View拦截、消费触摸是没有意义的,因此默认不拦截这些对象流,都发给最顶层或者最内层的View。当然位于最内层的View即便拦截也是没有意义的,对象传到这里就必须得出个结果,所以在最顶层的子View拦截也得进入onTouchEvent处理,不拦截还是要进入这个方法处理的,因此没必要覆写什么拦截方法了。
方法三:dispatchTouchEvent(默认返回true)
dispatchTouchEvent对初学者来说更加少见,而涉及其详细处理逻辑的文章非常少见,小生作本文的初衷便是窥得该方法的冰山一角,以为它是条大鱼,想再挖掘点好玩的出来,可费了老大劲后才发现,其内部无非是对前两个方法的迭代调用,只不过逻辑有点耐人寻味而已。闻其名而知其意,dispatch--分发,该方法是前两者的集合,而且其返回状态直接决定MotionEvent对象是进入该层View(Group)的拦截过滤、分析处理模块,还是直接丢弃。该方法如此霸道,自有其霸道的资本,因为任何MotionEvent对象默认都是先走他的父类方法的,既然产生触摸事件,那这些对象自然是默认进行分发操作的,只因为很少如此绝决的阻断某一系列Event对象,初级程序员很少有必要对该方法进行覆写罢了,即便覆写该方法,其内部迭代逻辑我们是改变不了的,只能在直接丢弃某些MotionEvent对象时有所作为,不过通常类似丢垃圾的操作都在onTouch里面解决,当然要想保持onTouch的整洁,可以考虑在dispatch中干掉目标Event,免去后顾之忧。
小结:我想到了一个很具体的机构可以用来形容三个方法的关系--快递公司。
dispatch相当于收集快递的卡车;onIntercept相当于分拣员;onTouch相当于快递员。只有有价值的快递才会被装进收件的卡车,碰到砖头、空箱之类的废物就直接丢弃。等开车载着值得分发的快递回到公司,该轮到分拣员工作了。分拣员可能会把这一车快递分成两类,一类是本地的,另一类是外地的,本地的快递自然要找本地的快递员递送到客户手中,而外地的快递还要重新装车拉往外地子公司再次进行以上集中、筛选、递送流程。下图是我自己做的关于拦截原理的示意图:
盲点二:它们的协作逻辑是怎样的?
要描述三者之间的逻辑,还是用伪代码来演示更明了了吧:
//首先进入分发环节dispatchTouchEvent(){ //进入dispatchTouchEvent方法//判断当前对象的返回值,true - 进行分发(拦截、处理);false - 不进行分发,直接丢弃if(dispatch== true){//进入onInterceptTouchEvent方法//其次判断当前onInterceptTouchEvent的返回值(是否被拦截)if(Intercept== false){//没被拦截,得到此ViewGroup的全部子类:for (int i = count - 1; i >= 0; i--){//获取子View(Group)对象,对其进行分发检查 final View child = children[i];child.dispatchTouchEvent(this){···};} } else {//进入onTouchEvent方法//如果ViewGroup被打断(onInterceptTouchEvent返回true),或者当前为最内(顶)层的纯ViewonTouchEvent();}} else {//将返回标志false的MotionEvent对象统统丢弃} }
小结:
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);// TODO Auto-generated constructor stub}public MainView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}@SuppressLint("NewApi")public MainView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}@SuppressLint("NewApi")@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.v(TAG, "III DDD");break;case MotionEvent.ACTION_MOVE:Log.v(TAG, "III MMM");//触摸屏幕上半边,拦截该View的所有ActionMove对象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) ;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (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);}@Overridepublic 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);}}
布局文件:activity_main.xml如下:
<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>
打印日志说明:标签Tag111代表MainView222 代表InnerView333或444 代表BtnView或TxtViewText前半段 DDD 代表dispatch方法内III 代表intercept方法内TTT 代表touch方法内Text后半段 DDD 代表Action_DownMMM 代表Action_MoveCCC 代表Action_CancelUUU 代表Action_Up
罗列部分日志供分析参考、分析:
情况一:轻点目标MainView一下,TAG=111,
01-23 12:25:32.159: V/111(15128): DDD DDD01-23 12:25:32.159: V/111(15128): III DDD01-23 12:25:32.159: V/111(15128): TTT DDD01-23 12:25:32.279: V/111(15128): DDD UUU01-23 12:25:32.279: V/111(15128): TTT UUU01-23 12:25:32.279: E/MainAct(15128): main click点击最底层红褐色区域,
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 DDD01-23 13:34:52.559: V/111(17377): III DDD01-23 13:34:52.559: I/222(17377): DDD DDD01-23 13:34:52.559: I/222(17377): III DDD01-23 13:34:52.559: I/222(17377): TTT DDD01-23 13:34:52.649: V/111(17377): DDD UUU01-23 13:34:52.649: V/111(17377): III UUU01-23 13:34:52.649: I/222(17377): DDD UUU01-23 13:34:52.649: I/222(17377): TTT UUU01-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 DDD01-23 13:46:14.679: V/111(17377): III DDD01-23 13:46:14.679: I/222(17377): DDD DDD01-23 13:46:14.679: I/222(17377): III DDD01-23 13:46:14.679: E/333(17377): DDD DDD01-23 13:46:14.679: E/333(17377): TTT DDD01-23 13:46:14.769: V/111(17377): DDD UUU01-23 13:46:14.769: V/111(17377): III UUU01-23 13:46:14.769: I/222(17377): DDD UUU01-23 13:46:14.769: I/222(17377): III UUU01-23 13:46:14.769: E/333(17377): DDD UUU01-23 13:46:14.769: E/333(17377): TTT UUU01-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 DDD01-23 14:49:31.189: V/111(18763): III DDD01-23 14:49:31.189: I/222(18763): DDD DDD01-23 14:49:31.189: I/222(18763): III DDD01-23 14:49:31.189: E/333(18763): DDD DDD01-23 14:49:31.189: E/333(18763): TTT DDD01-23 14:49:31.259: V/111(18763): DDD MMM01-23 14:49:31.259: V/111(18763): III MMM01-23 14:49:31.259: I/222(18763): DDD CCC01-23 14:49:31.259: I/222(18763): DDD UUU01-23 14:49:31.259: I/222(18763): III CCC01-23 14:49:31.259: E/333(18763): DDD CCC01-23 14:49:31.259: E/333(18763): TTT CCC01-23 14:49:31.289: V/111(18763): DDD MMM01-23 14:49:31.289: V/111(18763): TTT MMM//此处省略无限多111的Move状态日志······01-23 14:49:31.399: V/111(18763): DDD UUU01-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 DDD01-23 15:26:12.739: V/111(18763): III DDD01-23 15:26:12.739: I/222(18763): DDD DDD01-23 15:26:12.739: I/222(18763): III DDD01-23 15:26:12.739: E/333(18763): DDD DDD01-23 15:26:12.739: E/333(18763): TTT DDD01-23 15:26:12.819: V/111(18763): DDD MMM01-23 15:26:12.819: V/111(18763): III MMM01-23 15:26:12.819: I/222(18763): DDD MMM01-23 15:26:12.819: I/222(18763): III MMM01-23 15:26:12.819: E/333(18763): DDD CCC01-23 15:26:12.819: E/333(18763): TTT CCC01-23 15:26:12.839: V/111(18763): DDD MMM01-23 15:26:12.839: V/111(18763): III MMM01-23 15:26:12.839: I/222(18763): DDD MMM01-23 15:26:12.839: I/222(18763): TTT MMM//此处省略无限多111、222的Move状态日志······01-23 15:26:12.849: V/111(18763): DDD MMM01-23 15:26:12.849: V/111(18763): III MMM01-23 15:26:12.849: I/222(18763): DDD MMM01-23 15:26:12.849: I/222(18763): TTT MMM01-23 15:26:12.899: V/111(18763): DDD UUU01-23 15:26:12.899: V/111(18763): III UUU01-23 15:26:12.899: I/222(18763): DDD UUU01-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 DDD01-23 15:43:36.129: V/111(23276): III DDD01-23 15:43:36.129: I/222(23276): DDD DDD01-23 15:43:36.129: I/222(23276): III DDD01-23 15:43:36.129: I/222(23276): TTT DDD01-23 15:43:36.329: V/111(23276): DDD MMM01-23 15:43:36.329: V/111(23276): III MMM01-23 15:43:36.329: I/222(23276): DDD MMM01-23 15:43:36.329: I/222(23276): TTT MMM//此处省略无限多111、222的Move状态日志······01-23 15:43:36.689: V/111(23276): DDD UUU01-23 15:43:36.689: V/111(23276): III UUU01-23 15:43:36.689: I/222(23276): DDD UUU01-23 15:43:36.699: I/222(23276): TTT UUU01-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 DDD01-23 15:55:01.849: V/111(23981): III DDD01-23 15:55:01.849: I/222(23981): DDD DDD01-23 15:55:01.849: I/222(23981): III DDD01-23 15:55:01.849: E/333(23981): DDD DDD01-23 15:55:01.849: E/333(23981): TTT DDD01-23 15:55:01.899: V/111(23981): DDD MMM01-23 15:55:01.899: V/111(23981): III MMM01-23 15:55:01.899: I/222(23981): DDD MMM01-23 15:55:01.929: V/111(23981): DDD MMM01-23 15:55:01.929: V/111(23981): III MMM01-23 15:55:01.929: I/222(23981): DDD MMM01-23 15:55:01.949: V/111(23981): DDD MMM01-23 15:55:01.949: V/111(23981): III MMM01-23 15:55:01.949: I/222(23981): DDD MMM01-23 15:55:01.959: V/111(23981): DDD MMM01-23 15:55:01.959: V/111(23981): III MMM01-23 15:55:01.959: I/222(23981): DDD MMM01-23 15:55:01.979: V/111(23981): DDD MMM01-23 15:55:01.979: V/111(23981): III MMM01-23 15:55:01.979: I/222(23981): DDD MMM01-23 15:55:01.999: V/111(23981): DDD MMM01-23 15:55:01.999: V/111(23981): III MMM01-23 15:55:01.999: I/222(23981): DDD MMM01-23 15:55:02.069: V/111(23981): DDD UUU01-23 15:55:02.069: V/111(23981): III UUU01-23 15:55:02.069: I/222(23981): DDD UUU01-23 15:55:02.069: I/222(23981): III UUU01-23 15:55:02.069: E/333(23981): DDD UUU01-23 15:55:02.069: E/333(23981): TTT UUU01-23 15:55:02.069: E/MainAct(23981): btn click如果在dispatch中改动Move事件的返回标志,则每个Move对象传递到dispatch时都卡住了,不能进入本层以及内层的intercept、onTouch方法,因此归结其原因为dispatch返回false的所有对象都被丢弃了,不可能再往内层传递。因此dispatch是MotionEvent处理的重要方法,但一般不轻易在dispatch里面做手脚。
0 0
- dispatch、onIntercept、onTouch三者对MotionEvent的分发、拦截机制分析
- 15 Wallpaper 之OnTouch OnIntercept dispatch事件机制
- Android中级第十一讲之MotionEvent的分发、拦截机制分析
- dispatch onintercept
- Android开发之onTouch事件的分发拦截消费机制探究学习
- Android的onTouch事件分发机制
- Android MotionEvent分发机制
- android 的触摸事件的分发拦截机制分析
- OnTouch事件分发机制解析
- Android 事件拦截和分发机制分析
- Android中view的onTouch&onClick事件分发机制详解
- android事件分发机制dispatch
- Android 事件拦截机制、事件分发机制简单分析
- android 的分发、拦截、处理机制
- View事件的分发拦截机制流程
- Android事件的分发与拦截机制
- Android MotionEvent事件分发机制源码剖析
- Android学习记录:MotionEvent,onTouch,OnTouchListener 事件机制等学习
- 谷歌眼镜设计规范之排版
- python 库安装,安装 numpy matplotlib opencv wxpython PIL(linux环境下)
- 赋值语句的执行顺序
- Opencv 例程讲解8 ----如何实现Mat以及自定义类型的读写操作
- Java学习第五天:面向对象
- dispatch、onIntercept、onTouch三者对MotionEvent的分发、拦截机制分析
- jquery UI dialoag 窗口 建表 提交 (学习记录)
- 编译原理学习笔记06——(连连看—准备一下很多课件都演示的公式E → E+T | T )——2014_1_22
- db2导入导出表命令
- centos6 上用nginx 和 uwsgi 搭建 python web运行环境
- Construct Binary Tree from Preorder and Inorder Traversal
- Mysql date转string
- 黑马程序员_Java集合概述
- Hadoop管理员的十个最佳实践