1 遇到问题场景:在ListView 中添加 ViewPager这个控件时,经常会出现事件冲突的问题,
2 解决方案:自定义viewPager 这个控件:具体代码如下:
public class CustomerViewPager extends ViewPager {
private ViewGroup mParent;
private float mLastX;
private float mLastY;
public DecoratorViewPage<pre name="code" class="java">public class CustomerViewPager extends ViewPager {
private ViewGroup mParent;
private float mLastX;
private float mLastY;
public DecoratorViewPager(Context context) {
super(context);
}
public DecoratorViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setParent(ViewGroup parent) {
mParent = parent;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mParent == null) {
return;
}
final int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
mParent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getX() - mLastX) > Math.abs(event.getY() - mLastY)) {
mParent.requestDisallowInterceptTouchEvent(true);
} else {
mParent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mParent.requestDisallowInterceptTouchEvent(false);
break;
mLastX = event.getX();
mLastY = event.getY();
}
}
return super.dispatchTouchEvent(event);
}
}
分析:
(1) 自定义控件ViewPager,重写DispatchTouchEvent 这个方法,在 MotionEvent.ACTION_DOWN 事件中 添加 mParent.requestDisallowInterceptTouchEvent(true); 这个段代码,为什么要添加这段代码?如果不添加这段代码的后果是,处理冲突不生效,通过源码来分析是什么原因:mParent.requestDisallowInterceptTouchEvent(true),这段代码执行的源码是:
/**
* {@inheritDoc}
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
通过源码可以看出:FLAG_DISALLOW_INTERCEPT 这个标记位来标记拦截成功,在ViewGroup 源码中用到FLAG_DISALLOW_INTERCEPT 这个标记的地方分别为:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...........
}
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
为什么说要在 MotionEvent.ACTION_DOWN 中去执行下
mParent.requestDisallowInterceptTouchEvent(true),是因为在ViewGroup 的源码中,有段代码如下:
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
这里调用resetTouchState(); 这里会把 FLAG_DISALLOW_INTERCEPT 变成 false; 这样我们起不到拦截的效果
所以需要我们先在 MotionEvent.ACTION_DOWN 中设置成true;
所以得出结论:事件的传递顺序先从父view 传给子View ,通过requestDisallowInterceptTouchEvent这个方法中参数的true or false 可以实现在子view 去干涉 父view 的事件的分发过程,但是ACTION_DOWN 除外
(2) 由于在 MotionEvent.ACTION_DOWN 中设置了 mParent.requestDisallowInterceptTouchEvent(true) ,通过这种方式去避免MotionEvent.ACTION_DOWN 事件对拦截事件的影响,为了消除这种人为的干预,所以我们会在 MotionEvent.ACTION_UP 或者 MotionEvent.ACTION_CANCENL 事件中去调用 requestDisallowInterceptTouchEvent(false) 去消除影响!