SwipeRefreshLayout + ViewPager, chỉ giới hạn cuộn ngang?


94

Tôi đã triển khai SwipeRefreshLayoutViewPagertrong ứng dụng của mình nhưng có một rắc rối lớn: bất cứ khi nào tôi vuốt sang trái / phải để chuyển đổi giữa các trang, thao tác cuộn quá nhạy. Một chút vuốt xuống cũng sẽ kích hoạt SwipeRefreshLayoutlàm mới.

Tôi muốn đặt giới hạn cho thời điểm bắt đầu vuốt ngang, sau đó chỉ ép ngang cho đến khi hết vuốt. Nói cách khác, tôi muốn hủy thao tác xoay dọc khi ngón tay di chuyển theo chiều ngang.

Sự cố này chỉ xảy ra trên ViewPager, nếu tôi vuốt xuống và SwipeRefreshLayoutchức năng làm mới được kích hoạt (thanh được hiển thị) và sau đó tôi di chuyển ngón tay theo chiều ngang, nó vẫn chỉ cho phép vuốt dọc.

Tôi đã cố gắng mở rộng ViewPagerlớp học nhưng nó không hoạt động chút nào:

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

}

Bố cục 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>

Bất kỳ trợ giúp sẽ được đánh giá cao, cảm ơn


Kịch bản tương tự có hoạt động không nếu một trong các phân đoạn của bạn bên trong máy ngắm có một SwipeRefreshLayout?
Zapnologica

Câu trả lời:


160

Tôi không chắc liệu bạn có còn gặp sự cố này không nhưng ứng dụng Google I / O iosched giải quyết được vấn đề này do đó:

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

Tôi đã sử dụng như nhau và hoạt động khá tốt.

CHỈNH SỬA: Sử dụng addOnPageChangeListener () thay vì setOnPageChangeListener ().


3
Đây là câu trả lời tốt nhất vì nó tính đến trạng thái của ViewPager. Nó không ngăn cản việc kéo xuống bắt nguồn từ ViewPager, điều này rõ ràng thể hiện ý định làm mới.
Andrew Gallasch

4
Câu trả lời tốt nhất, tuy nhiên nó có thể là tốt đẹp để gửi mã để enableDisableSwipeRefresh (có nó là rõ ràng từ tên hàm .. nhưng để chắc chắn tôi phải google nó ...)
Greg Ennis

5
Hoạt động hoàn hảo, nhưng setOnPageChangeListener hiện đã bị giảm giá trị. thay vào đó sử dụng addOnPageChangeListener.
Yon Kornilov

@nhasan Kể từ bản cập nhật gần đây, câu trả lời này không hoạt động nữa. Đặt trạng thái đã bật của swiperefresh thành false sẽ loại bỏ hoàn toàn swiperefresh trong khi trước đây nếu trạng thái cuộn của máy xem thay đổi trong khi swiperefresh đang làm mới, nó sẽ không xóa bố cục, nhưng sẽ vô hiệu hóa nó trong khi vẫn giữ nguyên trạng thái làm mới như trước đó.
Michael Tedla

2
Liên kết đến mã nguồn cho 'enableDisableSwipeRefresh' trong ứng dụng google i / o: android.googlesource.com/platform/external/iosched/+/HEAD/…
jpardogo

37

Giải quyết rất đơn giản mà không cần mở rộng bất cứ điều gì

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

làm việc như một cái duyên


Được, nhưng hãy nhớ rằng bạn sẽ gặp vấn đề tương tự đối với bất kỳ cuộn có thể cuộn nàoView mà bạn có thể có bên trong ViewPager, cũng như chỉ SwipeRefreshLayoutcho phép cuộn dọc đối với con cấp cao nhất (và trong các API thấp hơn ICS chỉ khi nó xảy ra ListView) .
corsair992

@ corsair992less Cảm ơn những mẹo của bạn
user3896501 23/09/14

@ corsair992 đang gặp sự cố. Tôi có ViewPagerbên trong SwipeRefrestLayoutvà ViewPager có Listview! SwipeRefreshLayoutđể tôi cuộn xuống nhưng khi cuộn lên, nó sẽ kích hoạt tiến trình làm mới. Bất kì lời đề nghị nào?
Muhammad Babar

1
bạn có thể phát triển thêm một chút về mLayout là gì?
desgraci

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

22

Tôi đã gặp vấn đề của bạn. Tùy chỉnh SwipeRefreshLayout sẽ giải quyết được vấn đề.

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

Xem liên kết ref:


Đây là giải pháp tốt nhất, bao gồm độ dốc trong phát hiện.
nafsaka

Giải pháp tuyệt vời +1
Tram Nguyen

12

Tôi dựa trên câu trả lời trước đây nhưng thấy điều này hoạt động tốt hơn một chút. Chuyển động bắt đầu bằng sự kiện ACTION_MOVE và kết thúc bằng ACTION_UP hoặc ACTION_CANCEL theo kinh nghiệm của tôi.

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 cho giải pháp
Hitesh Kushwah

9

