SwipeRefreshLayout + ViewPager, limit horizontal scroll only?
我已经在我的应用程序中实现了
我想为开始水平滑动设置一个限制,然后仅在滑动结束之前强制水平滑动。 换句话说,当手指水平移动时,我想取消垂直滑动。
仅当
我试图扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class CustomViewPager extends ViewPager { public CustomViewPager(Context ctx, AttributeSet attrs) { super(ctx, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean in = super.onInterceptTouchEvent(ev); if (in) { getParent().requestDisallowInterceptTouchEvent(true); this.requestDisallowInterceptTouchEvent(true); } return false; } } |
布局xml:
1 2 3 4 5 6 7 8 9 | <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/viewTopic" android:layout_width="match_parent" android:layout_height="match_parent"> <com.myapp.listloader.foundation.CustomViewPager android:id="@+id/topicViewPager" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.v4.widget.SwipeRefreshLayout> |
任何帮助,将不胜感激,谢谢
我不确定您是否仍然存在此问题,但是通过iosched的Google I / O应用程序可以解决此问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | viewPager.addOnPageChangeListener( new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled( int position, float v, int i1 ) { } @Override public void onPageSelected( int position ) { } @Override public void onPageScrollStateChanged( int state ) { enableDisableSwipeRefresh( state == ViewPager.SCROLL_STATE_IDLE ); } } ); private void enableDisableSwipeRefresh(boolean enable) { if (swipeContainer != null) { swipeContainer.setEnabled(enable); } } |
我已经用过,并且效果很好。
编辑:使用addOnPageChangeListener()代替setOnPageChangeListener()。
解决非常简单,无需扩展任何内容
1 2 3 4 5 6 7 8 9 10 11 12 | mPager.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mLayout.setEnabled(false); switch (event.getAction()) { case MotionEvent.ACTION_UP: mLayout.setEnabled(true); break; } return false; } }); |
像魅力一样工作
我遇到了你的问题。自定义SwipeRefreshLayout将解决此问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class CustomSwipeToRefresh extends SwipeRefreshLayout { private int mTouchSlop; private float mPrevX; public CustomSwipeToRefresh(Context context, AttributeSet attrs) { super(context, attrs); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPrevX = MotionEvent.obtain(event).getX(); break; case MotionEvent.ACTION_MOVE: final float eventX = event.getX(); float xDiff = Math.abs(eventX - mPrevX); if (xDiff > mTouchSlop) { return false; } } return super.onInterceptTouchEvent(event); } |
参见参考资料:链接
我以先前的答案为基础,但发现它的工作效果更好。根据我的经验,该动作以ACTION_MOVE事件开始,并以ACTION_UP或ACTION_CANCEL结尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | mViewPager.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: mSwipeRefreshLayout.setEnabled(false); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mSwipeRefreshLayout.setEnabled(true); break; } return false; } }); |
由于某些原因(只有他们最了解),支持库开发人员团队认为可以强制拦截
解决此问题的最简单方法是将类从支持库复制到您的项目中,然后删除方法覆盖。
我找到了ViewPager2的解决方案。我使用反射来降低阻力敏感性,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * Reduces drag sensitivity of [ViewPager2] widget */ fun ViewPager2.reduceDragSensitivity() { val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView") recyclerViewField.isAccessible = true val recyclerView = recyclerViewField.get(this) as RecyclerView val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop") touchSlopField.isAccessible = true val touchSlop = touchSlopField.get(recyclerView) as Int touchSlopField.set(recyclerView, touchSlop*8) //"8" was obtained experimentally } |
对我来说,它就像是一种魅力。
nhasan解决方案存在一个问题:
如果在
这里的问题是
诚然,我们的测试人员用最快的手指来触发这种情况,但在现实情况下也可能偶尔发生。
解决此问题的一种方法是重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class MySwipeRefreshLayout extends SwipeRefreshLayout { private boolean paused; public MySwipeRefreshLayout(Context context) { super(context); setColorScheme(); } public MySwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); setColorScheme(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (paused) { return false; } else { return super.onInterceptTouchEvent(ev); } } public void setPaused(boolean paused) { this.paused = paused; } } |
在布局-文件中使用
1 2 3 4 5 6 7 8 | ... @Override public void onPageScrollStateChanged(int state) { swipeRefreshLayout.setPaused(state != ViewPager.SCROLL_STATE_IDLE); } ... |
2020-10-17
@nhasan完美答案的最小补充。
如果您已从
1 2 3 4 5 6 7 | mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageScrollStateChanged(int state) { super.onPageScrollStateChanged(state); swipe.setEnabled(state == ViewPager2.SCROLL_STATE_IDLE); } }); |
将ViewPager放在可垂直滚动的容器中时,@ huu duy答案可能有问题,而该容器又可以放在SwiprRefreshLayout中
如果内容可滚动容器未完全向上滚动,则可能无法以相同的向上滚动手势激活刷卡刷新。
确实,当您开始滚动内部容器并无意间水平移动手指而不是mTouchSlop时(默认为8dp),
建议的CustomSwipeToRefresh拒绝此手势。因此,用户必须再次尝试开始刷新。
对于用户来说,这可能看起来很奇怪。
我从支持库中将原始SwipeRefreshLayout的源代码提取到了我的项目中,并重新编写了onInterceptTouchEvent()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | private float mInitialDownY; private float mInitialDownX; private boolean mGestureDeclined; private boolean mPendingActionDown; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { ensureTarget(); final int action = ev.getActionMasked(); int pointerIndex; if (mReturningToStart && action == MotionEvent.ACTION_DOWN) { mReturningToStart = false; } if (!isEnabled() || mReturningToStart || mRefreshing ) { // Fail fast if we're not in a state where a swipe is possible if (D) Log.e(LOG_TAG,"Fail because of not enabled OR refreshing OR returning to start."+motionEventToShortText(ev)); return false; } switch (action) { case MotionEvent.ACTION_DOWN: setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop()); mActivePointerId = ev.getPointerId(0); if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) >= 0) { if (mNestedScrollInProgress || canChildScrollUp()) { if (D) Log.e(LOG_TAG,"Fail because of nested content is Scrolling. Set pending DOWN=true."+motionEventToShortText(ev)); mPendingActionDown = true; } else { mInitialDownX = ev.getX(pointerIndex); mInitialDownY = ev.getY(pointerIndex); } } return false; case MotionEvent.ACTION_MOVE: if (mActivePointerId == INVALID_POINTER) { if (D) Log.e(LOG_TAG,"Got ACTION_MOVE event but don't have an active pointer id."); return false; } else if (mGestureDeclined) { if (D) Log.e(LOG_TAG,"Gesture was declined previously because of horizontal swipe"); return false; } else if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) < 0) { return false; } else if (mNestedScrollInProgress || canChildScrollUp()) { if (D) Log.e(LOG_TAG,"Fail because of nested content is Scrolling."+motionEventToShortText(ev)); return false; } else if (mPendingActionDown) { // This is the 1-st Move after content stops scrolling. // Consider this Move as Down (a start of new gesture) if (D) Log.e(LOG_TAG,"Consider this move as down - setup initial X/Y."+motionEventToShortText(ev)); mPendingActionDown = false; mInitialDownX = ev.getX(pointerIndex); mInitialDownY = ev.getY(pointerIndex); return false; } else if (Math.abs(ev.getX(pointerIndex) - mInitialDownX) > mTouchSlop) { mGestureDeclined = true; if (D) Log.e(LOG_TAG,"Decline gesture because of horizontal swipe"); return false; } final float y = ev.getY(pointerIndex); startDragging(y); if (!mIsBeingDragged) { if (D) Log.d(LOG_TAG,"Waiting for dY to start dragging."+motionEventToShortText(ev)); } else { if (D) Log.d(LOG_TAG,"Dragging started!"+motionEventToShortText(ev)); } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mIsBeingDragged = false; mGestureDeclined = false; mPendingActionDown = false; mActivePointerId = INVALID_POINTER; break; } return mIsBeingDragged; } |
请参阅我在Github上的示例项目。