SwipeRefreshLayout + ViewPager, 가로 스크롤 만 제한 하시겠습니까?


94

내가 구현 한 SwipeRefreshLayoutViewPager내 응용 프로그램에서하지만 큰 문제가 있습니다 : 내가 페이지 사이를 잘 스위치 / 왼쪽으로 스 와이프 갈거야 때마다 스크롤이 너무 민감하다. 아래로 약간 스 와이프하면 SwipeRefreshLayout새로 고침도 트리거됩니다 .

수평 스 와이프가 시작될 때로 제한을 설정 한 다음 스 와이프가 끝날 때까지만 수평으로 강제로 설정하고 싶습니다. 즉, 손가락이 수평으로 움직일 때 수직 스 와이프를 취소하고 싶습니다.

이 문제는에서만 발생합니다. ViewPager아래로 스 와이프하고 SwipeRefreshLayout새로 고침 기능이 트리거 된 다음 (바가 표시됨) 손가락을 가로로 움직여도 세로 스 와이프 만 허용됩니다.

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>

어떤 도움을 주시면 감사하겠습니다.


뷰 페이지 내의 조각 중 하나에 SwipeRefreshLayout? 가있는 경우 동일한 시나리오가 작동 합니까?
Zapnologica

답변:


160

여전히이 문제가 있는지 확실하지 않지만 Google I / O 앱 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);
    }
}

나는 똑같은 것을 사용했고 아주 잘 작동합니다.

편집 : setOnPageChangeListener () 대신 addOnPageChangeListener ()를 사용하십시오.


3
ViewPager의 상태를 고려하므로 이것이 가장 좋은 대답입니다. ViewPager에서 시작되는 아래로 드래그하는 것을 방지하지 않으며, 이는 새로 고침 의도를 분명히 보여줍니다.
앤드류 Gallasch

4
그러나 가장 좋은 대답은 enableDisableSwipeRefresh에 대한 코드를 게시하는 것이 좋을 수 있습니다 (예, 함수 이름에서 분명하지만 Google에서 확인해야합니다 ...)
Greg Ennis

5
완벽하게 작동하지만 setOnPageChangeListener는 이제 감가 상각됩니다. 대신 addOnPageChangeListener를 사용하십시오.
Yon Kornilov

@nhasan 최근 업데이트 에서이 답변은 더 이상 작동하지 않습니다. swiperefresh의 enabled 상태를 false로 설정하면 swiperefresh가 완전히 제거되지만 이전에는 swiperefresh가 새로 고쳐지는 동안 viewpager의 스크롤 상태가 변경되면 레이아웃이 제거되지 않지만 이전과 동일한 새로 고침 상태를 유지하면서 비활성화됩니다.
Michael Tedla

2
Google i / o 앱에서 'enableDisableSwipeRefresh'의 소스 코드 링크 : android.googlesource.com/platform/external/iosched/+/HEAD/…
jpardogo

37

아무것도 확장하지 않고 매우 간단하게 해결

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

매력처럼 일하다


좋습니다.하지만 최상위 하위 항목에 대해서만 세로 스크롤 을 허용하므로 내부에있을 수있는 모든 스크롤 가능 항목에 대해 이와 동일한 문제 View가 발생할 수 있습니다 (ICS보다 낮은 API에서는 ICS보다 낮은 경우에만 ). . ViewPagerSwipeRefreshLayoutListView
corsair992 2014 년

당신의 팁 @ corsair992less 감사
user3896501

@ corsair992가 문제에 직면했습니다. 나는 ViewPager안에 SwipeRefrestLayout있고 ViewPager가 있습니다 Listview! SwipeRefreshLayout아래로 스크롤 해 보겠습니다 만 위로 스크롤하면 새로 고침 진행률이 트리거됩니다. 어떠한 제안?
Muhammad Babar 2015 년

1
mLayout에 대해 조금 더 개발할 수 있습니까?
desgraci

2
viewPager.setOnTouchListener {_, event-> swipeRefreshLayout.isEnabled = event.action == MotionEvent.ACTION_UP false}
Axrorxo'ja Yodgorov

22

나는 당신의 문제를 만났습니다. 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);
}

참조 참조 : 링크


감지에 기울기를 포함하는 것이 가장 좋은 솔루션입니다.
nafsaka

훌륭한 솔루션 +1
Tram Nguyen

12

나는 이것을 이전 답변을 기반으로했지만 이것이 조금 더 잘 작동한다는 것을 알았습니다. 모션은 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;
    }
});

