自定义Viewgrou中我们也许会经常碰到这样的情况,2个子控件的事件冲突导致滑动没有用了,滑动反应很慢,点击没用了,要划很多次才移动一点点等等。也许我们第一反应就是百度,google去搜索下答案,把代码直接copy过来。其实也许可以换个解决办法,自己想想为什么会出现这种情况。

以下是博主对android事件分发机制的探索。希望大家看完后能对Android事件分发机制有一个详细的了解,以后不用百度,google也能轻松解决由于事件冲突导致各种问题。

首先我们要对Android 事件有初步的了解:

1.Android  Touch事件相关的函数包括了:

dispatchTouchEvent(MotionEvent ev):负责事件分发的函数,在各个view里面最先被调用

onInterceptTouchEvent(MotionEvent ev) :事件拦截的函数(viewGroup非常重要函数,下面会有具体说明)

onTouchEvent(MotionEvent ev):事件响应的函数

onTouch(MotionEvent ev):事件响应的函数

onTouchEvent(MotionEvent ev)和onTouch(MotionEvent ev)均是事件响应的函数,2者区别:onTouch会优先于onTouchEvent调用,onTouch只有在listener不为空与点击的控件为enable的情况下会被调用,onTouch能通过控件外部传入onTouchListener来实现监听,而onTouchEvent不能通过外部设置。(可能描述过于抽象,简单点就是有些控件没有ontouch事件,或者控件不可点击那么我们想监听onTouch事件就必须重写onTouchEvent来实现监听)


请看以下view的dispatchTouchEvent源码中调用onTouch()和onTouchEvent()的区别:


if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }
外层判断暂时不管(用来判断view是否位于顶部的,如果view不在顶部,过滤掉用户点击事件),请注意内层判断,当mListenerInfo中的mOnTouchListener不为空(即我们给view注册了监听事件)并且view是可点击的就把事件交给mListenerInfo的mOnTouchListener.onTouch来处理并且根据onTouchListener的boolean来决定事件是否继续传递,根据result的值来决定是否调用onTouchEvent

返回值说明:当dispatchTouchEvent(MotionEvent ev)返回为false表示继续向上传递,true表示停止传递

以下是事件传递的顺序:


假定我们有一个LinearLayout,   布局中有一个Button。那么touch事件的传递如下:


activity的dispatchTouchEvent()------>LinearLayout的dispatchTouchEvent()--------->onInterceptTouchEvent()------->button的dispatchTouchEvent()从根元素向上依次传递,如果中间我们重写了某view的dispatchTouchEvent()并且返回true,那么事件会停止继续传递并且由当前函数消费。onTouch和onTouchEvent一样的道理(这两者区别见上面描述),只是顺序正好和dispatchTouchEvent的顺序相反,从最外层向根元素传递。


至于onInterceptTouchEvent(),首先该函数是ViewGroup的函数,也意味着只有ViewGroup和该类的子类中可以重写该函数,例如我们自定义的view继承自LinearLayout(LinearLayout为ViewGroup的子类),那么我们就可以重写该函数来达到事件拦截的目的,该函数紧跟dispatchTouchEvent()后调用(前提是该函数存在,默认返回false),如果onInterceptTouchEvent()返回为false 事件会继续传递,如果返回为true,那么事件将停止继续向上面的dispatchTouchEvent()并且将事件交给自己的onTouch()和onTouchEvent()来处理。

下面我们来看下实验的结果

1.没有改变事件返回的结果


事件最终被customButton消费掉了,从中我们可以得到以下事件传递的图



2.重写onInterceptTouchEvent,并且返回为true截断事件继续传递



这里需要说明下由于在coustomLinearLayout中事件没有被消费掉(也就是Touch相关函数全部返回为false),如果是activity分发下去的事件那么最终会到由activity onTouchEvent()消费掉,下面是调用的示意图



3.CustomButton的onTouchEvent()返回false




4.点击在CustomLinearLayout上,没有点击到CustomButton





从上面我们可以得到

1.除了onInterceptTouchEvent()外,其他事件按照1所示依次由根元素传递给点击的view,并且由view消费掉,并且中间环节任意一个函数返回了true(除了onInterceptTouchEvent()外),那么事件将会由当前返回true的函数消费,停止向后面传递,由于函数过多,博主就没有把每个函数返回true的情况截图贴出来了。

2.ViewGroup的子类中,重写onInterceptTouchEvent()函数,返回为true,那么该函数将停止向子view的dispatchTouchEvent()传递,并把事件交由当前view的onTouch()和onTouchEvent()处理

3.view的onTouchEvent默认会消费掉事件,ViewGroup的0nTouchEvent则不会消费掉事件

4.同级别view,会根据你点击的控件来进行事件传递,传递到相应的你点击的view,如果点击的是ViewGroup,那么事件将不会被消费掉,直到传递到分发的根元素的OnTouchEvent()才会被消费掉


后续补充:

偶然回顾很久之前写的这篇博客,发现有关dispatchTouchEvent()函数的处理有些情况未做说明,容易导致读者出现误会,特此补充,

ViewGroup中dispatchTouchEvent()的返回值分为3种情况:

1.返回false 停止事件向上的传递.调用上级传递者的onTouchEvent()处理 


2.返回true    消费掉该次事件,终止事件传递


3.调用super返回     正常向下传递


说明:关于向下向上传递,只是个人理解的不同,我理解的事件分发模型类似一根立起来的管道,事件的传递从地面流向管道顶层,再流回到地面.正好符合视图叠加的流程.并不一定说这种就是对的,方便自己理解的就是好的,看官也不必纠结于这点,关键是去理解中间事件分发的流程


掌握了以上的的事件传递的基本知识,下次我们碰到事件冲突就可以尝试自己去解决了!

更多相关文章

  1. Android(安卓)Studio如何提示函数用法
  2. android 事件机制与事件监听(一)
  3. 老罗的Android视频教程整理之入门介绍
  4. Android(安卓)让人又爱又恨的触摸机制(二)
  5. Android安全讲座第九层 android gdb 调试实例演示(无源代码篇)
  6. Android(安卓)UI事件处理
  7. Android中WebView加载本地Html,与JavaScript与Android方法相互传
  8. Android平台调用so库中函数的流程及一些坑
  9. Android的JNI总结

随机推荐

  1. Android(安卓)VLAN的配置
  2. github中的常用库
  3. Android布局文件.xml中的自定义属性
  4. apk在线升级
  5. android单选框和复选框(练习)
  6. android 自定义通知
  7. Android获取网络状态
  8. Android模仿QQ空间图片上传——原理
  9. android动态显示图片
  10. android su源码