SwipeRefreshLayout + ViewPager, केवल क्षैतिज स्क्रॉल सीमित करें?


94

मैंने लागू किया है 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>

किसी भी मदद को सराहा जाएगा, धन्यवाद


यदि परिदृश्य के अंदर आपके टुकड़े में से एक में एक ही परिदृश्य काम करता है SwipeRefreshLayout?
जैपनीलिका

जवाबों:


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

मैंने उसी का उपयोग किया है और काफी अच्छी तरह से काम करता है।

EDIT: addOnPageChangeListener () का उपयोग setOnPageChangeListener () के बजाय करें।


3
यह सबसे अच्छा उत्तर है क्योंकि यह ViewPager की स्थिति को ध्यान में रखता है। यह एक ड्रैग-डाउनवर्ड को रोकता नहीं है जो कि व्यूपेज पर उत्पन्न होता है, जो स्पष्ट रूप से ताज़ा करने के इरादे को प्रदर्शित करता है।
एंड्रयू गैलश

4
सबसे अच्छा जवाब, हालाँकि यह कोड को पोस्ट करने के लिए अच्छा हो सकता है enableDisableSwipeRefresh (हाँ यह फ़ंक्शन नाम से स्पष्ट है .. लेकिन यह सुनिश्चित करने के लिए कि मुझे इसे Google करना है ...)
ग्रेग

5
पूरी तरह से काम करता है, लेकिन setOnPageChangeListener अब मूल्यह्रास हो गया है। इसके बजाय addOnPageChangeListener का उपयोग करें।
योन कोर्निलोव

@nhasan हाल के अपडेट के अनुसार यह उत्तर अब काम नहीं करता है। Swiperefresh की सक्षम स्थिति को गलत पर सेट करने से swiperefresh पूरी तरह से हटा दिया जाता है, जबकि इससे पहले कि अगर व्यूअर का स्क्रॉल राज्य परिवर्तित हो गया, जबकि swiperefresh रिफ्रेश हो रहा है तो यह लेआउट को नहीं हटाएगा, लेकिन इसे उसी ताज़ा स्थिति में रखते हुए इसे अक्षम कर देगा, जो पहले था।
माइकल टेडला

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जो आपके अंदर हो सकता है ViewPager, क्योंकि SwipeRefreshLayoutयह केवल शीर्ष स्तर के बच्चे के लिए भी ऊर्ध्वाधर स्क्रॉल करने की अनुमति देता है (और अगर ऐसा होता है तो केवल आईसीएस से कम एपीआई में ListView) ।
corsair992

@ corsair992less आपकी युक्तियों के लिए धन्यवाद
user3896501

@ corsair992 समस्या का सामना कर रहा है। मैं ViewPagerअंदर है SwipeRefrestLayoutऔर ViewPager है Listview! SwipeRefreshLayoutमुझे स्क्रॉल करने दें, लेकिन स्क्रॉल करने पर यह रीफ़्रेश प्रगति को ट्रिगर करता है। कोई उपाय?
मुहम्मद बाबर

1
क्या आप mLayout के बारे में थोड़ा और विकसित कर सकते हैं?
desgraci

2
viewPager.setOnTouchListener {_, घटना -> swipeRefreshLayout.isEnabled = event.action == MotionEvent.ACTION_UP गलत}
Axrorxo'ja Yodgoros

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

रेफरी देखें: लिंक


ढलान में शामिल करने के लिए यह सबसे अच्छा उपाय है।
नफ्सका

महान समाधान +1
ट्राम गुयेन

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
हितेश कुशवाह

9

किसी कारण के लिए जो केवल उनके लिए सबसे अच्छी तरह से जाना जाता है, समर्थन पुस्तकालय डेवलपर टीम ने बच्चे के लेआउट से सभी ऊर्ध्वाधर ड्रैग गति घटनाओं को जबरदस्ती रोकना उचित देखा SwipeRefreshLayout, तब भी जब एक बच्चा विशेष रूप से घटना के स्वामित्व का अनुरोध करता है। केवल एक चीज जो वे जांचते हैं, वह यह है कि मुख्य बच्चे की ऊर्ध्वाधर स्क्रॉल स्थिति शून्य पर है (इस मामले में कि वह बच्चा लंबवत स्क्रॉल करने योग्य है)। requestDisallowInterceptTouchEvent()विधि एक खाली शरीर के साथ ओवरराइड की गई है, और (नहीं तो) रोशन टिप्पणी "नहीं"।

