Próbuję tutaj utworzyć widok podobny do karuzeli przy użyciu RecyclerView, chcę, aby element był przyciągany na środku ekranu podczas przewijania, jeden element na raz. Próbowałem użyćrecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
ale widok nadal płynnie się przewija, próbowałem też zaimplementować własną logikę za pomocą nasłuchiwania przewijania:
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();
}
}
Przesunięcie od prawej do lewej działa teraz dobrze, ale nie na odwrót, czego tu brakuje?
źródło
LinearLayoutManager
gdzie wszystkie widoki były regularne. Nie jest potrzebne nic poza powyższym fragmentem.LinearSnapHelper
.Aktualizacja Google I / O 2019
ViewPager2 jest tutaj!
Firma Google właśnie ogłosiła podczas wykładu „Co nowego w Androidzie” (znanego również jako „Myśl przewodnia Androida”), że pracuje nad nowym ViewPager opartym na RecyclerView!
Ze slajdów:
Możesz sprawdzić najnowszą wersję tutaj, a informacje o wydaniu tutaj . Istnieje również oficjalna próbka .
Osobista opinia: myślę, że to naprawdę potrzebny dodatek. Ostatnio miałem dużo problemów z
PagerSnapHelper
oscylacją w lewo w prawo w nieskończoność - zobacz bilet , który otworzyłem.Nowa odpowiedź (2016)
Możesz teraz po prostu użyć SnapHelpera .
Jeśli chcesz, aby zachowanie przyciągania wyrównane do środka było podobne do ViewPager, użyj PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
Istnieje również LinearSnapHelper . Wypróbowałem to i jeśli rzucasz energią, przewija 2 przedmioty z 1 rzutem. Osobiście mi się to nie podobało, ale po prostu zdecyduj sam - wypróbowanie tego zajmuje tylko kilka sekund.
Oryginalna odpowiedź (2016)
Po wielu godzinach próbowania 3 różnych rozwiązań znalezionych tutaj w SO w końcu zbudowałem rozwiązanie, które bardzo dokładnie naśladuje zachowanie występujące w pliku
ViewPager
.Rozwiązanie jest oparte na rozwiązaniu @eDizzle , które, jak sądzę, poprawiłem na tyle, aby powiedzieć, że działa prawie jak plik
ViewPager
.Ważne:
RecyclerView
szerokość moich przedmiotów jest dokładnie taka sama jak na ekranie. Nie próbowałem z innymi rozmiarami. Używam go również z poziomymLinearLayoutManager
. Myślę, że będziesz musiał dostosować kod, jeśli chcesz przewijać w pionie.Tutaj masz kod:
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); } } } }
Cieszyć się!
źródło
Jeśli celem jest
RecyclerView
naśladowanie zachowania,ViewPager
to podejście jest dość łatweRecyclerView 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);
Używając
PagerSnapHelper
, możesz uzyskać takie zachowanie jakViewPager
źródło
LinearSnapHelper
zamiastPagerSnapHelper
i to działa dla mnieLinearSnapHelper
LinearSnapHelper
i przeskakuje do środkowego elementu.PagerSnapHelper
utrudnia łatwe przewijanie (przynajmniej listy obrazów).Aby iść w przeciwnym kierunku, musisz użyć funkcji findFirstVisibleItemPosition. Aby wykryć, w którym kierunku był zamach, musisz uzyskać prędkość lotu lub zmianę w x. Podszedłem do tego problemu z nieco innego punktu widzenia niż ty.
Utwórz nową klasę, która rozszerza klasę RecyclerView, a następnie przesłoń metodę fling RecyclerView w następujący sposób:
@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; }
źródło
Po prostu dodaj
padding
imargin
dorecyclerView
irecyclerView item
:recyklerZobacz element:
<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>
recyklerView:
<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" />
i ustaw
PagerSnapHelper
:int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels; parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4; SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
dp do px:
public static int dpToPx(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics()); }
wynik:
źródło
Moje rozwiązanie:
/** * 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; } } }
Przekazuję tego menedżera układu do RecycledView i ustawiam przesunięcie wymagane do wyśrodkowania elementów. Wszystkie moje przedmioty mają tę samą szerokość, więc stałe przesunięcie jest w porządku
źródło
PagerSnapHelper
nie działa zGridLayoutManager
spanCount> 1, więc moim rozwiązaniem w tej sytuacji jest: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 } }
źródło