Ortada yatay kaydırma çıtçıtını görüntüleyin


89

RecyclerView kullanarak burada atlı karıncaya benzer bir görünüm oluşturmaya çalışıyorum, öğenin her seferinde bir öğe kaydırırken ekranın ortasına oturmasını istiyorum. Kullanmayı denedimrecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);

ancak görünüm hala sorunsuz bir şekilde kaydırılıyor, ayrıca kaydırma dinleyicisini kullanarak kendi mantığımı şu şekilde uygulamaya çalıştım:

recyclerView.setOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    Log.v("Offset ", recyclerView.getWidth() + "");
                    if (newState == 0) {
                        try {
                               recyclerView.smoothScrollToPosition(layoutManager.findLastVisibleItemPosition());
                                recyclerView.scrollBy(20,0);
                            if (layoutManager.findLastVisibleItemPosition() >= recyclerView.getAdapter().getItemCount() - 1) {
                                Beam refresh = new Beam();
                                refresh.execute(createUrl());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

Sağdan sola kaydırma şimdi iyi çalışıyor, ancak tam tersi değil, burada neyi özlüyorum?

Yanıtlar:


165

İle LinearSnapHelperbu artık çok kolay.

Tek yapmanız gereken şudur:

SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);

Güncelleme

25.1.0'dan beri mevcuttur ve benzer efekt PagerSnapHelperelde etmeye yardımcı olabilir ViewPager. Kullanacağınız gibi kullanın LinearSnapHelper.

Eski çözüm:

Eğer onun gibi davranmasını istiyorsanız, bunun ViewPageryerine şunu deneyin:

LinearSnapHelper snapHelper = new LinearSnapHelper() {
    @Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
        View centerView = findSnapView(layoutManager);
        if (centerView == null) 
            return RecyclerView.NO_POSITION;

        int position = layoutManager.getPosition(centerView);
        int targetPosition = -1;
        if (layoutManager.canScrollHorizontally()) {
            if (velocityX < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        if (layoutManager.canScrollVertically()) {
            if (velocityY < 0) {
                targetPosition = position - 1;
            } else {
                targetPosition = position + 1;
            }
        }

        final int firstItem = 0;
        final int lastItem = layoutManager.getItemCount() - 1;
        targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem));
        return targetPosition;
    }
};
snapHelper.attachToRecyclerView(recyclerView);

Yukarıdaki uygulama, büyüklükten bağımsız olarak hızın yönüne bağlı olarak sadece geçerli öğenin yanındaki konumu (ortalanmış) döndürür.

İlki, Destek Kitaplığı 24.2.0 sürümünde bulunan birinci taraf bir çözümdür. Bunu uygulama modülünüze eklemeniz build.gradleveya güncellemeniz gerektiği anlamına gelir.

compile "com.android.support:recyclerview-v7:24.2.0"

Bu benim için işe yaramadı (ancak @Albert Vila Calvo'nun çözümü işe yaradı). Uyguladın mı? Ondan yöntemleri geçersiz kılmamız mı gerekiyor? Sınıfın tüm işi kutudan
çıkarması gerektiğini düşünürdüm

Evet, dolayısıyla cevap. Ancak tam olarak test etmedim. Açık olmak gerekirse, bunu LinearLayoutManagergörüşlerin hepsinin normal boyutta olduğu bir yatay ile yaptım . Yukarıdaki kod parçacığından başka hiçbir şeye gerek yoktur.
razzledazzle

@galaxigirl özür dilerim, ne demek istediğinizi anladım ve bunu kabul ederek bazı düzenlemeler yaptım, lütfen yorum yapın. Bu LinearSnapHelper,.
razzledazzle

Bu benim için tam olarak çalıştı. @galaxigirl'in bu yöntemi geçersiz kılması, doğrusal yakalama yardımcısının aksi takdirde kaydırma yerleşirken yalnızca sonraki / önceki öğeye değil en yakın öğeye yapışmasına yardımcı olur. Bunun için çok teşekkürler, razzledazzle
AA_PV

LinearSnapHelper benim için işe yarayan ilk çözümdü, ancak PagerSnapHelper'ın kaydırmaya daha iyi bir animasyon verdiği görülüyor.
LeonardoSibela

77

Google I / O 2019 Güncellemesi

ViewPager2 burada!

Google , 'Android'deki Yenilikler' (diğer adıyla 'Android açılış konuşması') konuşmasında RecyclerView'e dayalı yeni bir ViewPager üzerinde çalıştıklarını duyurdu !

Slaytlardan:

ViewPager gibi ama daha iyi

  • ViewPager'dan kolay geçiş
  • RecyclerView'e göre
  • Sağdan Sola mod desteği
  • Dikey sayfalara izin verir
  • Geliştirilmiş veri seti değişiklik bildirimleri

En son sürümü buradan ve sürüm notlarını buradan kontrol edebilirsiniz . Bir de bulunmaktadır resmi numune .

Kişisel görüş: Bunun gerçekten gerekli bir ilave olduğunu düşünüyorum. Son zamanlarda PagerSnapHelper sağa salınan solda süresiz olarak çok sorun yaşadım - açtığım bileti görün .


Yeni cevap (2016)

Artık sadece bir SnapHelper kullanabilirsiniz .

ViewPager'a benzer bir ortaya hizalı yapışma davranışı istiyorsanız, PagerSnapHelper'ı kullanın :

SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

Ayrıca bir LinearSnapHelper vardır . Ben denedim ve eğer enerji ile savaşırsanız, 2 öğeyi 1 atışla kaydırır. Şahsen ben hoşuma gitmedi, ancak kendi kendinize karar verin - denemek sadece saniyeler sürer.


Orijinal cevap (2016)

Sonra birçok 3 farklı çözümler çalışmakla saat SO burada buldum nihayet taklit çok yakından davranış bulduğu bir çözüm inşa ettik ViewPager.

Çözüm, @eDizzle çözümüne dayanıyor ve bunun neredeyse bir ViewPager.

Önemli: Öğelerimin RecyclerViewgenişliği ekranla tamamen aynı. Diğer bedenlerle denemedim. Ayrıca yatay olarak kullanıyorum LinearLayoutManager. Dikey kaydırma istiyorsanız kodu uyarlamanız gerekeceğini düşünüyorum.

İşte kodunuz var:

public class SnappyRecyclerView extends RecyclerView {

    // Use it with a horizontal LinearLayoutManager
    // Based on https://stackoverflow.com/a/29171652/4034572

    public SnappyRecyclerView(Context context) {
        super(context);
    }

    public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean fling(int velocityX, int velocityY) {

        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

        int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;

        // views on the screen
        int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
        View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
        int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
        View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

        // distance we need to scroll
        int leftMargin = (screenWidth - lastView.getWidth()) / 2;
        int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
        int leftEdge = lastView.getLeft();
        int rightEdge = firstView.getRight();
        int scrollDistanceLeft = leftEdge - leftMargin;
        int scrollDistanceRight = rightMargin - rightEdge;

        if (Math.abs(velocityX) < 1000) {
            // The fling is slow -> stay at the current page if we are less than half through,
            // or go to the next page if more than half through

            if (leftEdge > screenWidth / 2) {
                // go to next page
                smoothScrollBy(-scrollDistanceRight, 0);
            } else if (rightEdge < screenWidth / 2) {
                // go to next page
                smoothScrollBy(scrollDistanceLeft, 0);
            } else {
                // stay at current page
                if (velocityX > 0) {
                    smoothScrollBy(-scrollDistanceRight, 0);
                } else {
                    smoothScrollBy(scrollDistanceLeft, 0);
                }
            }
            return true;

        } else {
            // The fling is fast -> go to next page

            if (velocityX > 0) {
                smoothScrollBy(scrollDistanceLeft, 0);
            } else {
                smoothScrollBy(-scrollDistanceRight, 0);
            }
            return true;

        }

    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);

        // If you tap on the phone while the RecyclerView is scrolling it will stop in the middle.
        // This code fixes this. This code is not strictly necessary but it improves the behaviour.

        if (state == SCROLL_STATE_IDLE) {
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

            int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;

            // views on the screen
            int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
            View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition);
            int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
            View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition);

            // distance we need to scroll
            int leftMargin = (screenWidth - lastView.getWidth()) / 2;
            int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
            int leftEdge = lastView.getLeft();
            int rightEdge = firstView.getRight();
            int scrollDistanceLeft = leftEdge - leftMargin;
            int scrollDistanceRight = rightMargin - rightEdge;

            if (leftEdge > screenWidth / 2) {
                smoothScrollBy(-scrollDistanceRight, 0);
            } else if (rightEdge < screenWidth / 2) {
                smoothScrollBy(scrollDistanceLeft, 0);
            }
        }
    }

}

