了解Android Touch事件传递机制
MotionEvent
每一次用户Touch事件都会被包装为一个MotionEvent
对象,对象中包含关于这个事件你想要的全部信息,包括事件所产生的动作(ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等)
、事件产生的位置坐标、事件发生时屏幕上手指的数量和时间的发生时间等。
在Android中,一个手势(gesture)的定义是一组开始于ACTION_DOWN
并结束于ACTION_UP
的事件集合。
Touch事件传递
对于每一个由硬件和底层框架产生的事件,这些事件会首先被发送到当前显示的Activity中,这个过程是由框架调用Activity的dispatchTouchEvent()
来实现的,不管你在程序中做了何种事件处理,Activity的dispatchTouchEvent()
都会是Event被处理的第一站。在这个方法里,Event将开始由上到下被传递,从rootView依次往下传递直到视图树的最底部,之后,事件会反过来以冒泡的方式向上传递。这里的传递机制是事件会在程序中从上往下再从下往上穿梭,直到某个View宣布其对这个事件感兴趣为止。是否感兴趣取决于View的onTouchEvent()
方法是否返回了true。
当尝试做自定义事件处理时,ACTION_DOWN
事件需要被关注的,如果你对ACTION_DOWN
后续发生的事件感兴趣,就需要在onTouchEvent()
中返回true。Android系统认为如果你对手势中的ACTION_DOWN
事件不感兴趣,那么你对这一手势中的其他事件亦不会感兴趣,一旦表示对ACTION_DOWN
感兴趣,那么手势中的其他事件便会以此View为直接目的地。
总结一下,事件在视图树中的传递方式就是从rootView开始自顶向下传递,根据各层是ViewGroup还是View调用各自的dispatchTouchEvent()
方法,ViewGroup会把事件递归地传递给它的child。接下来,事件从下往上传递时,会调用ViewGroup和View的onTouchEvent()
。所以,Activity中的onTouchEvent()
是最后被调用到的,如果已经有子View的onTouchEvent()
返回了true,那么Activity的onTouchEvent()
则根本不会被调用。另外我们可以使用TouchListener
来处理触摸事件,并返回true表示已经消费了该事件。
下面我们来梳理各个组件在事件传递流程中所承担的工作。
Activity
dispatchTouchEvent()
该方法总是在事件发生时最先被调用到;
执行后会将Event发送给Window,再由Window发送给DecorView,最后传递到用户自定义的RootView(通常是一个ViewGroup)并触发RootView的dispatchTouchEvent()
。
onTouchEvent()
在整个视图树中没有View消费了事件的时候被调用;
事件传递的终点,总是最后被调用(如果可能)。
View
dispatchTouchEvent()
如果存在TouchListener
,则将Event发送给执行listener.onTouch()
;
如果不存在TouchListener
或listener.onTouch()
没有返回true,则自己处理该事件,即调用自身的onTouchEvent()
。
onTouchEvent()
如果返回false,则将Event向上冒泡,并且该View不再能接受当前手势中的后续事件。
ViewGroup
dispatchTouchEvent()
对childView进行遍历和迭代,以确定哪些child可能对事件感兴趣(根据当前触摸位置判断),如果触摸位置位于多个child边界范围内,则按照被加入到ViewGroup的顺序的逆序遍历这些child,让child有处理事件的机会。
对事件进行中断或窃取,通过onInterceptTouchEvent()
onInterceptTouchEvent()
此方法在不断监控触摸事件,在需要满足该ViewGroup特殊需求的时候中断将事件分发给原本要消费事件的childView,转而让自己来处理这些事件,通常在ViewGroup自身要根据手势进行滚动等操作的时候调用此方法,调用此方法可能会向childView传递ACTION_CANCEL
事件。
requestDisallowInterceptTouchEvent()
由父视图调用,用来打断onInterceptTouchEvent()
的逻辑。通常,事件都是由ViewGroup先监测,再决定是否分发到childView中,同时也由ViewGroup决定是否屏蔽其childView的事件。但是,当childView需要决定父视图是否可以屏蔽触摸事件(不管是暂时还是永久)时,就需要调用此方法。如果传入true,则剥夺父视图中断此事件的能力。当ACTION_UP
事件发生时,前一次调用requestDisallowInterceptTouchEvent()
设置的状态会失效。通常,在ViewPager的页面中嵌入可以横向滑动的View时你会需要调用此方法来确保滑动View时不会引起ViewPager的页面切换。
Attention
除非真的有必要,否则尽量不要重写dispatchTouchEvent()
方法,如果进行了重写,则必须调用super.dispatchTouchEvent()
方法,否则事件分发过程会在此中断。
当dispatchTouchEvent
返回true时,表示在此时Event已经被处理,事件传递到此为止,返回false时,如果事件来自于Activity,则将事件交给Activity的onTouchEvent()
处理,如果来自于父View,则将事件交给父View的onTouchEvent()
处理
一旦View的onTouchEvent
在处理手势中的某一Event时返回了false,则该手势中的后续事件不会再到达该View