本文将介绍View的核心知识:事件分发机制
分析事件分发机制,实际上就是分析MotionEvent,即点击事件。
当一个MotionEvent产生之后,系统需要把这个事件传递给一个View,传递的过程就是 分发过程。
这涉及到三个核心方法
public void dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发,如果事件能够传递给当前View,那么此方法 一定会被调用。
返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public void onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent()方法的内部进行调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用。
返回结果表示是否拦截当前事件。
public void onTouchEvent(MotionEvent ev)
在dispatchTouchEvent()方法中调用,用来处理点击事件。
返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
1、点击事件的传递规则,可以大致这样理解:
对于一个根ViewGroup,当点击事件发生以后,首先传递给它,这时,首先调用dispatchTouchEvent()方法。
此时,如果onInterceptTouchEvent()返回true,表示它要拦截当前事件(即要对当前事件进行处理),此时它的onTouchEvent()方法就会被调用
如果onInterceptTouchEvent()返回false,表示不拦截当前事件,那么当前事件就会传递给它的子元素,子元素的dispatchTouchEvent()方法就会被调用。
如此反复,直到事件被最终处理。
以下伪代码展示了这个过程
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
//假如要拦截当前事件
if (onInterceptTouchEvent(ev)){
onTouchEvent(MotionEvent ev);
}else {
//不拦截当前事件
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
2、事件的传递过程:
当一个点击事件产生后,它的传递过程为:Activity->Window->View,
点击事件总是先传递给Activity,再传递给Window,最终传递给顶级View。
顶级View接收到事件后,按照事件分发机制去分发事件。
假如一个View的onTouchEvent()方法返回false,表示它不处理这个事件,那么其父容器的onTouchEvent()方法将被调用。
若所有元素都不处理事件,最终事件会传递给Activity处理
3、事件传递的优先级问题:
一个View要处理事件,假如我们给自定义View设置onTouchListener,如下
public class MyListentr implements OnTouchListener{
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}
那么其onTouch()方法会被调用,可以看到,默认返回false。
假如返回false,那么当前View的onTouchEvent()方法会被调用;
如果返回true,那么onTouchEvent()方法不会被调用,也就是说,我们在onTouchEvent()中写的处理逻辑将失效。
可见,onTouchListener()比onTouchEvent()优先级要高。
此外,onClickListener()方法优先级最低。
4、关于事件分发机制的一些概念和结论:
(1)同一个事件序列,指的是,在手指触摸屏幕起,到手指离开屏幕结束的过程,在此期间产生的一些列事件,以down事件开始,中间包含数量不等的move事件,up事件作为结束。
(2)一个事件序列只能被一个View拦截,消耗。因为一个元素一旦拦截了某个事件,改事件序列内的所有事件都会交给它处理。但通过特殊手段,比如,在进行拦截的View的onTouchEvent()方法中强行传递给其他View进行处理,可以实现一个以上的View共同处理事件。
(3)一个View决定进行拦截某事件后,它的onInterceptTouchEvent()不会再被调用(即只会在刚开始决定是否拦截时调用一次),因为同一序列的其它方法都会交给它进行处理,不用再进行判断。
(4)一个View开始处理事件,如果它不消耗down事件,即onTouchEvent返回了false,那么同一事件序列的其他事件都不会再交给它处理,而重新交给其父元素进行处理。
(5)若View不消耗除down事件意外的其他事件,那么这个点击事件将消失,但父元素的onTouchEvent()不会被调用,当前View可以接收到后续事件,而消失的点击事件将被传递给Activity处理
(6)ViewGroup默认不处理任何事件,源码中ViewGroup的onInterceptTouchEvent()默认返回false
(7)View没有onInterceptTouchEvent()方法,一旦有事件传递给它,它的onTouchEvent方法就会被调用