通俗理解Android事件分发与消费机制
深入:Android Touch事件传递机制全面解析(从WMS到View树)
通俗理解Android事件分发与消费机制
说起Android滑动冲突,是个很常见的场景,比如SliddingMenu与ListView的嵌套,要解决滑动冲突,不得不提及到View的事件分发机制。
Touch事件传递规则分析
首先,我们要知道Touch事件是包装在MotionEvent对象中的,在手指与屏幕接触过程中产生一系列事件,典型的事件有以下三种:
ACTION_DOWN:手指刚接触屏幕的瞬间
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指刚离开屏幕的瞬间
那么,Android中Touch事件是一个怎样的传递过程呢?
1 , 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件发生时Activity的dispatchTouchEvent(MotionEvent ev)方法会将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,该方法对事件进行分发。分发逻辑如下:
如果return true,事件会由当前View的dispatchTouchEvent方法进行消费,同时事件会停止向下传递;
如果return false,事件分发分为两种情况:
如果当前 View 获取的事件直接来自 Activity,则会将事件返回给Activity的onTouchEvent进行消费;
如果当前 View 获取的事件来自外层父控件,则会将事件返回给父View的onTouchEvent进行消费。
如果return super.dispatchTouchEvent(ev),事件分发分为两种情况:
如果当前View是ViewGroup,则事件会分发给onInterceptTouchEvent方法进行处理;
如果当前View是普通View,则事件直接交给onTouchEvent方法进行处理
2, 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
此方法只有ViewGroup才有, Activity与普通View没有。上面已经提到,如果当前ViewGroup的dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev), 那么事件会传递到传递到onInterceptTouchEvent方法, 该方法对事件进行拦截。拦截逻辑如下:
如果return true,则表示拦截该事件,并将事件交给当前View的onTouchEvent方法;
如果return false,则表示不拦截该事件,并将该事件交由子View的dispatchTouchEvent方法进行事件分发,重复上述过程;
如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况:
如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发给子View 处理, 此时相当于return false。
如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。
一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而ScrollView、ListView等ViewGroup则可能拦截,得看具体情况。
3, 事件响应:public boolean onTouchEvent(MotionEvent ev)
上面已经提到,在dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent进行拦截(事件拦截返回true)的情况下,那么事件会传递到onTouchEvent方法,该方法对事件进行响应。响应逻辑如下:
如果return true,则表示响应并消费该事件;
如果return fasle,则表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么认为该事件不消耗,则在同一个事件系列中,当前View无法再次接收到事件,该事件会交由Activity的onTouchEvent进行处理;
如果return super.dispatchTouchEvent(ev),事件处理分为两种情况:
如果该View是clickable或者longclickable的,则会返回true, 表示消费了该事件, 与返回true一样;
如果该View不是clickable或者longclickable的,则会返回false, 表示不消费该事件,将会向上传递,与返回false一样.
上述三个方法到底有什么区别与联系呢?我们通过一段伪代码来表示:
事件分发伪代码
- public boolean dispatchTouchEvent(MotionEvent ev){
- boolean consume = false;
- if(onInterceptTouchEvent(ev)){ // 如果onInterceptTouchEvent返回true
- consume = onTouchEvent(ev); // 则交由该View的onTouchEvent方法
- } else {
- consume = child. dispatchTouchEvent(ev); // 否则交由子View的dispatchTouchEvent事件进行分发
- }
- return consume; // 如果成功消费该事件,则返回true,然后停止传递,否则返回false
- }
-------------------------------------------------------------------分割线------------------------------------------------------------
各组件对应方法的有无情况:
注: Activity的dispatchTouchEvent最终是调用了Window对应DecorView的dispatchTouchEvent, 相当于ViewGroup; onTouchEvent是Activity自带的方法并不是DecorView的onTouchEvent; 同时,没有onInterceptTouchEvent方法是因为Window并没有回调该方法。
返回值作用:true和false标志事件是否被消费。
如果消费了就不再传递给其他控件了,如果没有消费则还会传递给父控件或者子控件,触发相应控件的事件处理函数。
控件默认返回值
1,对于ViewGroup的onInterceptTouchEvent方法:
如果存在子View且点击到了子View, 则不拦截, 继续分发给子View 处理, 此时返回super.onInterceptTouchEvent(ev) 就相当于return false。
如果没有子View或者有子View但是没有点击中子View(此时ViewGroup相当于普通View), 则交由当前View的onTouchEvent响应,此时返回super.onInterceptTouchEvent(ev) 相当于return true。
2,对于View的onTouchEvent方法: 如果是clickable或者longClickable的,则返回true消费该事件; 否则返回false不消费该事件,从而往上传递.
-------------------------------------------------------------------分割线------------------------------------------------------------
注:同一个事件序列是指从手指接触屏幕的那一刻开始,到手指离开屏幕那一刻结束,在这过程中所产生的一系列事件,这个事件序列以down事件开始,以up事件结束,中间含有数量不定的move事件.
事件分发与消费的规则总结:
(1)事件的分发是以隧道方式由上到下的, 即事件总是先传递给父元素, 然后再由父元素分发给子元素。对于onTouchEvent事件,如果返回false,则会以冒泡方式向上传递。 顶级View接收到事件之后,就会按相应规则去分发事件。如果一个View的onTouchEvent方法返回false,那么将会交给父容器的onTouchEvent方法进行处理,以冒泡方式逐级往上,如果所有的View都不处理该事件,则交由Activity的onTouchEvent进行处理。就跟工作中遇到了难题,逐级找领导解决一个道理,领导解决不了,再找上一级领导。
(2)正常情况下,一个事件序列只能被一个View拦截且消耗。某个View一旦进行事件拦截,那么这一个事件序列都只能交由他处理,并且onInterceptTouchEvent也不会被再次调用。因此,正常情况下一个事件是不能交给两个View来处理的,当然,特殊做法就是在View的onTouchEvent处理完之后再返回false,强行交给其他View处理。
(3)如果某一个View开始处理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),则同一事件序列比如接下来进行ACTION_MOVE、ACTION_UP,则不会再交给该View处理,并且事件将重新提交给它的父元素处理。就像工作中做一件事情,你要么做完,要么你就不要做这件事了。
(4)ViewGroup的onInterceptTouchEvent方法默认返回false,即不拦截任何事件,而交给子View进行分发处理(前提是有子View)。
(5)普通View(比如TextView、ImageView,非ViewGroup)没有onInterceptTouchEvent方法, 一旦有事件传递给它,它的onTouchEvent方法就会被调用。正常情况下,它们都会消耗事件(返回true),除非它们是不可点击的(clickable和longClickable都为false),那么就会交由父容器的onTouchEvent处理。View的longClickable默认都是false的,而对于clickable则要分情况,比如Button的clickable默认你是true,而TextView默认是false.
(6)如果View不消耗除down以外的其他事件, 此时父View的onTouchEvent并不会被调用, 并且当前View可以持续收到后续事件,最终这些事件会传递给Activity处理.
(7)View的enable属性不影响onTouchEvent的默认返回值,只要它clickable或者longClickable为true,则onTouchEvent就会返回true。
(8) 如果当前View是可点击的,并且它收到了down和up事件(以down开始,以up结束),则它的click事件就会触发;对于onLongClick,则只要当前View是longClickable的并接收到down事件且超过了系统默认的long时间,则就会触发,只与down事件有关而与up事件无关.
(9)点击事件分发过程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是说,我们平时调用的setOnClickListener,优先级是最低的,所以,OnTouchListener的onTouch方法如果返回true,则不响应onClick方法...
(10) 子View可以通过requestDisallowInterceptTouchEvent方法请求父控件不要拦截事件,从而干预事件的分发过程,但是down事件除外,无法干预到
(11)如果一个View监听了onTouch,则在onTouch里面应该返回false,否则onTouchEvent事件及点击、长按事件就无法监听到
(12)如果ViewGroup中的子View将传递的事件消费掉,ViewGroup的onTouch将无法接收到任何事件, 但onTouchEvent还是能接收到的; 如果是View的onTouchEvent消费,则该View的onTouch仍然能接收到事件,因为此时onTouch的调用在onTouchEvent之前。 总之,对于View, 无论onTouchEvent消费与否,都会触发View的onTouch事件, 因为onTouch的调用在onTouchEvent之前。
-------------------------------------------------------------------分割线------------------------------------------------------------
【参考资料】
Trinea
郭林
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
鸿洋
Android ViewGroup事件分发机制 源码解析(下)
工匠若水
Android触摸屏事件派发机制详解与源码分析一(View篇)
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
Android触摸屏事件派发机制详解与源码分析三(Activity篇)
AigeStudio
click相关: