Android点击事件和滑动冲突解决
-
点击事件的主要流程
首先点击事件主要的就是分发、拦截和消费者三个方法
image.png
然后我们看一流程图
image.png
对于一个根ViewGroup来说,发生点击事件首先调用dispatchTouchEvent
如果这个ViewGroup的onIterceptTouchEvent返回true就表示它要拦截当前事件,接着这个ViewGroup的onTouchEvent就会被调用.如果onIterceptTouchEvent返回false,那么就会继续向下调用子View的dispatchTouchEvent方法,其实整体来说就是个责任链的模式,一层一层的进行分发,当某一层能处理的时候,事件消费结束,当任何一层都不处理的时候,事件返回到最上层。
当一个View需要处理事件的时候,如果它没有设置onTouchListener,那么直接调用onTouchEvent.如果设置了Listenter 那么就要看Listener的onTouch方法返回值.为true就不调,为false就调onTouchEvent。onTouchListener中的onTouch方法每次都会先于view本身的onTouchEvent调用,且有优先消费权
//可以看到是需要先检测onTouchListener的返回值的
View.dispatchTouchEvent {
if( ! Listener.onTouch )
this.onTouchEvent
}
View的默认实现会在onTouchEvent里面把touch事件解析成Click之类的事件
点击事件传递顺序 Activity -> Window -> View
一旦一个元素拦截了某事件,那么一个事件序列里面后续的Move,Down事件都会交给它处理.并且它的onInterceptTouchEvent不会再调用
View的onTouchEvent默认都会消耗事件,除非它的clickable和longClickable都是false(不可点击),但是enable属性不会影响
-
为什么setOnTouchListener的优先级比setOnClickListener高
我们知道setOnTouchListener的优先级比setOnClickListener的要高,个人理解是onTouch是指的你手指触摸屏幕就会触发,这时候其实不论你手指是滑动还是点完离开,onTouch这个方法都已经触发,而onClick这个方法是指的一次完整的点击事件,至少包括按下(down)和离开(up)两部分,也就是说要手指完成点击离开以后才会触发,这就是为什么onTouch会先于onClick触发。 -
滑动事件的的冲突
一般分为内部解决和外部解决
内部是调用getParent(). requestDisallowInterceptTouchEvent(true)方法,就是强制不让父类拦截,一般是在down方法里先拦截(必须是在donw方法里,因为down方法里FLAG_DISALLOW_INTERCEPT标志位被置为false),然后在move方法判断是否满足我们内部控件的滑动需求,如果满足则继续拦截,然后不满足则返回false释放交给外部处理此事件。
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
}
在父类dispatchTouchEvent的时候,根据FLAG_DISALLOW_INTERCEPT的值不去执行onInterceptTouchEvent
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
外部调用一般是在外部的onInterceptTouchEvent的方法里,一般也是在move里判断具体的滑动是否需要拦截,如果符合我们外部控件的需求则返回true拦截,如果不符合则返回false不拦截,传递给子控件。两种方式其实原理都是一样就是在move里去根据x和y的一个滑动距离来判断具体将这次滑动事件交给谁去处理。
public boolean onInterceptTouchEvent(MotionEvent ev) { //外部拦截法
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mLastXInmLastX=(int) ev.getX();
mLastYIntercept=(int)ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int y_now= (int) ev.getY();
int x_now= (int) ev.getX();
int dY= y_now-mLastYIntercept;
int dX=x_now-mLastXInmLastX;
if(Math.abs(dY)>(Math.abs(dX))){ //外部竖向滑动满足需求
return true;
}else
return false;
}
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}