SwipeRefreshLayout + ViewPager, yalnızca yatay kaydırmayı sınırlasın mı?


96

Uygulamamda SwipeRefreshLayoutve uyguladım ViewPagerancak büyük bir sorun var: Sayfalar arasında geçiş yapmak için sola / sağa kaydıracağım zaman kaydırma çok hassas. Biraz aşağı kaydırmak da SwipeRefreshLayoutyenilemeyi tetikleyecektir .

Yatay kaydırmanın başlayacağı zamana bir sınır ayarlamak ve ardından yalnızca kaydırma bitene kadar yatay kaydırmaya zorlamak istiyorum. Başka bir deyişle, parmak yatay hareket ederken dikey kaydırmayı iptal etmek istiyorum.

Bu sorun yalnızca ViewPageraşağı kaydırırsam ve SwipeRefreshLayoutyenileme işlevi tetiklenirse (çubuk gösterilir) ve ardından parmağımı yatay olarak hareket ettirirsem oluşur, yine de yalnızca dikey kaydırmalara izin verir.

ViewPagerSınıfı uzatmaya çalıştım ama hiç çalışmıyor:

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

}

Düzen 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>

herhangi bir yardım takdir edilecektir, teşekkürler


Görüntüleyicideki parçalarınızdan birinde bir tane varsa aynı senaryo çalışır mı SwipeRefreshLayout?
Zapnologica

Yanıtlar:


160

Hala bu sorunu yaşıyor musunuz emin değilim, ancak Google I / O uygulaması iosched bu sorunu şu şekilde çözüyor:

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

Ben de aynısını kullandım ve oldukça iyi çalışıyor.

DÜZENLEME: setOnPageChangeListener () yerine addOnPageChangeListener () kullanın.


3
Bu en iyi cevaptır çünkü ViewPager'ın durumunu hesaba katar. Açıkça yenileme niyetini gösteren ViewPager'dan kaynaklanan aşağı doğru sürüklemeyi engellemez.
Andrew Gallasch

4
En iyi yanıt, ancak kodu enableDisableSwipeRefresh'e göndermek güzel olabilir (evet, işlev adından anlaşılıyor .. ama emin olmak için onu google'da aratmam gerekiyordu ...)
Greg Ennis

5
Mükemmel çalışıyor, ancak setOnPageChangeListener artık amortismana tabi tutuldu. bunun yerine addOnPageChangeListener'ı kullanın.
Yon Kornilov

@nhasan Son güncellemeden itibaren bu cevap artık çalışmıyor. Swiperefresh'in etkin durumunun yanlış olarak ayarlanması, swiperefresh'i tamamen kaldırır, oysa daha önce, swiperefresh yenilenirken görüntüleyicinin kaydırma durumu değiştiyse, düzeni kaldırmaz, ancak daha önce olduğu gibi aynı yenileme durumunda tutarken devre dışı bırakır.
Michael Tedla

2
Google i / o uygulamasında "enableDisableSwipeRefresh" için kaynak koduna bağlantı: android.googlesource.com/platform/external/iosched/+/HEAD/…
jpardogo

37

Hiçbir şeyi genişletmeden çok basit bir şekilde çözüldü

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

büyü gibi çalışmak


Tamam, ancak içinde sahip olabileceğiniz herhangi bir kaydırılabilir öğe için aynı sorunu yaşayacağınızı unutmayın , çünkü yalnızca üst düzey alt öğesi için dikey kaydırmaya bile izin verir (ve ICS'den daha düşük API'larda yalnızca a ise ) . ViewViewPagerSwipeRefreshLayoutListView
corsair992

@ corsair992less Tavsiyeleriniz için teşekkürler
user3896501

@ corsair992 sorunla karşı karşıya. Ben ViewPageriçeride SwipeRefrestLayoutve ViewPager vardır Listview! SwipeRefreshLayoutaşağı kaydırmama izin verin, ancak yukarı kaydırdığınızda yenileme sürecini tetikler. Herhangi bir öneri?
Muhammad Babar

1
mLayout nedir hakkında biraz daha geliştirebilir misin?
desgraci

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

22

Senin probleminle karşılaştım. SwipeRefreshLayout'u özelleştirmek sorunu çözecektir.

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

Ref: bağlantısına bakın


Algılamaya eğimi dahil etmek için en iyi çözüm budur.
nafsaka

Harika çözüm +1
Tram Nguyen

12

Bunu önceki bir cevaba dayandırdım, ancak bunun biraz daha iyi çalıştığını buldum. Hareket bir ACTION_MOVE olayı ile başlıyor ve benim deneyimime göre ACTION_UP veya ACTION_CANCEL ile bitiyor.

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

Çözüm için Thnxx
Hitesh Kushwah

9