Vì một số lý do chỉ được biết đến nhiều nhất đối với họ, nhóm nhà phát triển thư viện hỗ trợ thấy phù hợp để chặn bắt buộc tất cả các sự kiện chuyển động kéo dọc khỏi SwipeRefreshLayoutbố cục con của nó, ngay cả khi trẻ yêu cầu cụ thể quyền sở hữu sự kiện. Điều duy nhất họ kiểm tra là trạng thái cuộn dọc của con chính của nó là 0 (trong trường hợp con của nó có thể cuộn theo chiều dọc). Các requestDisallowInterceptTouchEvent()phương pháp đã được ghi đè với một cơ thể trống rỗng, và (không như vậy) chiếu sáng bình luận "Không".

Cách dễ nhất để giải quyết vấn đề này là chỉ cần sao chép lớp từ thư viện hỗ trợ vào dự án của bạn và xóa phương thức ghi đè. ViewGroupViệc triển khai sử dụng trạng thái nội bộ để xử lý onInterceptTouchEvent(), vì vậy bạn không thể chỉ cần ghi đè phương thức một lần nữa và sao chép nó. Nếu bạn thực sự muốn ghi đè việc triển khai thư viện hỗ trợ, thì bạn sẽ phải thiết lập cờ tùy chỉnh khi các lệnh gọi đến requestDisallowInterceptTouchEvent(), ghi đè onInterceptTouchEvent()onTouchEvent()(hoặc có thể là hack canChildScrollUp()) hành vi dựa trên đó.


Trời đất, thật là thô bạo. Tôi thực sự ước họ sẽ không làm điều đó. Tôi có một danh sách mà tôi muốn bật kéo để làm mới và khả năng vuốt các mục hàng. Cách họ đã tạo SwipeRefreshLayout khiến điều đó gần như không thể thực hiện được nếu không có một số giải pháp điên rồ.
Jessie A. Morris

3

Tôi đã tìm thấy giải pháp cho ViewPager2. Tôi sử dụng phản xạ để giảm độ nhạy kéo như thế này:

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

Nó hoạt động như một sự quyến rũ đối với tôi.


2

Có một vấn đề với giải pháp của nhasan:

Nếu swipe ngang mà trigger các setEnabled(false)cuộc gọi trên SwipeRefreshLayouttrong OnPageChangeListenerxảy ra khi SwipeRefreshLayoutđã nhận một Pull-to-Nạp lại nhưng vẫn chưa được gọi là gọi lại thông báo, biến mất hình ảnh động nhưng tình trạng nội bộ của SwipeRefreshLayoutlưu trú vào "làm mới" mãi mãi như không gọi lại thông báo được gọi có thể đặt lại trạng thái. Từ góc độ người dùng, điều này có nghĩa là Kéo để Tải lại không hoạt động nữa vì tất cả các cử chỉ kéo không được nhận dạng.

Vấn đề ở đây là lệnh disable(false)gọi loại bỏ hoạt ảnh của spinner và lệnh gọi lại thông báo được gọi từ onAnimationEndphương thức của AnimationListener nội bộ cho spinner được thiết lập theo cách đó.

Phải thừa nhận rằng người thử nghiệm của chúng tôi có những ngón tay nhanh nhất để gây ra tình huống này nhưng nó cũng có thể xảy ra một lần trong các tình huống thực tế.

Giải pháp để khắc phục điều này là ghi đè onInterceptTouchEventphương thức SwipeRefreshLayoutnhư sau:

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

Sử dụng MySwipeRefreshLayouttrong Bố cục của bạn - Tệp và thay đổi mã trong giải pháp của mhasan thành

...

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

...

1
Tôi cũng gặp vấn đề tương tự như của bạn. Giải pháp của nhasan vừa được sửa đổi với pastebin.com/XmfNsDKQ Bố cục làm mới bằng thao tác vuốt Lưu ý không tạo ra sự cố khi làm mới, vì vậy đó là lý do tại sao kiểm tra.
Amit Jayant

0

Có thể xảy ra sự cố với @huu duy answer khi ViewPager được đặt trong vùng chứa có thể cuộn theo chiều dọc, đến lượt nó, được đặt trong SwiprRefreshLayout Nếu vùng chứa có thể cuộn của nội dung không được cuộn lên hoàn toàn, thì có thể không kích hoạt vuốt để làm mới trong cùng một cử chỉ cuộn lên. Thật vậy, khi bạn bắt đầu cuộn vùng chứa bên trong và di chuyển ngón tay theo chiều ngang nhiều hơn mTouchSlop không chủ ý (theo mặc định là 8dp), CustomSwipeToRefresh được đề xuất sẽ từ chối cử chỉ này. Vì vậy, người dùng phải thử một lần nữa để bắt đầu làm mới. Điều này có thể trông kỳ lạ đối với người dùng. Tôi đã trích xuất mã nguồn từ SwipeRefreshLayout gốc từ thư viện hỗ trợ vào dự án của mình và viết lại 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;
}

Xem dự án ví dụ của tôi trên Github .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.