禁用在 ViewPager 中的某些片段上滑动

Disable swipe on some fragments in ViewPager

我有一个 ViewPager 可以禁用或启用滑动触摸:

public class ConfigurablePager extends ViewPager {

    private final AtomicBoolean touchesAllowed = new AtomicBoolean();

    ...

    private boolean touchesAllowed() {
        return touchesAllowed.get();
    }

    public void enableTouches() {
        touchesAllowed.set(true);
    }

    public void disableTouches() {
        touchesAllowed.set(false);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return touchesAllowed() && super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return touchesAllowed() && super.onInterceptTouchEvent(ev);
    }
}

有的片段可以刷,有的不可以。寻呼机适配器知道每个片段的滑动行为。此行为可以在 ViewPager.OnPageChangeListener:

中更改
@Override
public void onPageSelected(int position) {
    if (adapter.isTouchesAllowed(position)) {
        views.pager.enableTouches();
    } else {
        views.pager.disableTouches();
    }
}

问题
有时,当我快速滑动片段并同时单击其他片段的选项卡时,viewpager 会抛出 IllegalArgumentException:

FATAL EXCEPTION:
main java.lang.IllegalArgumentException: pointerIndex out of range
at android.view.MotionEvent.nativeGetAxisValue(Native Method)
at android.view.MotionEvent.getX(MotionEvent.java:1979)
at android.support.v4.view.MotionEventCompatEclair.getX(MotionEventCompatEclair.java:32)
at android.support.v4.view.MotionEventCompat$EclairMotionEventVersionImpl.getX(MotionEventCompat.java:110)
at android.support.v4.view.MotionEventCompat.getX(MotionEventCompat.java:462)
at android.support.v4.view.ViewPager.onTouchEvent(ViewPager.java:2080)
at com.test.debugpager.ConfigurablePager.onTouchEvent(ConfigurablePager.java:39)
at android.view.View.dispatchTouchEvent(View.java:7384)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2203)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1938)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2231)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1952)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2209)

发生这种情况是因为 ViewPager 保存了最后一个 pointerId 并获得了不一致的状态(一些触摸事件被 onInterceptTouchEvent 丢弃)例如ACTION_MOVE 上次触摸事件 mActivePointerId 不正确(参见 ViewPager.java 的来源)

问题
是否可以通过其他方式禁用某些片段上的滑动,也许无需覆盖 onInterceptTouchEvent

ViewPager 来源 (onTouchEvent):

case MotionEvent.ACTION_MOVE:
    if (!mIsBeingDragged) {
        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
        final float x = MotionEventCompat.getX(ev, pointerIndex);
        final float xDiff = Math.abs(x - mLastMotionX);
        final float y = MotionEventCompat.getY(ev, pointerIndex);
        final float yDiff = Math.abs(y - mLastMotionY);
        if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
        if (xDiff > mTouchSlop && xDiff > yDiff) {
            if (DEBUG) Log.v(TAG, "Starting drag!");
            mIsBeingDragged = true;
            requestParentDisallowInterceptTouchEvent(true);
            mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
            mInitialMotionX - mTouchSlop;
            mLastMotionY = y;
            setScrollState(SCROLL_STATE_DRAGGING);
            setScrollingCacheEnabled(true);

            // Disallow Parent Intercept, just in case
            ViewParent parent = getParent();
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
        }
    }

已解决

我仔细阅读了 android guide ViewGroup 中关于手势识别的内容,并分析了 ViewPager onTouchEvent 来源。在这里我认识到 ViewPager 只对 ACTION_MOVE 事件进行滑动,所以我们不应该只为这个动作调用触摸回调,我们应该在调用 base 之前服从 base ViewGroup onInterceptTouchEvent 结果class onTouchEvent

根据这个规则,我更改了我的 ViewPager 代码:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (touchesAllowed()) {
        return super.onInterceptTouchEvent(ev);
    } else {
        if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_MOVE) {
            // ignore move action
        } else {
            if (super.onInterceptTouchEvent(ev)) {
                super.onTouchEvent(ev);
            }
        }
        return false;
    }
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (touchesAllowed()) {
        return super.onTouchEvent(ev);
    } else {
        return MotionEventCompat.getActionMasked(ev) != MotionEvent.ACTION_MOVE && super.onTouchEvent(ev);
    }
}