Zevk almak!


OnScrollStateChanged (); aksi takdirde RecyclerView'ı yavaşça kaydırırsak küçük bir hata oluşur. Teşekkürler!
Mauricio Sartori

Kenar boşluklarını desteklemek için (android: layout_marginStart = "16dp") int screenwidth = getWidth (); ve işe yarıyor gibi görünüyor.
eigil

AWESOOOMMEEEEEEEEE
raditya gumay

1
@galaxigirl hayır, henüz LinearSnapHelper'ı denemedim. İnsanların resmi çözümden haberdar olması için yanıtı güncelledim. Çözümüm uygulamamda sorunsuz çalışıyor.
Albert Vila Calvo

1
@AlbertVilaCalvo LinearSnapHelper'ı denedim. LinearSnapHelper, bir Scroller ile çalışır. Yerçekimi ve fırlatma hızına göre kaydırır ve çırpar. Hız arttıkça daha fazla sayfa kaydırılır. ViewPager benzeri bir davranış için bunu tavsiye etmem.
mipreamble

47

Amaç RecyclerViewmimik yapmaksa, davranışı ViewPageroldukça kolaydır.

RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);

LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
SnapHelper snapHelper = new PagerSnapHelper();
recyclerView.setLayoutManager(layoutManager);
snapHelper.attachToRecyclerView(mRecyclerView);