Sadece kendileri tarafından en iyi bilinen nedenlerden ötürü, destek kitaplığı geliştirici ekibi , bir çocuk özel olarak olayın sahipliğini talep ettiğinde bile, tüm dikey sürükleme hareketi olaylarını çocuğun düzeninden zorla durdurmayı uygun gördü SwipeRefreshLayout. Kontrol ettikleri tek şey, ana alt öğesinin dikey kaydırma durumunun sıfırda olmasıdır (alt öğesinin dikey olarak kaydırılabilir olması durumunda). requestDisallowInterceptTouchEvent()Yöntem boş gövdeli geçersiz kılınan olmuş ve (o kadar) aydınlatıcı comment "Hayır" oldu.

Bu sorunu çözmenin en kolay yolu, sınıfı destek kitaplığından projenize kopyalamak ve yöntemi geçersiz kılmayı kaldırmaktır. ViewGroup'nin uygulaması, işlemek için dahili durumu kullanır onInterceptTouchEvent(), bu nedenle yöntemi tekrar geçersiz kılıp çoğaltamazsınız. Destek kitaplığı uygulamasını gerçekten geçersiz kılmak istiyorsanız , o zaman çağrı üzerine özel bir bayrak ayarlamanız ve buna göre davranışı requestDisallowInterceptTouchEvent()geçersiz kılmanız (veya muhtemelen hacklemeniz ) gerekir.onInterceptTouchEvent()onTouchEvent()canChildScrollUp()


Dostum, bu çok zor. Keşke bunu yapmasalardı. Çekmeyi yenilemek ve satır öğelerini hızlıca kaydırmak için etkinleştirmek istediğim bir listem var. SwipeRefreshLayout'u yaratma biçimleri, bazı çılgın çalışmalar olmadan bunu neredeyse imkansız hale getiriyor.
Jessie A. Morris

3

Nhasan'ın çözümüyle ilgili bir sorun var:

Eğer tetikleyiciler olduğu yatay süpürme setEnabled(false)çağrı SwipeRefreshLayoutiçinde OnPageChangeListenerolur SwipeRefreshLayoutzaten tanınan bir Çekme Reload-to ancak henüz bildirim geri arama, animasyon kaybolur fakat iç durumunu aramadı SwipeRefreshLayoutdiye sonsuza "ferahlatıcı" konulu kalır durumu sıfırlayabilen bildirim geri aramaları çağrılır. Kullanıcı açısından bakıldığında, bu, tüm çekme hareketleri tanınmadığından, Yeniden Yüklemek için Çekin artık çalışmadığı anlamına gelir.

Buradaki sorun, disable(false)çağrının döndürücünün animasyonunu kaldırması ve bildirim geri çağrısının, onAnimationEndbu şekilde sıra dışı ayarlanmış bu döndürücü için dahili bir AnimationListener yönteminden çağrılmasıdır .

Kuşkusuz bu durumu kışkırtmak için en hızlı parmakları olan testçimiz aldı ama gerçekçi senaryolarda da ara sıra olabilir.

Bunu düzeltmek için bir çözüm, onInterceptTouchEventyöntemi SwipeRefreshLayoutaşağıdaki gibi geçersiz kılmaktır:

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

MySwipeRefreshLayoutDüzeninizde kullanın - mhasan çözümündeki kodu dosyalayın ve değiştirin

...

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

...

1
Ben de seninkiyle aynı sorunu yaşadım. Sadece bu pastebin.com/XmfNsDKQ ile nhasan'ın çözümünü değiştirdi .
Amit Jayant

3

ViewPager2 için bir çözüm buldum. Sürükleme duyarlılığını azaltmak için yansımayı şu şekilde kullanıyorum:

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

Benim için bir cazibe gibi çalışıyor.


0

ViewPager, sırasıyla SwiprRefreshLayout'a yerleştirilen dikey olarak kaydırılabilir bir konteynere yerleştirildiğinde @huu duy cevabıyla ilgili bir sorun olabilir.İçerik kaydırılabilir konteyner tamamen yukarı kaydırılmamışsa, aynı yukarı kaydırma hareketinde yenilemek için kaydırmayı etkinleştirin. Aslında, iç kabı kaydırmaya başladığınızda ve parmağınızı istemeden mTouchSlop'tan daha fazla yatay olarak hareket ettirdiğinizde (varsayılan olarak 8dp'dir), önerilen CustomSwipeToRefresh bu hareketi reddeder. Bu nedenle, kullanıcının yenilemeye başlamak için bir kez daha denemesi gerekir. Bu, kullanıcı için garip görünebilir. Orijinal SwipeRefreshLayout'un kaynak kodunu destek kitaplığından projeme çıkardım ve onInterceptTouchEvent () 'i yeniden yazdım.

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'daki örnek projeme bakın .


0

2020-10-17

@ nhasan mükemmel cevabına minimal bir katkı .

Eğer göç varsa ViewPageretmek ViewPager2, kullanım

registerOnPageChangeCallback kaydırma olaylarını dinleme yöntemi

mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageScrollStateChanged(int state) {
        super.onPageScrollStateChanged(state);
        swipe.setEnabled(state == ViewPager2.SCROLL_STATE_IDLE);
    }
}); 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.