了解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发送给RootView(通常是一个ViewGroup)并触发RootView的dispatchTouchEvent()

onTouchEvent()

在整个视图树中没有View消费了事件的时候被调用;
事件传递的终点,总是最后被调用(如果可能)。

View

dispatchTouchEvent()

如果存在TouchListener,则将Event发送给执行listener.onTouch()
如果不存在TouchListenerlistener.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