SwipeRefreshLayout + ViewPager จำกัด การเลื่อนในแนวนอนเท่านั้นหรือไม่


96

ฉันใช้งานSwipeRefreshLayoutแล้วและViewPagerในแอปของฉัน แต่มีปัญหาใหญ่เมื่อใดก็ตามที่ฉันจะปัดไปทางซ้าย / ขวาเพื่อสลับไปมาระหว่างหน้าการเลื่อนนั้นไวเกินไป การปัดลงเล็กน้อยจะทำให้เกิดการ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>

ความช่วยเหลือใด ๆ จะได้รับการชื่นชมขอบคุณ


สถานการณ์เดียวกันนี้ใช้งานได้หรือไม่หากส่วนหนึ่งของคุณใน viewpager มี a SwipeRefreshLayout?
Zapnologica

คำตอบ:


161

ฉันไม่แน่ใจว่าคุณยังมีปัญหานี้อยู่หรือไม่ แต่แอป 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);
    }
}

ฉันได้ใช้แบบเดียวกันและได้ผลดีทีเดียว

แก้ไข: ใช้ addOnPageChangeListener () แทน setOnPageChangeListener ()


3
นี่เป็นคำตอบที่ดีที่สุดเนื่องจากคำนึงถึงสถานะของ ViewPager ไม่ได้ป้องกันการลากลงที่มาจาก ViewPager ซึ่งแสดงให้เห็นอย่างชัดเจนถึงความตั้งใจที่จะรีเฟรช
Andrew Gallasch

4
คำตอบที่ดีที่สุดอย่างไรก็ตามอาจเป็นการดีที่จะโพสต์รหัสเพื่อ enableDisableSwipeRefresh (ใช่มันชัดเจนจากชื่อฟังก์ชัน .. แต่เพื่อให้แน่ใจว่าฉันต้องใช้ Google มัน ... )
Greg Ennis

5
ทำงานได้อย่างสมบูรณ์ แต่ตอนนี้ setOnPageChangeListener คิดค่าเสื่อมราคาแล้ว ใช้ addOnPageChangeListener แทน
Yon Kornilov

@nhasan จากการอัปเดตล่าสุดคำตอบนี้ใช้ไม่ได้อีกต่อไป การตั้งค่าสถานะเปิดใช้งานของ swiperefresh เป็นเท็จจะลบ swiperefresh ทั้งหมดในขณะที่ก่อนหน้านี้หากสถานะการเลื่อนของ viewpager เปลี่ยนไปในขณะที่ swiperefresh กำลังรีเฟรชจะไม่ลบเลย์เอาต์ แต่จะปิดใช้งานในขณะที่คงสถานะการรีเฟรชเหมือนเดิม
Michael Tedla

2
ลิงก์ไปยังซอร์สโค้ดสำหรับ "enableDisableSwipeRefresh" ในแอป Google i / o: 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ที่คุณอาจมีอยู่ในViewPagerนั้นเนื่องจากSwipeRefreshLayoutอนุญาตให้เลื่อนในแนวตั้งได้เฉพาะสำหรับเด็กระดับบนสุดเท่านั้น (และใน API ที่ต่ำกว่า ICS ก็ต่อเมื่อมันเป็น a ListView) .
corsair992

@ corsair992less ขอบคุณสำหรับเคล็ดลับ
user3896501

@ corsair992 ประสบปัญหา ฉันมีViewPagerอยู่ข้างในSwipeRefrestLayoutและ ViewPager มีListview! SwipeRefreshLayoutให้ฉันเลื่อนลง แต่เมื่อเลื่อนขึ้นมันจะทริกเกอร์ความคืบหน้าการรีเฟรช ข้อเสนอแนะใด ๆ ?
Muhammad Babar

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's รูปแบบเด็กแม้เมื่อเด็กโดยเฉพาะการร้องขอความเป็นเจ้าของของเหตุการณ์ สิ่งเดียวที่พวกเขาตรวจสอบคือสถานะการเลื่อนแนวตั้งของลูกหลักอยู่ที่ศูนย์ (ในกรณีที่ลูกเลื่อนในแนวตั้งได้) requestDisallowInterceptTouchEvent()วิธีการได้รับการแทนที่ด้วยร่างกายที่ว่างเปล่าและ (ไม่ให้) ส่องสว่างความคิดเห็น "Nope"

วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือเพียงแค่คัดลอกคลาสจากไลบรารีการสนับสนุนไปยังโปรเจ็กต์ของคุณและลบการแทนที่เมธอด ViewGroupการนำไปใช้งานจะใช้สถานะภายในในการจัดการonInterceptTouchEvent()ดังนั้นคุณจึงไม่สามารถแทนที่วิธีการอีกครั้งและทำซ้ำได้ หากคุณต้องการลบล้างการใช้งานไลบรารีการสนับสนุนจริงๆคุณจะต้องตั้งค่าแฟล็กที่กำหนดเองเมื่อมีการโทรrequestDisallowInterceptTouchEvent()และลบล้างonInterceptTouchEvent()และonTouchEvent()(หรืออาจแฮ็กcanChildScrollUp()) พฤติกรรมตามนั้น


ผู้ชายที่หยาบ ฉันหวังว่าพวกเขาจะไม่ทำอย่างนั้น ฉันมีรายการที่ฉันต้องการเปิดใช้งานการดึงเพื่อรีเฟรชและความสามารถในการปัดรายการแถว วิธีที่พวกเขาสร้าง SwipeRefreshLayout ทำให้แทบจะเป็นไปไม่ได้เลยหากไม่มีการแก้ไขอย่างบ้าคลั่ง
Jessie A. Morris

3

มีปัญหาหนึ่งในการแก้ปัญหาของ 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ใน Layout ของคุณ - ไฟล์และเปลี่ยนรหัสในการแก้ปัญหาของ mhasan เป็น

...

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

...

1
ฉันก็มีปัญหาเช่นเดียวกับคุณ เพิ่งแก้ไขโซลูชันของ nhasan ด้วยpastebin.com/XmfNsDKQหมายเหตุเค้าโครงการรีเฟรชแบบปัดไม่สร้างปัญหาเมื่อรีเฟรชดังนั้นจึงเป็นสาเหตุที่ต้องตรวจสอบ
Amit Jayant

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
}

มันเป็นเสน่ห์สำหรับฉัน


0

อาจมีปัญหากับคำตอบ @huu duy เมื่อ ViewPager ถูกวางไว้ในคอนเทนเนอร์ที่เลื่อนได้ในแนวตั้งซึ่งจะถูกวางไว้ใน SwiprRefreshLayout หากคอนเทนเนอร์ที่เลื่อนเนื้อหาไม่สามารถเลื่อนขึ้นได้อย่างสมบูรณ์อาจเป็นไปไม่ได้ที่จะ เปิดใช้งานการปัดเพื่อรีเฟรชในท่าทางเลื่อนขึ้นเดียวกัน อันที่จริงเมื่อคุณเริ่มเลื่อนคอนเทนเนอร์ด้านในและเลื่อนนิ้วในแนวนอนมากขึ้นจากนั้น mTouchSlop โดยไม่ได้ตั้งใจ (ซึ่งเป็น 8dp โดยค่าเริ่มต้น) CustomSwipeToRefresh ที่เสนอจะปฏิเสธท่าทางสัมผัสนี้ ดังนั้นผู้ใช้ต้องลองอีกครั้งเพื่อเริ่มการรีเฟรช สิ่งนี้อาจดูแปลกสำหรับผู้ใช้ ฉันแตกซอร์สโค้ด f 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


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);
    }
}); 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.