programing

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

nasanasas 2020. 9. 13. 10:40
반응형

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


내가 구현 한 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>

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


여전히이 문제가 있는지 확실하지 않지만 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 ()를 사용하십시오.


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

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을 사용자 지정하면 문제가 해결됩니다.

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로 끝납니다.

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

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

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


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

...

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
}

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


There could be a problem with @huu duy answer when the ViewPager is placed in a vertically-scrollable container which, in turn, is placed in the SwiprRefreshLayout If the content scrollable container is not fully scrolled-up, then it may be not possible to activate swipe-to-refresh in the same scroll-up gesture. Indeed, when you start scrolling the inner container and move finger horizontally more then mTouchSlop unintentionally (which is 8dp by default), the proposed CustomSwipeToRefresh declines this gesture. So a user has to try once more to start refreshing. This may look odd for the user. I extracted the source code f the original SwipeRefreshLayout from the support library to my project and re-wrote the 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;
}

See my example project on Github.

참고URL : https://stackoverflow.com/questions/25978462/swiperefreshlayout-viewpager-limit-horizontal-scroll-only

반응형