一篇文章读懂android事件消费、事件分发、事件拦截
Android 源码分析事件分发机制、事件消费、事件拦截
解决SwipeRefreshLayout和ViewPager滑动冲突的三种方案
触摸反馈(事件消费)
触摸事件并不是独立的,而是成组出现的,每组事件都以按下事件ACTION_DOWN开头,以抬起事件ACTION_UP或者ACTION_CANCEL结束,中间可能会有多个ACTION_MOVE事件。ACTION_CANCEL对应的是事件组非人为的结束,比如你的手指正在一个按钮上来回移动,还没调用到ACTION_UP结束这组行为。但这时上层父view突然拦截了你的事件,这时候你就会被迫调用到ACTION_CANCEL事件提前结束你的这组行为。
在代码上的体现,当用户触摸屏幕,产生触摸操作的时候,就会调用到View的onTouchEvent方法,并会把这个事件的类型:是按下、抬起,还是移动,以及这个事件的坐标等信息作为参数传给onTouchEvent方法。当一系列事件不断产生,onTouchEvevt方法就会不断被调用。比如一个button,当用户点击它一下的时候,它的onTouchEvent方法会被调用两次,第一次传入事件DOWN表示按下,第二次传入事件UP表示抬起,中间可能会有细微的移动事件MOVE传入onTouchEvent方法,如果你想写自己的触摸反馈算法,就要重写onTouchEvent方法,并返回true就好了。这就是触摸反馈,也叫事件消费。
触摸事件分发
触摸事件分发其实就是为了解决触摸事件冲突而设置的机制。比如,现在现在我有一个可以点击的view,它上面有一个也可以点击的子view,现在我点击这个子view,是子view响应了这个触摸事件,但现在上面还有一个不可点击的子view,我点击这个view触发的却是下面父view的事件,那么Android是怎么做到这种自动分配的呢?就是靠的事件分发机制。
但用户的手指刚刚触摸到屏幕的时候,也就是一个事件组的第一个事件DOWMN事件发生的时候。android会从离用户最近的那个view开始,向下一个一个的去调用每一个view的onTouchEvent方法,如果第一个view的onTouchEvent对这个DOWN事件没有响应,就向下传递,直到遇到第一个对这个DOWN事件作出响应的View,这个向下的DOWN事件就会结束,这个时候,这个view就成了这组事件的接受者。这个down以后的事件都会发送给它,不会给它的上一个view,也不会给它下一个view,直到这组事件结束,也就是up事件,或者cancel事件出现。
那么这个“响应”,在代码上是如何体现的呢?就是重写onTouchEvent方法的时候的返回值写成true,就表示这个view消费了这个事件,更直观的讲,就是把DOWN事件以后的这组事件都交给了这个view。
现在还有一种情况,就是现在有一个列表,上面的按钮可以点击,列表也可以滑动,刚刚我们说了,android的触摸是从屏幕上层开始往下去传递的,被某个view消费之后,就不会再往下传递了。那么这种隔着一个按钮来实现的滑动是怎么实现的呢?这就是我们接下来要讲的触摸事件分发的事件拦截机制
事件拦截机制
之前我们说的触摸事件分发是从屏幕最顶部的拿个view开始向下传递的对吧,其实在这个过程之前还有一个过程。在用户触摸屏幕的时候,每一个触摸事件到达View的onTouchEvent之前,Android会从这个Activity里面最底部的那个根view,向上去一级一级的询问:你要不要拦截这组事件?拦截的意思就是事件不交给子view了,我自己来处理。具体在实现上,就是通过调用viewGroup的onInterceptTouchEvent方法来实现的。Intercept就是拦截的意思,它默认是返回false,如果它返回false,也就是不拦截,那么就会继续向上去问他的子view要不要拦截这个事件,如果整个流程都走完,全部返回false,那么就会走第二个流程onTouchEvent,从上往下。 如果中途有个view想要拦截这个事件,它就可以在onInterceptTouchEvent里返回true,那么就不会把事件再分发给他的子view了,而是直接交给他自己的onTouchEvent来处理了。并且在这之后,这个事件组的所有后续事件都会被他拦截了,并且交由它自己的onTouchEvent来处理。onInterceptTouchEvent和onTouchEvent有一点不同就是,onTouchEvent是否消费这组事件,是需要在down事件中决定的。如果你在down事件发过来的时候返回了false,你就与这组事件无缘了,没有第二次机会。而onInterceptTouchEvent是,你在整个过程中,都可以对事件流中的每个事件进行监听, 你可以选择先观望,给子view一个处理事件的机会,而一旦事件流的发展达到你的触发条件。
比如,你发现用户在滑动列表,你可以返回true,立刻实现事件流的接管。 这样就可以做到两不误,既可以把事件交给子view进行处理,也可以在你需要的时候把事件流接管过来。另外,在onInterceptTouchEvent返回true的时候,除了完成事件的接管,这个view还会做一件事,就是会对它的子view发送一个取消事件cancel,因为你在接管事件的时候,上面的子view可能还处于一个中间状态,这时候就需要通知他去取消事件,告诉他,接下来的事件你不要管了,整个事件流都交给我处理吧。
现在还有一种更复杂的情况,比如你在一个列表里长按列表里的item,上下滑动是移动列表项,你不想滑动列表,只想移动列表item项的,你要怎么办呢?这种情况下就需要请求父view不要拦截事件了, 这时候就需要另外一个方requestDisallowInterceptTouchEvent(true);这个方法不是拿来重写的,而是拿来调用的,在事件过程中,你在子view里面调用父view的这个方法并且传入值是true, 父view就不会在这个事件流中用onInterceptTouchEvent来进行拦截了。并且它是一个递归返回,它会阻止每一级父view的拦截。不过仅限制于这个事件流,在这个事件流结束之后,一切恢复正常。 如果你想再下个事件组中再用, 就再调用一次这个方法,因为这个方法不是为了让子view获取至高无上的能力,而是为了实际的业务需求。
dispatchTouchEvent
最后再说一下dispatchTouchEvent方法,它是事件分发的总调度方法,刚才我们讲的onInterceptTouchEvent和onTouchEvent都是在 dispatchTouchEvent方法发生的,一个事件分发的过程,实际上就是从根view递归的调用了一次dispatchTouchEvent的过程。