面试的时候,很多时候都会问到Touch事件的传递,而且问法角度都有所不同,但是还是会遵循基本的事件传递规则的,可能他问的你没处理过,但是根据基本规则慢慢思考来回答,都不会错。
首先我们知道touch事件 主要是是在三个方法中传递和处理的。分别是:
其中事件拦截的方法只在VeiwGroup中有,Activity和View中都没有,而事件的传递遵循从上到下分发->拦截。而处理则是从内到外的反馈过程,然后在从外到内的分发完成,最后在处理层调用onTouchEvent的ACTION_UP至此,事件的分发和处理走完。这里大家先有一个印象,后面还会详细讲解。
如上图页面所示:主要包含TouchNormalActivity,注意这个activity要继承系统自带AppCompatActivity或者其他,不要继承自己封装的Activity,有可能你封装的时候做了处理,自己不知道,会出现事件传递不准确的情况。
页面包含三层:TouchNormalActivity->NormalViewGroup->NormalView
下面事件处理只有DOWN事件的日志
三层都不处理,按照系统默认处理来进行
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[onTouchEvent, 56]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
TouchNormalActivity[onTouchEvent, 63]: ACTION_UP
我们可以看到遵循我们在简介中说的情况。
这里主要指在最内层处理了点击事件可以是该View实现了OnClickListener接口,也可以是在onTouchEvent里面返回了true。
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
NormalView[onTouchEvent, 96]: ACTION_UP
从日志我们可以看到,当内层View消费掉点击事件的时候,就不会在调用super.onTouchEvent(event);去询问父View是否要处理该事件。跟1)相比少了一个,从内到外反馈询问是否处理点击事件的过程。
因为内层View已经处理了点击事件,这个时候我们可以认为本地点击事件已经分发完毕,从日志我们可以清楚的看到有一个从外到内通知事件分发完毕的过程,一直到处理事件层的dispatchTouchEvent.ACTION_UP。表示事件分发到这一层完毕,然后才会去执行onTouchEvent的UP事件,至此表示该事件处理完毕。
首先我们必须明白的一件事是,事件的分发是从外到内的过程,但是事件的处理是从内到外的过程,手指点击到屏幕,虽然是在最外层,但是事件处理的时候。要优先询问最内层的是否处理,然后在往外层一层一层的询问,这样才能保证事件处理不会乱。
这里再次重复说这个,是因为想让大家看清楚 view 层 和 viewgroup层处理日志的不同
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onTouchEvent, 95]: ACTION_UP
正如上面说的,虽然我们是在ViewGroup消费掉了点击事件,但是发现View层也是可以接收到ACTION_DOWN 然后再往外反馈的。
经过了Viewgroup层的讲述,相信activity层大家已经能猜到了
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[onTouchEvent, 56]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
TouchNormalActivity[onTouchEvent, 63]: ACTION_UP
我们会发现 这个日志和三层都不处理的日志是一样的。因为已经到了最上层,你不处理谁处理。所以日志一样。
我们发现MOVE事件的前期过程和DOWN事件一样,只不过在down到事件消费层的时候。开始从外网内再次分发了一个MOVE事件。然后在UP的时候 和 DOWN是一样的。
三层都不处理,按照系统默认处理来进行
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[onTouchEvent, 56]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 33]: ACTION_MOVE
TouchNormalActivity[onTouchEvent, 60]: ACTION_MOVE
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
TouchNormalActivity[onTouchEvent, 63]: ACTION_UP
我们能够看到。MOVE事件,是在ACTION_DOWN事件传递和处理完毕以后才进行MOVE处理的。这个时候已经能够确定那一层来消费touchEvent事件了。所以就再次来分发事件,因为已经能确定在Activity层处理事件了。就不会往内层分发,直接分发完,在该层的onTouchEvent中进行处理了。
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 33]: ACTION_MOVE
NormalViewGroup[dispatchTouchEvent, 39]: ACTION_MOVE
NormalViewGroup[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalView[dispatchTouchEvent, 67]: ACTION_MOVE
NormalView[onTouchEvent, 93]: ACTION_MOVE
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
NormalView[onTouchEvent, 96]: ACTION_UP
我们发现这个过程其实和Down是一样的,只不过多了一个Move的一个分发和处理
这个拦截没有细分,直接在onInterceptTouchEvent中返回true了。
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[onTouchEvent, 56]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
TouchNormalActivity[onTouchEvent, 63]: ACTION_UP
因为ViewGroup做了拦截,在内层View中根本就接受不到事件。
这个时候想一下MOVE事件的情况,因为ViewGroup虽然拦截了,但是并没有处理事件,所以还需要网上反馈,最终在最上层做处理。所以此时的MOVE只会在最上层(activity)的dispatchTouchEvent和onTouchEvent中执行。
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalViewGroup[onTouchEvent, 88]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onTouchEvent, 95]: ACTION_UP
我们能看到。拦截只是不往内层传递,其他的都还是一样的。
但是区别在于MOVE事件。首先拦截以后 就会直接走当前层的onTouchEvent方法。如果我们不处理,就会向上反馈,MOVE的时候就会在上层MOVE
如果在ViewGroup中消费点击事件,那么在MOVE的时候是在ViewGroup中MOVE,分发完DOWN事件以后再次有外网内进行分发MOVE事件。
首先我们必须知道一点,MOVE事件是,你那一层要消费那一次才能得到MOVE的分发。如果我们都没有去处理DOWN事件,那么我们在MOVE的时候,ViewGroup和View是得不到MOVE事件的。
因此,我们在ViewGroup做MOVE拦截,就必须保障ViewGroup的内层View要消费点击事件。
因为我们只拦截了MOVE事件,所以DOWN事件的日志和DOWN三层都不处理的日志是一样的。
MOVE日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 33]: ACTION_MOVE
NormalViewGroup[dispatchTouchEvent, 39]: ACTION_MOVE
NormalViewGroup[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalView[dispatchTouchEvent, 74]: ACTION_CANCEL
NormalView[onTouchEvent, 100]: ACTION_CANCEL
TouchNormalActivity[dispatchTouchEvent, 33]: ACTION_MOVE
NormalViewGroup[dispatchTouchEvent, 39]: ACTION_MOVE
NormalViewGroup[onTouchEvent, 92]: ACTION_MOVE
TouchNormalActivity[onTouchEvent, 60]: ACTION_MOVE
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onTouchEvent, 95]: ACTION_UP
TouchNormalActivity[onTouchEvent, 63]: ACTION_UP
从日志中我们可以看到。主要分为四段,第一段是Down事件的处理,因为拦截的是move所以,down还是能进入到最底层View并且消费掉事件,然后开始进行MOVE,因为最底层View消费掉事件,道理上MOVE应该是可以分发到View层的,但是因为在上层的Viewgroup做了拦截,我们能够看到在View层的dispatchTouchEvent和onTouchEvent给到的状态是ACTION_CANCEL就是取消当前View事件的处理,然后再次重新分发MOVE事件。同事因为做了拦截,所以onInterceptTouchEvent不执行。
本来觉得UP事件拦截有什么好做的,没什么用。后来试了下,发现也有点意思。
我们在View层 添加了onClick方法。并在Viewgroup做UP拦截。
不拦截日志如下:
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
NormalView[onTouchEvent, 96]: ACTION_UP
NormalView$1[onClick, 47]: onClick: ======
这是一个正常在View消费事件的日志,并且答应了onClick日志。
拦截日志:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 74]: ACTION_CANCEL
NormalView[onTouchEvent, 100]: ACTION_CANCEL
从日志我们可以看到,在执行到ViewGroup的onInterceptTouchEvent的UP事件以后,后面的变成ACTION_CANCEL事件了。
因为我们做了分发的拦截,系统可以认为该事件不需要在往下分发了。或者认为分发失败。然后去调用CANCEL来通知下层。该事件重置或者取消了。
在View层添加了onClick事件,但是在View层的dispatchTouchEvent.ACTION_DOWN返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
NormalView[onTouchEvent, 96]: ACTION_UP
onClick没有执行。
在View层的dispatchTouchEvent.ACTION_DOWN返回true 以后,就没有走View的onTouchEvent了。会认为事件在分发的时候已经完成,然后从外到内走分发完成的过程。
在View层添加了onClick事件,但是在View层的dispatchTouchEvent.ACTION_UP返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
跟我想象中的还是有点出入的,onClick不执行是肯定的。我以为会调用ACTION_CANCEL结果并没有。
只是比正常流程少了一步onTouchEvent.ACTION_UP而导致 onClick不执行。
在View层添加了onClick事件,但是在ViewGroup层的dispatchTouchEvent.ACTION_DOWN返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onTouchEvent, 95]: ACTION_UP
TouchNormalActivity[onTouchEvent, 64]: ACTION_UP
onClick没有执行。
在ViewGroup层的dispatchTouchEvent.ACTION_DOWN返回true 以后,就会认为在ViewGroup分发完成,然后在由外到内去调用分发的up事件。但是我们会发现一个很奇怪的现象,就是ViewGroup层的onTouchEvent.down事件因为在分发的down中被返回,所以并没有执行,但是他的up事件却执行了。并且因为Viewgroup并没有处理事件,up事件还向上反馈了。
在View层添加了onClick事件,但是在ViewGroup层的dispatchTouchEvent.ACTION_UP返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
在ViewGroup的dispatchTouchEvent.ACTION_UP以后的都不执行了。所以造成onClick也不会执行。
在View层添加了onClick事件,但是在Activity层的dispatchTouchEvent.ACTION_DOWN返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
TouchNormalActivity[onTouchEvent, 64]: ACTION_UP
onClick没有执行。
对比ViewGroup一目了然
在View层添加了onClick事件,但是在Activity层的dispatchTouchEvent.ACTION_UP返回true
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 46]: ACTION_CANCEL
NormalViewGroup[onInterceptTouchEvent, 73]: ACTION_CANCEL
NormalView[dispatchTouchEvent, 74]: ACTION_CANCEL
NormalView[onTouchEvent, 100]: ACTION_CANCEL
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
onClick不执行
同时这个日志很奇特,我们在Activity的分发UP中返回true,我以为回像Viewgroup那样down事件向下传递,然后后面的不执行分发的up,没想到打印出的日志竟然是这样的。
从日志我们可以看出来先从上到下执行了一次Cancel,然后在执行了了一遍down,然后在Activity层的up事件终止。(这里我也不是很明白,带后续研究)
总结:
在分发的down和up分别做处理,我们能看出来的是,如果在down中处理,其实就等于在上层做了拦截,但是如果实在up中做处理,事件还是能进入到最底层View中的。
还有就是在分发的时候MOVE事件的处理情况,有兴趣的同学可以看一下,记住MOVE的处理层级是在down层级的。记住这一点,就能很快知道MOVE可以深入到那一层。
给View设置 setOnTouchListener 和 setOnClickListener
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 65]: ACTION_DOWN
NormalView$2[onTouch, 55]: onTouch
NormalView[onTouchEvent, 91]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 72]: ACTION_UP
NormalView[onTouch, 55]: onTouch
NormalView[onTouchEvent, 98]: ACTION_UP
NormalView[onClick, 49]: onClick
我们能看到 onTouchListener优先于onClick执行,先于onTouchEvent执行。
setOnTouchListener 返回true的时候
日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 65]: ACTION_DOWN
NormalView[onTouch, 55]: onTouch
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 72]: ACTION_UP
NormalView[onTouch, 55]: onTouch
从日志我们看到,因为onTouch是优先于onClick执行的,当onTouch返回true的时候,事件就没有传递到View层的onTouchEvent.ACTION_DOWN中。所以onClick并没有执行。
在View添加onClick事件,日志如下:
TouchNormalActivity[dispatchTouchEvent, 29]: ACTION_DOWN
NormalViewGroup[dispatchTouchEvent, 35]: ACTION_DOWN
NormalViewGroup[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 63]: ACTION_DOWN
NormalView[onTouchEvent, 89]: ACTION_DOWN
TouchNormalActivity[dispatchTouchEvent, 37]: ACTION_UP
NormalViewGroup[dispatchTouchEvent, 42]: ACTION_UP
NormalViewGroup[onInterceptTouchEvent, 69]: ACTION_UP
NormalView[dispatchTouchEvent, 70]: ACTION_UP
NormalView[onTouchEvent, 96]: ACTION_UP
NormalView$1[onClick, 47]: onClick
我们可以看到 onClick执行是在View层的onTouchEvent.ACTION_UP以后执行的,此时如果我们在View层的 dispatchTouchEvent.ACTION_UP或者onTouchEvent.ACTION_UP直接返回true,都会导致onClick不会执行。
以上就是Touch事件的传递和拦截过程。看起来有一些枯燥,其实自己跟一遍流程,还有有些感悟和收获的。
首先我们要记住,事件的传递流程是 分发->拦截->处理。
具体过程为,我们点击一个屏幕的时候,肯定是外部最先接触到点击事件,但是为了保证内部能处理到事件,所以我们需要一个从内到外反馈的过程,因为事件是先从外到内分发和拦截down事件,然后到达最内层View的onTouchEvent处理事件,然后在从内到外来进行反馈,那一层要处理该事件。
当该层要处理down事件以后。在从外到内来分发和拦截up事件,到处理层的onTouchEvent.ACTION_UP终止。
同时呢,MOVE事件是在down的处理层进行的。先进行down处理,进入到处理层级,然后从外到内传递MOVE。如果内层都不做处理,那么就只有在外层的分发和处理里面才有MOVE回调。
项目源码:
https://github.com/wangxp423/ViewExercise
在首页的Touch事件研究中。