方法:
- dispatchTouchEvent:分发事件。如果返回true,表示事件分发下去后被处理了;如果返回false,则表示分发下去后没有被任何View处理
- onInterceptTouchEvent 拦截事件。如果返回true,表示拦截事件;如果返回false,则表示不拦截。这里拦截的是本来要传给子控件的事件,所以这个方法是ViewGroup独有的。
- onTouchEvent 处理事件。如果返回true,则表示处理事件;如果返回false,则表示不处理事件。
关于事件传递机制的一些结论:
- activity没有onInterceptTouchEvent方法的,因为是事件源,拦截没有意义
- view没有onInterceptTouchEvent方法,因为是叶子节点,拦截没有意义
- viewgroup中onInterceptTouchEvent方法默认返回false(不拦截),除非重写该方法并返回true;
//ViewGroup.java
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
- view的ontouchEvent默认都会消费事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false(true表示消费该事件,false表示不消费该事件)
- 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子元素,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
ViewGroup的dispatchTouchEvent方法的伪代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){//是否拦截,拦截就走当前控件的onTouchEvent方法
consume = onTouchEvent(ev);
}else{//不拦截,调用子控件分发方法;以此类推,直到事件被消费
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
事件传递方向:由外到内,再由内到外,直到事件被消费
分发流程:
- 首先viewgroup在分发时候首先判断自己是否需要此事件,如果需要就在onInterceptTouchEvent返回true,对事件进行拦截,并调用当前viewgroup的onTouchEvent方法;
- 如果不如需要此事件,就会调用子控件的分发,依次类推直到事件被消费;
- 如果当前控件的onTouchEvent返回false,表示不消费当前事件,紧接着会调用父控件的onTouchEvent;
- 如果当前控件的onTouchEvent返回true,表示消费当前事件,该事件终止
事件冲突解决: - 外部拦截法(父控件根据业务逻辑处理,子view需要事件的时候就不拦截,交给子view处理)
//父控件控制逻辑的伪代码如下
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;//默认不拦截
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if(当前容器需要这个点击事件){
intercepted = true;
}else{//当前容器不需要这个点击事件
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
return intercepted;
}
- 内部拦截法(子控件根据业务逻辑处理,需要事件的时候请求父控件不拦截事件requestDisallowInterceptTouchEvent,不需要事件的时候请求父控件去拦截事件)
1.子控件中通过dispatchTouchEvent控制拦截逻辑
//子控件中控制逻辑的代码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);//请求父控件不拦截
break;
case MotionEvent.ACTION_MOVE:
if(父控件需要该事件){
getParent().requestDisallowInterceptTouchEvent(false);//让父控件处理该事件
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
2.父控件不能拦截ACTION_DOWN事件,因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父控件拦截了ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
参考
《android开发艺术探索》