Kullanarak PagerSnapHelperşu davranışları elde edebilirsiniz:ViewPager


4
LinearSnapHelperPagerSnapHelper
Onun

1
Evet, içinde ani bir etki yokLinearSnapHelper
Boonya Kitpitak

1
bu, öğeyi görünümde yapıştırır kaydırılabilir hale getirir
Sam

@BoonyaKitpitak, denedim LinearSnapHelperve orta öğeye oturdu . PagerSnapHelperkolayca kaydırılmasını engeller (en azından bir resim listesi).
CoolMind

@BoonyaKitpitak bana bunu nasıl yapacağımı söyleyebilir misin, cente'de bir öğe tamamen görünür ve yan öğeler yarı görünür durumda mı?
Rucha Bhatt Joshi

30

Ters yöne gitmek için findFirstVisibleItemPosition kullanmanız gerekir. Ve kaydırmanın hangi yönde olduğunu tespit etmek için, ya fırlatma hızını ya da x'teki değişikliği almanız gerekir. Bu soruna sizden biraz farklı bir açıdan yaklaştım.

RecyclerView sınıfını genişleten yeni bir sınıf oluşturun ve ardından RecyclerView'ın fling yöntemini şu şekilde geçersiz kılın:

@Override
public boolean fling(int velocityX, int velocityY) {
    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();

//these four variables identify the views you see on screen.
    int lastVisibleView = linearLayoutManager.findLastVisibleItemPosition();
    int firstVisibleView = linearLayoutManager.findFirstVisibleItemPosition();
    View firstView = linearLayoutManager.findViewByPosition(firstVisibleView);
    View lastView = linearLayoutManager.findViewByPosition(lastVisibleView);

//these variables get the distance you need to scroll in order to center your views.
//my views have variable sizes, so I need to calculate side margins separately.     
//note the subtle difference in how right and left margins are calculated, as well as
//the resulting scroll distances.
    int leftMargin = (screenWidth - lastView.getWidth()) / 2;
    int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth();
    int leftEdge = lastView.getLeft();
    int rightEdge = firstView.getRight();
    int scrollDistanceLeft = leftEdge - leftMargin;
    int scrollDistanceRight = rightMargin - rightEdge;

//if(user swipes to the left) 
    if(velocityX > 0) smoothScrollBy(scrollDistanceLeft, 0);
    else smoothScrollBy(-scrollDistanceRight, 0);

    return true;
}