इस मुद्दे को हल करने का सबसे आसान तरीका यह होगा कि आप अपनी परियोजना में सहायक पुस्तकालय से कक्षा की प्रतिलिपि बनाएँ और विधि ओवरराइड को हटा दें। ViewGroupहैंडलिंग के लिए कार्यान्वयन आंतरिक स्थिति का उपयोग करता है onInterceptTouchEvent(), इसलिए आप बस फिर से विधि को ओवरराइड नहीं कर सकते हैं और इसे डुप्लिकेट कर सकते हैं। यदि आप वास्तव में समर्थन लाइब्रेरी कार्यान्वयन को ओवरराइड करना चाहते हैं , तो आपको कॉल के आधार पर एक कस्टम ध्वज स्थापित करना होगा, और उसके आधार पर requestDisallowInterceptTouchEvent()ओवरराइड onInterceptTouchEvent()और onTouchEvent()(या संभवतः हैक canChildScrollUp()) व्यवहार करना होगा।


यार, वह मोटा है। मैं वास्तव में चाहता हूं कि उन्होंने ऐसा नहीं किया होगा। मेरे पास एक सूची है जिसे मैं ताज़ा करने के लिए खींचने और पंक्ति वस्तुओं को स्वाइप करने की क्षमता को सक्षम करना चाहता हूं। जिस तरह से उन्होंने SwipeRefreshLayout बनाया है वह लगभग कुछ पागल काम के बिना असंभव बना देता है।
जेसी ए मॉरिस

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

नसन के समाधान के साथ एक समस्या है:

तो क्षैतिज कड़ी चोट है कि चलाता setEnabled(false)पर कॉल SwipeRefreshLayoutमें OnPageChangeListenerहोता है जब SwipeRefreshLayoutपहले से ही मान्यता प्रदान की है एक पुल को पुन: लोड करना है लेकिन अभी तक सूचना कॉलबैक, एनीमेशन गायब लेकिन की आंतरिक स्थिति का आह्वान किया है SwipeRefreshLayoutपर "ताज़ा" हमेशा के लिए के रूप में कोई रहता है अधिसूचना कॉलबैक को कहा जाता है जो राज्य को रीसेट कर सकता है। एक उपयोगकर्ता के नजरिए से इसका मतलब है कि पुल-टू-रीलोड अब काम नहीं कर रहा है क्योंकि सभी पुल के इशारों को मान्यता नहीं दी गई है।

यहां समस्या यह है कि 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;
    }
}

MySwipeRefreshLayoutअपने लेआउट - फ़ाइल में उपयोग करें और mhasan के समाधान में कोड बदलें

...

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

...

1
मुझे भी तुम्हारी जैसी ही समस्या थी। बस इस pastebin.com/XmfNsDKQ नोट के साथ संशोधित nhasan के समाधान को स्वाइप करें रिफ्रेश लेआउट जब इसके ताज़ा होने पर समस्या पैदा नहीं करता है, तो इसीलिए चेक करें।
अमित जयंत

0

@Huu duy उत्तर के साथ कोई समस्या हो सकती है जब ViewPager को एक लंबवत-स्क्रॉल करने योग्य कंटेनर में रखा जाता है, जो बदले में, SwiprRefreshLayout में रखा जाता है। यदि सामग्री स्क्रॉल करने योग्य कंटेनर पूरी तरह से स्क्रॉल-अप नहीं है, तो यह संभव नहीं हो सकता है। उसी स्क्रॉल-अप जेस्चर में स्वाइप-रिफ्रेश को सक्रिय करें। दरअसल, जब आप आंतरिक कंटेनर को स्क्रॉल करना शुरू करते हैं और उंगली को क्षैतिज रूप से आगे बढ़ाते हैं तो mTouchSlop अनायास ही (जो डिफ़ॉल्ट रूप से 8dp है), प्रस्तावित CustomSwipeToRefresh इस इशारे को कम कर देता है। तो एक उपयोगकर्ता को ताज़ा करने के लिए एक बार और प्रयास करना होगा। यह उपयोगकर्ता के लिए अजीब लग सकता है। मैंने अपनी परियोजना के लिए समर्थन पुस्तकालय से मूल SwipeRefreshLayout के स्रोत कोड को निकाला और onInceptceptTouchEvent () को फिर से लिखा।

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

गितुब पर मेरा उदाहरण प्रोजेक्ट देखें ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.