SwipeRefreshLayout + ViewPager,仅限制水平滚动?

时间:2014-09-22 16:06:52

标签: android android-viewpager gesture swiperefreshlayout

我已经在我的应用中实现了SwipeRefreshLayoutViewPager但是有一个很大的麻烦:每当我要向左/向右滑动以在页面之间切换时,滚动过于敏感。稍微向下滑动也会触发SwipeRefreshLayout刷新。

我想设置水平滑动开始时的限制,然后强制水平直到滑动结束。换句话说,当手指水平移动时,我想取消垂直滑动。

此问题仅发生在ViewPager上,如果我向下滑动并触发SwipeRefreshLayout刷新功能(显示条形图)然后我水平移动手指,它仍然只允许垂直滑动。 / p>

我试图扩展ViewPager类,但它根本不起作用:

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:

<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>

任何帮助将不胜感激,谢谢

9 个答案:

答案 0 :(得分:134)

我不确定您是否还有这个问题,但Google I / O app iosched解决了这个问题:

    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 :(得分:32)

非常简单地解决而没有扩展任何东西

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;
    }
});

像魅力一样工作

答案 2 :(得分:20)

我遇到了你的问题。自定义SwipeRefreshLayout可以解决问题。

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);
}

请参阅参考:link

答案 3 :(得分:10)

我基于之前的答案,但发现这更好一点。该动作以ACTION_MOVE事件开始,以我的经验结束于ACTION_UP或ACTION_CANCEL。

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;
    }
});

答案 4 :(得分:9)

出于某种原因,只有他们才知道,支持库开发团队认为适合强制拦截来自SwipeRefreshLayout的子布局的所有垂直拖动动作事件,即使是专门针对孩子的请求事件的所有权。他们唯一检查的是它的主要子项的垂直滚动状态为零(在它的子项可垂直滚动的情况下)。 requestDisallowInterceptTouchEvent()方法已被空主体覆盖,并且(不是那样)有启发性的注释“不是”。

解决此问题的最简单方法是将类从支持库复制到项目中并删除方法覆盖。 ViewGroup的实现使用内部状态来处理onInterceptTouchEvent(),因此您不能再简单地重写该方法并复制它。如果您确实希望覆盖支持库实现,那么您必须在调用requestDisallowInterceptTouchEvent()时设置自定义标记,并覆盖onInterceptTouchEvent()onTouchEvent() (或可能是基于此行为的canChildScrollUp())行为。

答案 5 :(得分:1)

nhasan解决方案存在一个问题:

如果在setEnabled(false) SwipeRefreshLayout OnPageChangeListener已经识别出“重新加载”但是没有然后调用通知回调,动画消失但SwipeRefreshLayout的内部状态保持在&#34;刷新&#34;永远不会调用可能重置状态的通知回调。从用户角度来看,这意味着Pull-to-Reload不再工作,因为无法识别所有拉动手势。

这里的问题是SwipeRefreshLayout调用删除了微调器的动画,并且通过内部AnimationListener的disable(false)方法为该微调器调用通知回调,该方法按顺序设置

不可否认,我们的测试人员用最快的手指来挑起这种情况,但在现实场景中也可能偶尔发生。

解决此问题的解决方法是覆盖onAnimationEnd中的onInterceptTouchEvent方法,如下所示:

SwipeRefreshLayout

使用布局 - 文件中的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; } } 并将mhasan解决方案中的代码更改为

MySwipeRefreshLayout

答案 6 :(得分:1)

我找到了ViewPager2的解决方案。我使用反射来降低阻力灵敏度,如下所示:

[[224 227 222 ... 164 168 170]
 [223 228 224 ... 171 171 170]
 [221 227 228 ... 171 163 157]
 ...
 [248 248 248 ... 224 224 224]
 [248 248 248 ... 224 224 224]
 [248 248 248 ... 223 223 223]]
------------
[[ 7015.56   6303.855  7427.895 ... 26778.57  25727.97  25242.195]
 [ 7430.7    6117.45   6902.595 ... 24940.02  24940.02  25355.16 ]
 [ 7882.56   6380.1    6004.485 ... 25168.755 27233.235 28809.135]
 ...
 [ 2369.46   2369.46   2369.46  ...  8557.29   8557.29   8557.29 ]
 [ 2369.46   2369.46   2369.46  ...  8557.29   8557.29   8557.29 ]
 [ 2369.46   2369.46   2369.46  ...  8819.94   8819.94   8819.94 ]]69.46  ...  8819.94   8819.94   8819.94 ]]

对我来说,它就像是一种魅力。

答案 7 :(得分:0)

将ViewPager放在可垂直滚动的容器中时,@ huu duy答案可能有问题,而该容器又可以放在SwiprRefreshLayout中 如果内容可滚动容器未完全向上滚动,则可能无法以相同的向上滚动手势激活刷卡刷新。 确实,当您开始滚动内部容器并水平移动手指而不是无意mTouchSlop(默认情况下为8dp)时, 建议的CustomSwipeToRefresh拒绝此手势。因此,用户必须再次尝试开始刷新。 对于用户来说,这可能看起来很奇怪。 我从支持库中将原始SwipeRefreshLayout的源代码提取到了我的项目中,然后重新编写了onInterceptTouchEvent()。

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上查看我的示例项目。

答案 8 :(得分:0)

2020-10-17

@nhasan 完美答案的最小补充。

如果您已从ViewPager迁移到ViewPager2,请使用

registerOnPageChangeCallback方法,用于监听滚动事件

mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageScrollStateChanged(int state) {
        super.onPageScrollStateChanged(state);
        swipe.setEnabled(state == ViewPager2.SCROLL_STATE_IDLE);
    }
});