最近在做一个ListView里面嵌套一个pageView的案例(如下图),在处理pageView左右拖动的touch事件,会跟ListView的冲突,原本想要切换pageview的图片,结果切换了ListView,这种滑动冲突问题,查看了相关的材料和源代码后,总结如下:
一、首先查看消息传递流程图:
1、android事件分发是先传递到Viewgroup,再有Viewgroup传递到View。
2、在Viewgroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onIntercrptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认是false
3、子View中如果将传递的事件消费掉(通过dispatchTouchEvent方法),Viewgroup中将无法接收到事件
二、进一步了解具体如何实现,来看下ViewGroup的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
//可以通过requestDisallowInterceptTouchEvent方法来设置该变量的值,通常是false</span>
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
//onInterceptTouchEvent在默认情况下是返回false的,所以这里通常是可以进去的</span>
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//遍历ViewGroup的孩子,如果触摸点在某一个子View中,则调用在子View的dispatchTouchEvent</span>
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//判断触点是否在该子View上</span>
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//调用了某一个子View 的dispatchTouchEvent ,如果这个子View 的dispatchTouchEvent返回true,那么意味着这个事件已经被这个子View消费了,不会继续 //传递</span>
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
<span style="white-space:pre"> </span>//mMotionTarget 置为处理该事件的子View
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
if (target == null) {
//对于一个Action_down事件,如果走到了这里,说明所有的子View 都没有消费掉这个事件,那么它就调用父类的
//的dispatchTouchEvnet方法,也就是View的
<pre name="code" class="java">ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
总结如下:
1、如果disallowIntercept||!onInterceptTouchEvent(),那么事件才可以继续传递下去,否则直接调用该ViewGroup的父类的dispatchTouchEvent,也就是View的dispatchTouchEvent.
2、依次遍历ViewGroup的所有子View,将事件传递个子View,如果某一个子View处理了该事件,并且返回true,那么事件结束,停止传递
3、如果所有的子View没有消费掉这个事件,那么就调用父类即View的dispatchTouchEvent
三、接下来我们查看View的dispatchTouchEvent代码
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
首先判断mTouchListener是否为空,并且这个View是否Eneable,如果都满足,那么首先调用mOnTouchListener.onTouch方法,如果onTouch方法返回true,那么就是说这个View消费了该事件,直接返回true,如果onTouch返回false,那么就会调用onTouchEvnet方法
ViewGroup的通过dispatchTouchEvent,如果它的所有的子View没有处理掉该事件,那么调用的是父类View的dispatchTouchEvnet方法,从而执行到了该ViewGroup的onTouch和onTouchEvent方法。
像本项目的拖动冲突,需要覆写pageView的dispatchTouchEvnet并返回true,就会实现其图片的切换,不会再将touch事件传递到其父View(ListView),从而不会再执行翻页行为。
(PS:来了优快云这段事件,逐渐喜欢上了这里的氛围,这是第一篇博客,还有很多不足还望指教,希望大家共同进步)