- View的事件分发机制
- View的滑动冲突
View的事件分发机制
view的事件分发机制不仅是android中的核心知识,更是难点,掌握view的分发机制能更好的帮助我们理解怎样的自定义view,以及处理
view中的各种点击事件。研究View事件的分发,即是研究MotionEvent.当一个MotionEvent产生了以后,系统需要把这个事件传递给
一个能处理它的view,这个过程就是事件分发过程。
-点击事件的的传递
点击事件的分发过程由三个很重要的方法来完成:1.dispatchTouchEvent. 2.onInterceptTouchEvent.3onTouchEvent.
1.public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分发。如果事件能够传递给当前的view,此方法被调用,返回的结果受当前view的onTouchEvent和下级的view的
dispatchTouchEvent影响,表示是否消耗当前事件。
2.onInterceptTouchEvent(MotionEvent event)
用来判断是否拦截事件。如果当前view拦截了某个事件,那么在同一个事件序列中,此方法不会再次调用,返回的结果表示是否拦截当前事件。
3.onTouchEvent(MotionEvent event)
用来处理点击事件。在dispathTouchEvent方法中调用。返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接收到事件。
上述的三个方法的关系可如下伪代码所示:
//事件分发
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume=false;//是否消耗该事件
if(onInterceptTouchEvent(event)){//如果拦截该事件,就进行点击事件处理。
consume=onTouchEvent(event);
}else{//如果不拦截,就交由其子view处理。
consume=child.dispatchTouchEvent(event);
}
return consume;
}
根据上述伪代码,对于一个ViewGroup来说,当产生点击事件时,首先传递给它,触发ViewGroup的dispatchTouchEvent事件,如果ViewGroup的onInterceptTouchEvent方法返回true,就表示当前ViewGroup拦截了此事件,接着此事件就会交由ViewGroup的onTouchEvent方法来处理,处理的结果表示是否消费掉此事件,(如果onTouchEvent方法返回true,表示消费掉此事件,则此事件不再向下传递,如果onTouchEvent方法返回false,则此事件依然向下继续传递),如果ViewGroup的onInterceptTouchEvent方法返回false,表示它不拦截此事件,这时,当前事件就会继续传递给它的子View, 接着子View的patchTouchEvent方法就会被调用,如此反复知道事件被最终处理。
注:**1.只有ViewGroup才有onInterceptTouchEvent方法,而**View没有onInterceptTouchEvent方法,当事件传递给它时,它的onTouchEvent方法就会被调用。
2.ViewGroup默认不拦截任何事件。android源码中ViewGroup的onInterceptTouchEvent方法默认返回false;
3.View的点击事件有onTouch和onTouchEvent,如果View设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用,若onTouch方法返回false,则当前View的onTouchEvent方法才会被调用。在onTouchEvent方法中,若设置了OnClickListener,那么它的onClick方法会被调用。由此可见OnTouchListener的优先级比onTouchEvent高。
View的滑动冲突
滑动冲突多表现在内外两层都可以滑动的时候,到底什么时候内层滑动,什么时候外层滑动的问题。
针对滑动冲突,可以有两种解决方案。
1.外部拦截法
外部拦截法就是事件都经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就继续向下传递。此方法比较符合点击事件的分发机制,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。流程的伪代码如下:
public boolean onInterceptTouch(MotionEvent event){
boolean intercepted =false;
int x = (int)event.getX();
int y = (int)event.getY();
switch(event.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;
}
mLastX=x;
mLastY=y;
return intercepted;
}
上述代码为外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可,其余均不需要修改。针对ViewGroup的onInterceptTouchEvent,ACTION_DOWN事件必须返回false,即不拦截按下事件,如果一旦父容器拦截了ACTION_DOWN事件,则后续的ACTION_MOVE和ACTION_UP事件都会交由父容器来处理。ACTION_UP事件,也必须返回false,如果父容器返回了true,那么可能导致子View的onClick事件无法触发。
2.内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子view,如果子view需要就消耗此事件,如果不需要就交由父容器进行处理。需要重写子view的dispatchTouchEvent方法以及配合requestDisallowInterceptTouchEvent方法使用。方法的流程如下:
public boolean dispathTouchEvent(MotionEvent event){
int x = (int)event.getX();
int y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE:{
int deltax = x-LastX;
int deltay = y-Lasty;
if(父容器需要此点击事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP:{
break;
}
default:
break;
}
LastX=x;
LastY=y;
return super.dispatchTouchEvent(event);
}
**注:**requestDisallowInterceptTouchEvent(boolean); 子view如果不希望父容器拦截点击事件时,可以调用此方法,当requestDisallowInterceptTouchEvent方法返回true时,父容器将不拦截此事件。