android - lista - recyclerview cardview horizontal
RecyclerView snap scroll horizontal en el centro (7)
Estoy tratando de hacer una vista de carrusel aquí usando RecyclerView, quiero que el elemento se ajuste en el centro de la pantalla al desplazarse, un elemento a la vez.
He intentado usar
recyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
pero la vista todavía se desplaza suavemente, también he intentado implementar mi propia lógica usando el detector de desplazamiento de la siguiente manera:
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í?
Con
LinearSnapHelper
, esto ahora es muy fácil.
Todo lo que necesitas hacer es esto:
SnapHelper helper = new LinearSnapHelper();
helper.attachToRecyclerView(recyclerView);
Actualizar
Disponible desde 25.1.0,
PagerSnapHelper
puede ayudar a lograr
ViewPager
efecto similar a
ViewPager
.
Úselo como lo haría con
LinearSnapHelper
.
Antigua solución:
Si desea que se
ViewPager
al
ViewPager
, intente esto en su lugar:
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);
La implementación anterior solo devuelve la posición junto al elemento actual (centrado) en función de la dirección de la velocidad, independientemente de la magnitud.
La primera es una solución de primera parte incluida en la Biblioteca de soporte versión 24.2.0.
Lo que significa que debe agregar esto al
build.gradle
su módulo de
build.gradle
o actualizarlo.
compile "com.android.support:recyclerview-v7:24.2.0"
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 que usted tiene.
Cree una nueva clase que amplíe la clase RecyclerView y luego anule el método de lanzamiento de RecyclerView de la siguiente manera:
@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;
}
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
Si el objetivo es hacer que
RecyclerView
imite el comportamiento de
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 usar
PagerSnapHelper
puede obtener el comportamiento como
ViewPager
Simplemente agregue
padding
y
margin
a
recyclerView
y el
recyclerView item
:
elemento recicladorVer:
<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>
reciclador Vista:
<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 establezca
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:
PagerSnapHelper
no funciona con
GridLayoutManager
con spanCount> 1, por lo que mi solución en estas circunstancias 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
}
}
Actualización de Google I / O 2019:
ViewPager2 está aquí!
¡Google acaba de anunciar en la charla ''What''s New in Android'' (también conocido como ''The Android keynote'') que están trabajando en un nuevo ViewPager basado en RecyclerView!
De las diapositivas:
Como ViewPager, pero mejor
- Fácil migración desde ViewPager
- Basado en RecyclerView
- Soporte de modo de derecha a izquierda
- Permite paginación vertical
- Notificaciones mejoradas de cambio de conjunto de datos
Puede consultar la última versión here y las notas de la versión here . También hay una muestra oficial .
Opinión personal:
creo que esta es una adición realmente necesaria.
Recientemente he tenido muchos problemas con el
PagerSnapHelper
oscilando de izquierda a derecha de forma indefinida
; vea
el ticket
que he abierto.
Nueva respuesta (2016):
Ahora puede usar un SnapHelper .
Si desea un comportamiento de alineación alineado al centro similar a ViewPager , use un PagerSnapHelper :
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
También hay un LinearSnapHelper . Lo he intentado y si lanzas con energía, entonces desplaza 2 elementos con 1 lanzamiento. Personalmente, no me gustó, pero decidí por ti 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 he creado una solución que imita muy de cerca el comportamiento encontrado en un
ViewPager
.
La solución se basa en la
solution
@eDizzle, que creo que he mejorado lo suficiente como para decir que funciona casi como un
ViewPager
.
Importante: el ancho de mis elementos
RecyclerView
es exactamente el mismo que el de la pantalla.
No lo he probado con otros tamaños.
También lo uso con un
LinearLayoutManager
horizontal.
Creo que necesitará adaptar el código si desea desplazamiento vertical.
Aquí tienes el código:
public class SnappyRecyclerView extends RecyclerView {
// Use it with a horizontal LinearLayoutManager
// Based on https://.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!