Touch事件处理和传递
-
事件分类:
ACTION_DOWN, ACTION_UP, ACTION_MOVE:按下、离开、移动
ACTION_POINTER_DOWN, ACTION_POINTER_UP:多点按下、离开
ACTION_CANCEL:当控件收到前驱事件(前驱事件:一个从DOWN一直到UP的所有事件组合称为完整的手势,中间的任意一次事件对于下一个事件而言就是它的前驱事件)之后,后面的事件如果被父控件拦截,那么当前控件就会收到一个CANCEL事件,并且把这个事件会传递给它的子View,没有子View就调用父容器的dispatchTouchEvent()。
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }
ViewGroup的dispatchTransformedTouchEvent方法对ACTION_CANCEL的处理
-
事件处理方法:
-
传递—-dispatchTouchEvent():
只要事件传递到了当前View,那么dispatchTouchEcent方法就一定会被调用。返回结果表示是否消耗当前事件。
-
拦截——onInterceptTouchEvent():
在dispatchTouchEcent方法内部调用此方法,用来判断是否拦截某个事件。如果当前View拦截了某个事件,那 么在这同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。
-
消费—-onTouchEvent()函数和OnTouchListener():
在dispatchTouchEcent方法内调用此方法,用来处理事件。返回结果表示是否处理当前事件,如果不处理,那么在同一个事件序列里面,当前View无法再收到后续的事件。
-
-
Android事件传递结论:
- 事件都是从Activity.dispatchTouchEvent()开始传递
- 事件由父View传递给子View,ViewGroup可以通过onInterceptTouchEvent()方法对事件拦截,停止其向子view传递
- 某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。
- 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来,也就是说ACTION_DOWN必须返回true,之后的事件才会传递进来
- 如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。
- ViewGroup的onInterceptToucheEvent默认返回false,也就是默认不拦截事件。
- View没有InterceptTouchEvent方法,如果有事件传过来,就会直接调用onTouchEvent方法。
- View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要他是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。
- 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
- OnTouchListener优先于onTouchEvent()对事件进行消费
-
View拦截处理事件的流程,伪代码表示:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consum = false;
if(onInterceptTouchEvent(ev)){ //判断是否拦截
consum = onTouchEvent(ev); //处理事件
}else{ //不拦截,则交给子View的dispatchTouchEvent()方法处理,
consum = child.dispatchTouchEvent(ev);
}
return consum;
}
滑动冲突解决
外部拦截法:
-
事件都经过父容器的拦截处理,如果父容器需要则拦截,如果不需要则不拦截。
-
改写父容器的onInterceptTouchEvent(MotionEvent event)方法,确定是否拦截事件:
-
ACTION_DOWN事件返回false,使事件可以传递给子View。
-
ACTION_MOVE事件判断拦截时返回true,不拦截返回false。
-
ACTION_UP事件返回false,否则当子View处理时,无法处理click事件。
-
-
Example:
public boolean onInterceptTouchEvent(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;
}
return intercepted;
}
内部处理法:
-
父容器不拦截任何事件,将所有事件传递给子元素,如果子元素需要则消耗掉,如果不需要则通过requestDisallowInterceptTouchEvent方法交给父容器处理。
-
重写父容器的onInterceptTouchEvent方法
-
重写子View的dispatchTouchEvent(MotionEvent event)方法
-
Example:
//重写父容器的onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {//不处理DOWN事件,全部交给子View判断
return false;
} else {
return true;
}
}
//重写子View的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//父Viewgroup不处理onInterceptTouchEvent回调
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
//需要父Viewgroup处理onInterceptTouchEvent回调,拦截事件
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
网友评论