了解事件分发机制之前,需要对view的点击MotionEvent进行学习。事件分发的就是各种点击事件,即产生了一个MotionEvent事件后,我们要将它传递给需要处理该事件的view。
MotionEvent
手指解除屏幕后所产生的一系列事件。我们经常使用的事件包括:
- ACTION_DOWN
手指刚接触屏幕 - ACTION_MOVE
手指在屏幕上移动 - ACTION_UP
手指从屏幕上松开的一瞬间
一次手指触摸屏幕的行为会触发一系列点击事件
- 点击屏幕后松开
DOWN-->UP - 点击屏幕,滑动后松开
DOWN-->MOVE-->..MOVE-->UP
通过MotionEvent对象,我们可以获取当前事件的X,Y坐标,getX() getY()
是获取相对于当前View左上角的坐标,getRawX() getRawY()
返回相对于屏幕左上角的坐标。
点击事件传递规则
当产生点击事件,会由以下三个方法来进行分发
- public boolean dispatchTouchEvent(MotionEvetn event)
用来进行事件分发,如果事件能够传递到当前View,则此方法一定会被调用 - public boolean onInterceptTouchEvent(MotionEvent event)
用来判断是否拦截某个事件,当前View拦截某个事件,则在同一个事件序列当中,此方法不会再被调用 - public boolean onTouchEvent(MotionEvent event)
处理点击事件,返回false交由父控件处理
事件分发传递过程是由外向内的,也就是说当一个事件发生时,总是先传递给父元素,然后由父元素分发给子view
伪代码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if ( onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event)
}
return consume;
}
当产生一个事件,首先会传递给根ViewGroup,它的dispatchTouchEvent就会被调用,如果它的onInterceptTouchEvent返回true,则表示该ViewGroup拦截此事件,它的onTouchEvent就会被调用,否则该事件传递给它的子view,子view的dispatchTouchEvent被调用,继续分发事件,知道该事件被处理。
如果一个view设置了onTouchListener,那么onTouchListener中的onTouch会被回调。若onTouch返回false,当前view的onTouchEvent会被调用,否则,onTouchEvent则不会被调用。view设置onTouchListener比onTouchEvent优先级高。如果设置了onClickListener,onClick方法会被调用,但是优先级最低,处于事件传递的尾端。
当一个事件发生,传递顺序为:Activity--Window--View。如果传递过程中没有任何一个view处理事件,即onTouchEvent返回false,则交由最终的父控件Activity处理,即Activity的onTouchEvent会被调用。我们可以这样理解,就是上层领导交代了一件事给下级部门经理处理,部门经理由交给了普通员工,若是普通员工说这事我搞不定,那只能部门经理处理,若是部门经理也搞不定,那只有上层领导来出面解决。
注意
- view一旦决定拦截,那么这个事件序列都由它处理,且onInterceptEvent不在被调用。
- view一旦处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent中返回false),那么同一事件序列的后续事件也不会交由它处理。事件将交由父控件处理,这也和前文说明的一样,onTouchEvent返回false,由父控件处理。一旦事件由view处理,则它必须消耗这个事件,即onTouchEvent中返回true。
- 如果view不消耗任何事件(除ACTION_DOWN),则这个事件序列会消失,且父元素的onTouchEvent也不会被调用,当前view可以持续受到后续事件,消失的事件最终交由Activity处理。