솔루션에 대한 Thnxx
Hitesh Kushwah

9

그들에게만 가장 잘 알려진 어떤 이유로 지원 라이브러리 개발자 팀은 어린이가 이벤트의 소유권을 구체적으로 요청하더라도의 하위 레이아웃 에서 모든 수직 드래그 모션 이벤트 를 강제로 가로채는 데 적합하다고 생각했습니다 SwipeRefreshLayout. 그들이 확인하는 유일한 것은 기본 자식의 세로 스크롤 상태가 0이라는 것입니다 (자식의 경우 세로로 스크롤 할 수 있음). 이 requestDisallowInterceptTouchEvent()메서드는 빈 본문과 조명 주석 "Nope"로 재정의되었습니다.

이 문제를 해결하는 가장 쉬운 방법은 지원 라이브러리에서 프로젝트로 클래스를 복사하고 메서드 재정의를 제거하는 것입니다. ViewGroup의 구현은 처리를 위해 내부 상태를 사용 onInterceptTouchEvent()하므로 단순히 메서드를 다시 재정의하고 복제 할 수 없습니다. 실제로 지원 라이브러리 구현 을 재정의 하려면를 호출 할 때 사용자 지정 플래그를 설정하고이를 기반으로 동작을 requestDisallowInterceptTouchEvent()재정의 onInterceptTouchEvent()onTouchEvent()(또는 해킹 canChildScrollUp())해야합니다.


야, 그건 거칠다. 나는 그들이 그렇게하지 않았 으면 좋았을 것이다. 풀을 새로 고칠 수있는 목록과 행 항목을 스 와이프하는 기능이 있습니다. 그들이 SwipeRefreshLayout을 만든 방식은 약간의 미친 해결 방법 없이는 거의 불가능하게 만듭니다.
제시 A. 모리스

3

ViewPager2에 대한 솔루션을 찾았습니다. 다음과 같이 드래그 감도를 줄이기 위해 반사를 사용합니다.

/**
 * 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
}

제게는 매력처럼 작동합니다.


2

nhasan 솔루션에는 한 가지 문제가 있습니다.

만약 트리거하는 수평 슬쩍 setEnabled(false)온 전화 SwipeRefreshLayout에서이 OnPageChangeListener일이 (가) SwipeRefreshLayout이미 인식하고 끌어 오기 - 다시로드 투하지만 아직의 알림 콜백, 애니메이션 사라하지만 내부 상태라는되지 SwipeRefreshLayout는 어떠한 영원히 "새로 고침"에 숙박을 상태를 재설정 할 수있는 알림 콜백이 호출됩니다. 사용자 관점에서 이것은 모든 당기기 제스처가 인식되지 않기 때문에 Pull-to-Reload가 더 이상 작동하지 않음을 의미합니다.

여기서 문제는 disable(false)호출이 스피너의 애니메이션을 제거하고 알림 콜백이 해당 onAnimationEnd방식으로 설정된 해당 스피너에 대한 내부 AnimationListener 의 메소드 에서 호출 된다는 것입니다.

이 상황을 유발하는 데 가장 빠른 손가락을 가진 테스터가 필요했지만 현실적인 시나리오에서도 가끔 발생할 수 있습니다.

이 문제를 해결하는 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;
    }
}

MySwipeRefreshLayout레이아웃에서 사용 -파일 및 mhasan 솔루션의 코드를 다음과 같이 변경하십시오.

...

@Override
public void onPageScrollStateChanged(int state) {
    swipeRefreshLayout.setPaused(state != ViewPager.SCROLL_STATE_IDLE);
}

...

1
나는 또한 당신과 같은 문제가있었습니다. 이 pastebin.com/XmfNsDKQ로 방금 수정 한 nhasan의 솔루션은 새로 고침 할 때 레이아웃을 스 와이프해도 문제가 발생하지 않으므로 확인해야합니다.
Amit Jayant

0

ViewPager가 SwiprRefreshLayout에 차례로 배치되는 수직 스크롤 가능 컨테이너에 배치 될 때 @huu duy 응답에 문제가있을 수 있습니다. 컨텐츠 스크롤 가능 컨테이너가 완전히 스크롤되지 않은 경우에는 불가능할 수 있습니다. 동일한 스크롤 업 제스처에서 스 와이프하여 새로 고침을 활성화합니다. 실제로 내부 컨테이너를 스크롤하기 시작하고 의도하지 않게 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의 예제 프로젝트를 참조하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.