view分发事件之前:
整个事件分发从activity->window->顶级view ->子view逐层向里传递事件,顶级view接收事件后按照相应的分发机制分发事件,期间如果一个view的onTouchEvent返回false表示他不处理这个事件,那么就会调用父容器的onTouchEvent()来处理这个事件,如果所有view都不处理,那么将返回给activity调用onTouchEvent处理。
过程简单来说就是受到事件后一层一层向内(子view)分发,默认不拦截。分发到最顶层view后开始处理,如果不处理则一层一层向外(父view)回传这个事件,直到将此事件消耗。若无人消耗则最中调用activity的处理方法。
view分发事件中:
viewGroup主要围绕三个方法进行:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
viewGroup的dispatchTouchEvent方法比较复杂,其中首先判断是否拦截这个事件的流程:
当viewGroup不拦截事件的时候,将事件分发给他的子view判断是否拦截(默认不拦截),直到最后一层子view开始处理,子view若不处理,会逐层返回上层处理:
遍历viewGroup中所有的子view,逐个判断其是否可以接受点击事件,主要通过两点进行衡量:
-
子元素是否在播动画
-
点击事件坐标是否在子元素区域内
满足以上这两个条件则会交由这个view处理事件,调用dispatchTransformedTouchEvent(),该方法内当参数child不为空时,调用子view的dispatchTouchEvent处理事件,当参数child为空时则调用父view的dispatchTouchEvent来处理事件。
当找到符合条件处理的view之后,通过addTouchTarget方法给mFirstTouchTarget赋值并跳出循环(mFirstTouchTarget是单列表结构???)。根据上图可知mFirstTouchTarget是否为空直接影响整个viewGroup对事件拦截的策略。
如果遍历到最后事件都没有被合理的处理,包含两种情况,一是没有或没有合适的子view,二是子view处理了但是在dispatchTouchEvent中返回了false,一般是子view在onTouchEvent中返回了false,viewGruop就会自己处理点击事件(调用dispatchTransformedTouchEvent()方法,参数child为null,即调用super.dispatchTouchEvent()处理事件)
在view的dispatchTouchEvent中:
view(不包括viewGroup)中不含子view,所以事件只能是自己处理,不处理则返回上级处理。
主要流程为:
只要view的CLIKCKABLE和LONG_CLICKABLE有一个为true,那么这个view就会消耗这个事件,即onTouchEvent方法返回true,不管是否是DISABLE状态。
当ACTION_UP事件发生时会调用performClick方法,如果View设置了onClickListener,那么performClick内部会调用onClick方法。
所以优先级排序为:onTouchListener>onTouchEvent>onClickListener
一些结论:
-
正常情况下,一个事件序列只能被一个view拦截并消耗,view一旦开始拦截,那么整个事件序列只能都由他处理(在能够传递给他的前提下),因此就不会再调用这个view 的onInterceptEvent()调用方法来询问是否需要拦截他
-
某个view一旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其他事件都不会交由他处理了,这些事件将重新交给他的父view来处理(调用其onTouchEvent)。
-
如果view拦截不消耗除了ACTION_DOW以外的事件,那么这整个点击事件将会消失(父view的onTouchEvent也不会被调用)。当前view可收到后续事件,但最终这些事件将交由Activity处理。
-
view(只是view,不是viewGroup)没有onInterceptEvent方法,一旦有点击事件传给他,他的onTouchEvent就会被调用。
解决滑动冲突:
-
外部拦截法
重写父view的onIntercepterTouchEvent
当事件是ACTION_MOVE时进行判断,如果是父view需要的点击事件则intercepted = true,其他情况和事件均为false
-
内部拦截法
重写子view的dispatchTouchEvent和父view的onIntercepterTouchEvent
dispatchTouchEvent:ACTION_DOWN时调用requestDisallowInterceptTouchEvent(false),ACTION_MOVE时如果是父容器需要的事件则调用requestDisallowInterceptTouchEvent(true),最后return super.dispatchTouchEvent
onIntercepterTouchEvent:ACTION_DOWN时不拦截,其他全拦截