开发当中经常需要处理滑动冲突,而滑动冲突这种老大难的问题的理论基础就是事件分发机制
首先我们来认识下事件分发中3个主要的方法
- public boolean dispatchTouchEvent(MotionEvent event)
通过方法名我们不难猜测,它就是事件分发的重要方法。那么很明显,如果一个MotionEvent传递给了View,那么dispatchTouchEvent方法一定会被调用!
返回值:表示当前事件是否被消费了。可能是View本身的onTouchEvent方法消费,也可能是子View的dispatchTouchEvent方法中消费。返回true表示事件被消费,本次的事件终止。返回false表示View以及子View均没有消费事件,将调用父View的onTouchEvent方法 - public boolean onInterceptTouchEvent(MotionEvent ev)
事件拦截,当一个ViewGroup在接到MotionEvent事件序列的时候,首先会调用此方法判断是否需要拦截。特别注意,这是ViewGroup特有的方法,View并没有拦截方法
返回值:是否拦截事件传递,返回true表示拦截事件,那么事件将不再往下分发而调用View本身的onTouchEvent方法。返回false表示不做拦截,事件将乡下分发到子View的dispathtouchEvent方法 - pulic boolean onTouchEvent(MotionEvent ev)
真正对MotionEvent进行处理或者消费的方法。在dispatchTouchEvent进行调用。
返回值:返回true表示事件被消费,本次的事件终止。返回false表示事件没有被消费,将调用父View的onTouchEvent方法
通过下面的流程图,会更清晰的帮我们梳理事件分发机制
image
对于View(ViewGroup也是View)而言,如果设置了onTouchListener,那么onTouchListener方法中的onTouch方法会被回调。onTouch返回true,则onTouchEnent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent-onClick
触摸事件冲突处理
上面介绍了事件分发的相关方法,下面就来讲讲怎么解决冲突;所谓冲突就是在触摸事件发生时不知道该由谁处理,这个判断逻辑该怎么怎么写,写在哪(其实重点是写在哪)
外部拦截法:
即在父View根据需求对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。
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;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
- ACTION_DOWN 一定要返回false,不要进行拦截,否则根据事件分发机制,后续ACTION_MOVE与ACTION_UP事件都默认交给父View去处理!
- 同上为了让子View可以接收到事件,ACTION_UP也需要返回false
内部拦截法
即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作(干预父View的对除了ACTION_DOWN之外的事件拦截)。
public boolean dispatchTouchEvent(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 - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
父View需要从写onInterceptTouchEvent方法:
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
需要注意的是:
- 内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。
- 滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。
- 由于判断逻辑在子View的ACTION_MOVE中,所以在ACTION_DOWN时需要调用parent.requestDisallowInterceptTouchEvent(true)阻止父View对ACTION_MOVE的拦截
网友评论