ScreenWidth nerede tanımlanır?
ltsai

@Itsai: Oops! İyi soru. Sınıfın başka bir yerinde belirlediğim bir değişken. SmoothScrollBy yöntemi, kaydırmasını istediğiniz piksel sayısını gerektirdiğinden, aygıtın yoğunluk pikselleri değil, piksel cinsinden ekran genişliğidir.
eDizzle

3
@ltsai screenWidth yerine getWidth () (recyclerview.getWidth ()) olarak değiştirebilirsiniz. RecyclerView ekran genişliğinden daha küçük olduğunda screenWidth düzgün çalışmıyor.
VAdaihiep

RecyclerView'ı genişletmeye çalıştım ama "Sınıf özel RecyclerView şişirilirken hata oluştu" alıyorum. Yardım edebilir misin? Teşekkürler

@bEtTyBarnes: Bunu başka bir soru beslemesinde aramak isteyebilirsiniz. Buradaki ilk soruyla oldukça kapsam dışı.
eDizzle

6

Sadece eklemek paddingve marginiçin recyclerViewve recyclerView item:

Öğeyi görüntüle:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parentLayout"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginLeft="8dp" <!-- here -->
    android:layout_marginRight="8dp" <!-- here  -->
    android:layout_width="match_parent"
    android:layout_height="200dp">

   <!-- child views -->

</RelativeLayout>

Görüntüle:

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="8dp" <!-- here -->
    android:paddingRight="8dp" <!-- here -->
    android:clipToPadding="false" <!-- important!-->
    android:scrollbars="none" />

ve ayarlayın PagerSnapHelper:

int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4;
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

dp'den piksele:

public static int dpToPx(int dp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}

sonuç:

görüntü açıklamasını buraya girin


Öğeler arasında değişken dolgu elde etmenin bir yolu var mı? Örneğin, ilk ve son kartın ortalanmış olması gerekmiyorsa, sonraki / önceki kartları ortalanmakta olan ara kartlar yerine daha fazla göstermek için?
RamPrasadBismil

2

Çözümüm:

/**
 * Horizontal linear layout manager whose smoothScrollToPosition() centers
 * on the target item
 */
class ItemLayoutManager extends LinearLayoutManager {

    private int centeredItemOffset;

    public ItemLayoutManager(Context context) {
        super(context, LinearLayoutManager.HORIZONTAL, false);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller linearSmoothScroller = new Scroller(recyclerView.getContext());
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

    public void setCenteredItemOffset(int centeredItemOffset) {
        this.centeredItemOffset = centeredItemOffset;
    }

    /**
     * ********** Inner Classes **********
     */

    private class Scroller extends LinearSmoothScroller {

        public Scroller(Context context) {
            super(context);
        }

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return ItemLayoutManager.this.computeScrollVectorForPosition(targetPosition);
        }

        @Override
        public int calculateDxToMakeVisible(View view, int snapPreference) {
            return super.calculateDxToMakeVisible(view, SNAP_TO_START) + centeredItemOffset;
        }
    }
}

Bu düzen yöneticisini RecycledView'e iletiyorum ve öğeleri ortalamak için gereken ofseti ayarlıyorum. Tüm öğelerim aynı genişliğe sahip, bu nedenle sabit ofset tamam


0

PagerSnapHelperile çalışmaz GridLayoutManagerbu koşulda benim çözümdür böylece, spanCount> 1 ile:

class GridPagerSnapHelper : PagerSnapHelper() {
    override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
        val forwardDirection = if (layoutManager?.canScrollHorizontally() == true) {
            velocityX > 0
        } else {
            velocityY > 0
        }
        val centerPosition = super.findTargetSnapPosition(layoutManager, velocityX, velocityY)
        return centerPosition +
            if (forwardDirection) (layoutManager as GridLayoutManager).spanCount - 1 else 0
    }
}
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.