Estoy tratando de hacer una vista similar a un carrusel aquí usando RecyclerView, quiero que el elemento se ajuste en el medio de la pantalla cuando se desplaza, un elemento a la vez. He intentado usarrecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
pero la vista todavía se desplaza sin problemas, también intenté implementar mi propia lógica usando el oyente de desplazamiento así:
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();
}
}
El deslizamiento de derecha a izquierda funciona bien ahora, pero no al revés, ¿qué me estoy perdiendo aquí?
fuente
LinearLayoutManager
donde todas las vistas eran de tamaño regular. No se necesita nada más que el fragmento de arriba.LinearSnapHelper
.Actualización de Google I / O 2019
¡ViewPager2 ya está aquí!
Google acaba de anunciar en la charla 'Novedades de Android' (también conocido como 'La nota clave de Android') que están trabajando en un nuevo ViewPager basado en RecyclerView.
De las diapositivas:
Puede consultar la última versión aquí y las notas de la versión aquí . También hay una muestra oficial .
Opinión personal: Creo que esta es una adición realmente necesaria. Recientemente tuve muchos problemas con la
PagerSnapHelper
oscilación de izquierda a derecha indefinidamente ; vea el boleto que abrí.Nueva respuesta (2016)
Ahora puede usar SnapHelper .
Si desea un comportamiento de ajuste alineado al centro similar a ViewPager , use PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
También hay un LinearSnapHelper . Lo he probado y si arrojas con energía, se desplaza 2 elementos con 1 lanzamiento. Personalmente, no me gustó, pero solo decide tú mismo: intentarlo solo lleva unos segundos.
Respuesta original (2016)
Después de muchas horas de probar 3 soluciones diferentes que se encuentran aquí en SO, finalmente construí una solución que imita muy de cerca el comportamiento encontrado en un
ViewPager
.La solución se basa en la solución @eDizzle , que creo que he mejorado lo suficiente como para decir que funciona casi como un
ViewPager
.Importante: el
RecyclerView
ancho de mis artículos es exactamente el mismo que el de la pantalla. No lo he probado con otros tamaños. También lo uso con una horizontalLinearLayoutManager
. Creo que necesitará adaptar el código si desea un desplazamiento vertical.Aquí tienes el código:
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); } } } }
¡Disfrutar!
fuente
Si el objetivo es
RecyclerView
imitar el comportamiento,ViewPager
hay un enfoque bastante sencillo.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);
Al usarlo
PagerSnapHelper
, puede obtener el comportamiento comoViewPager
fuente
LinearSnapHelper
lugar dePagerSnapHelper
y funciona para míLinearSnapHelper
LinearSnapHelper
y se ajusta a un elemento del medio.PagerSnapHelper
impide desplazarse fácilmente (al menos, una lista de imágenes).Debe usar findFirstVisibleItemPosition para ir en la dirección opuesta. Y para detectar en qué dirección estaba el deslizamiento, necesitará obtener la velocidad de lanzamiento o el cambio en x. Abordé este problema desde un ángulo ligeramente diferente al tuyo.
Cree una nueva clase que amplíe la clase RecyclerView y luego anule el método de lanzamiento de RecyclerView así:
@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; }
fuente
Sólo tiene que añadir
padding
ymargin
arecyclerView
yrecyclerView item
:reciclador
<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>
recicladorView:
<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" />
y establecer
PagerSnapHelper
:int displayWidth = Resources.getSystem().getDisplayMetrics().widthPixels; parentLayout.getLayoutParams().width = displayWidth - Utils.dpToPx(16) * 4; SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
dp a px:
public static int dpToPx(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics()); }
resultado:
fuente
Mi solución:
/** * 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; } } }
Paso este administrador de diseño a RecycledView y configuro el desplazamiento requerido para centrar los elementos. Todos mis artículos tienen el mismo ancho, por lo que el desplazamiento constante está bien
fuente
PagerSnapHelper
no funciona conGridLayoutManager
spanCount> 1, por lo que mi solución en esta circunstancia es: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 } }
fuente