当一个触摸事件产生后,它的传递过程遵循如下顺序:Activity→Window→View。
所以Activity的rootView收到一个触摸事件后,就会按照控件树的触摸事件派发流程,而事件的派发流程由ViewGroup(View)的dispatchTouchEvent(MotionEvent ev),其实dispatchTouchEvent方法在执行事件派发前,还控制确定派发目标的逻辑,在这里就略过确定派发目标的分析了,直接来分析事件派发:
这里只说单点触摸的情况而略过多点触摸的情况,那么多于单点触摸就只有一个完整的事件序列中,就只有一个子序列(多点触摸有多个子序列),那么事件序列的开始就是手指按下(触发ACTION_DOMN),结束就是手指抬起(触发ACTION_UP)。
派发目标:即在ACTION_DOWN事件,被确定为该事件序列的接受目标View。只有在ACTION_DOWN时,才回去寻找派发目标,在该序列接下来的事件中该目标View的父控件将事件直接传给该目标的dispatchTouchEvent方法。
1.只有消耗了Down的child才会成为ViewGroup的派发目标,即在child.dispatchTouchEvent方法中返回了true。
2.一个事件序列只有一个派发目标(再次强调只针对单点触摸)。
3.派发目标的dispatchTouchEvent方法即使返回了一个true,父控件及父控件的父控件都不会再进行处理的了。最后只有Activity的的onTouchEvent方法会处理这个没人处理的事件。
4.除非父控件被设置requestDisallowInterceptTouchEvent(true);即不可拦截事件标志,否则父控件可拦截经过它的任何事件,只要在onIntercept方法中返回true,即可设置ViewGroup的Intercepted变量为true,此时父控件下的作为其派发目标的child依然会受到此事件(是收到被拦截的事件,而不是之后的事件序列),该事件的action被改成了ACTION_CANCEL,该目标View收到action为ACTION_CANCEL的事件后,该序列接下来的事件它都接收不到,因为它已经从父控件的派发目标列表中移除了。该序列接下来的事件都由父控件处理了。
5.控件disallowIntercept标志可以干扰该控件对事件的截取,该标志被置为true时,该控件无法调用其onIntercept()方法进行对事件的拦截。但是在ACTION_DOWN事件时除外,就是说当该事件的action为ACTION_DOWN的时候,无论disallowIntercept是否为true,该控件都可以截取,只要DOWN事件经过该控件。ViewGroup截取了DOWN事件,则其child无论如何都没有机会去处理这些事件序列。
6.VIewGrou处理事件的时候,调用super.dispatchTouchEvent(即View#dispatchTouchEvent,这应该与ViewGroup#dispatchTouchEvent区分开),在View#dispatchTouchEvent方法中,处理流程是:若设置了OnToucListener,则调用OnTouchListener.onTouch(MotinoEvent ev)(每个控件只有一个OnTouchListener),若OnTouchListener.onTouch返回false,则调用View#onTouchEvent方法,在这方法中判断各种中View的属性,如clickable,longClickable,若clickable或longClickable为true,且设置onClickListener则或调用onClickListener.onClick(注意:此方法没有返回值,OnLongClickListener有返回值)。当clickable或longClickable为true,View#onTouchEvent一定返回true,否则返回若没有任何监听器则返回false。
7.OnClickListener和OnLongClickListener一起使用会有些细节需要注意,这里先不说下次再补上。
8.View的longClickable属性默认为false,clickable属性要分情况,比如Button的clickable默认为true,TextView的默认为false。
9.View的enable属性不影响onTouchEvent的默认返回值,哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,则onTouchEvent就返回true。