View事件分发
点击事件的传递规则
点击事件首先要分析的对象就是MotionEvent
,即点击事件,其实所谓的点击事件的事件的,其实就是对MotionEvent
事件的分发过程,即当一个MotionEvent
产生之后,系统需要把这号事件传递给一个具体的View,而这个传递的过程就是View的事件分发过程。
其中,点击事件的分发过程由三个方法共同完成:dispatchTouchEvent
,onInterceptTouchEvent
,onTouchEvent
。
以下要注意消耗和拦截的区别
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分开,如果事件可以传递到当前的View中,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent
和下级View的dispatchTouchEvent
的影响,表示是否消耗当前事件。
onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent event)
在dispatchTouchEvent
方法内部调用,用于判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,这个方法不会被再次调用,返回结果表示是否拦截当前事件。
onTouchEvent
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent
方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列内,当前View无法再次接收到事件。
以下是它们之间关系的伪代码。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
上面的代码可以大概了表现出事件的传递规则:对一个根ViewGroup来说,点击事件产生后,首先会传递给它,这个时候它的dispatchTouchEvent
就会被调用,如果ViewGroup的onInterceptTouchEvent
方法返回true,那么就表示它要拦截当前事件,接着这个事件就会交给这个ViewGroup处理。即它的onTouchEvent
会被调用;而如果这个ViewGroup的onInterceptTouchEvent
方法返回false,那么就表示它不拦截当前事件,这个时候当前事件就会传递给它的子元素,接着子元素的dispatchTouchEvent
就会被调用,如此反复直至事件被最终处理。
onTouchListener与onClickListener
当一个View需要处理事件时,如果它设置了OnTouchListener
,那么OnTouchListener
中的onTouch
方法会被回调。这时候事件要根据onTouch
方法的返回值进行处理,如果onTouch
返回true,那么onTouchEvent
方法将不会被调用;如果onTouch
返回false,那么onTouchEvent
将会被调用。由此可见,给View设置的OnTouchListener
,优先级比onTouchEvent
要高。而在onTouchEvent
方法中,如果当前设置的有onClickListener
,那么它的onClick
方法会被调用,由此可见,onClickListener
优先级最低。
点击事件的传递顺序
当一个点击事件产生之后,它的传递过程遵循如下顺序:Activity
->Window
->View
。事件总是先传递给Activity
,Activity
再传递给Window
,最后Window
再传递给DecorView
。当DecorView
接收到事件后,就会按照事件分发机制去处理。
考虑一种情况,如果一个View的onTouchEvent
返回了false,那么它的父容器的onTouchEvent
将会被调用。以此类推,假如所有的元素都不处理这个事件,那么这个事件最终会交给Activity处理。
事件传递的规则
对于事件传递的机制,以下是其一些规则,根据规则可以更清楚地了解整个流程。
- 同一个事件序列是指从手指从触摸屏幕的那一刻起,到手指离开手机的那一刻结束,在这个过程中产生的一系列事件,这个事件以
down
事件开始,中间含有数量不定的move
事件,最终以up
事件结束。 - 正常情况下,一个事件序列只能被一个View拦截并消耗,这条原因可以参照3,因为一旦一个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列内的事件由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该直接自己处理的事件强行传递给其他View。
- 当某个View一旦决定拦截,那么这一个事件序列都只能由它处理(如果事件序列能够传递给它的话),并且它的
onInterceptTouchEvent
不会被调用。也就是说,当View决定拦截一个事件之后,系统会把同一个事件序列内的其他方法都直接交给它处理,因此就不会再调用这个View的onInterceptTouchEvent
去询问它是否拦截。 - 某个View一旦开始处理事件,如果它不消耗
ACTION_DOWN
事件,即onTouchEvent
返回了false,那么同一事件序列的其他事件都不会交由它处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent
会被调用。即事件一旦交给一个View处理,那么它必须消耗掉,否则同一事件的剩下的事件就不再交由它处理了。 - 如果View不消耗除
ACTION_DOWN
以外的事件,那么这个点击事件会消失,此时父元素的onTouchEvent
就不会被调用,并且当前View可以持续收到后续的事件,最后这些消失的事件传递给Activity
处理。 -
ViewGroup
默认不拦截任何事件,Android源码中的ViewGroup
的onInterceptTouchEvent
默认返回false。 -
View没有
onInterceptTouchEvent
方法,一旦有点击事件传递给它,那么它的onTouchEvent
将会被调用。 - View的
onTouchEvent
默认都会消耗拦截事件(返回true),除非它是不可点击的(即clickable
和longClickable
属性同时为false)。View的longClickable
属性默认都为false,View的clickable
属性要区分情况,比如Button
的为true,TextView
的为false。 - View的
enable
属性不影响onTouchEvent
的默认返回值。只要View的clickable
或者longClickable
有一个为true,那么它的onTouchEvent
返回值就是true。 -
onClick
发生的前提是当前View是可点击的,并且它收到了down
和up
事件。 - 事件的传递过程是由外向内的,事件总是先传递给父元素,然后再由父元素传递给子View,通过
requestDisallowInterceptTouchEvent
方法可以在子元素中干预父元素的分发过程,但是ACTION_DOWN
事件除外。
网友评论