禁用在 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);
}
}
我有一个 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);
}
}