Espacio entre columnas de Android Recyclerview GridLayoutManager

¿Cómo se establece el espaciado entre columnas con un RecyclerView usando un GridLayoutManager? Establecer el margen / relleno dentro de mi diseño no tiene ningún efecto.

RecyclerViews admite el concepto de ItemDecoration : compensaciones especiales y dibujo alrededor de cada elemento. Como se ve en esta respuesta , puede usar

 public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int space; public SpacesItemDecoration(int space) { this.space = space; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.left = space; outRect.right = space; outRect.bottom = space; // Add top margin only for the first item to avoid double space between items if (parent.getChildLayoutPosition(view) == 0) { outRect.top = space; } else { outRect.top = 0; } } } 

Luego agrégalo a través de

 mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view); int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing); mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels)); 

El siguiente código funciona bien, y cada columna tiene el mismo ancho:

 public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int spanCount; private int spacing; private boolean includeEdge; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view); // item position int column = position % spanCount; // item column if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } } 

Uso

1. sin ventaja

enter image description here

 int spanCount = 3; // 3 columns int spacing = 50; // 50px boolean includeEdge = false; recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge)); 

2. con borde

enter image description here

 int spanCount = 3; // 3 columns int spacing = 50; // 50px boolean includeEdge = true; recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge)); 

La siguiente es la solución simple paso a paso si desea un espacio equitativo alrededor de los artículos y el mismo tamaño de artículo.

ItemOffsetDecoration

 public class ItemOffsetDecoration extends RecyclerView.ItemDecoration { private int mItemOffset; public ItemOffsetDecoration(int itemOffset) { mItemOffset = itemOffset; } public ItemOffsetDecoration(@NonNull Context context, @DimenRes int itemOffsetId) { this(context.getResources().getDimensionPixelSize(itemOffsetId)); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); outRect.set(mItemOffset, mItemOffset, mItemOffset, mItemOffset); } } 

Implementación

En su código fuente, agregue ItemOffsetDecoration a su RecyclerView. El valor de compensación del artículo debe ser la mitad del tamaño del valor real que desea agregar como espacio entre los elementos.

 mRecyclerView.setLayoutManager(new GridLayoutManager(context, NUM_COLUMNS); ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(context, R.dimen.item_offset); mRecyclerView.addItemDecoration(itemDecoration); 

Además, establezca el valor de compensación de elementos como relleno para su RecyclerView y especifique android:clipToPadding=false .

  

Prueba esto. Cuidará el espacio equitativo alrededor. Funciona con List, Grid y StaggeredGrid.

Editado

El código actualizado debe manejar la mayoría de los casos de esquina con tramos, orientación, etc. Tenga en cuenta que si usa setSpanSizeLookup () con GridLayoutManager, se recomienda establecer setSpanIndexCacheEnabled () por motivos de rendimiento.

Tenga en cuenta que parece que con StaggeredGrid, parece haber un error en el que el índice de los niños se vuelve raro y difícil de rastrear, por lo que el siguiente código podría no funcionar muy bien con StaggeredGridLayoutManager.

 public class ListSpacingDecoration extends RecyclerView.ItemDecoration { private static final int VERTICAL = OrientationHelper.VERTICAL; private int orientation = -1; private int spanCount = -1; private int spacing; private int halfSpacing; public ListSpacingDecoration(Context context, @DimenRes int spacingDimen) { spacing = context.getResources().getDimensionPixelSize(spacingDimen); halfSpacing = spacing / 2; } public ListSpacingDecoration(int spacingPx) { spacing = spacingPx; halfSpacing = spacing / 2; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (orientation == -1) { orientation = getOrientation(parent); } if (spanCount == -1) { spanCount = getTotalSpan(parent); } int childCount = parent.getLayoutManager().getItemCount(); int childIndex = parent.getChildAdapterPosition(view); int itemSpanSize = getItemSpanSize(parent, childIndex); int spanIndex = getItemSpanIndex(parent, childIndex); /* INVALID SPAN */ if (spanCount < 1) return; setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex); } protected void setSpacings(Rect outRect, RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) { outRect.top = halfSpacing; outRect.bottom = halfSpacing; outRect.left = halfSpacing; outRect.right = halfSpacing; if (isTopEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) { outRect.top = spacing; } if (isLeftEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) { outRect.left = spacing; } if (isRightEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) { outRect.right = spacing; } if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) { outRect.bottom = spacing; } } @SuppressWarnings("all") protected int getTotalSpan(RecyclerView parent) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getSpanCount(); } else if (mgr instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager) mgr).getSpanCount(); } else if (mgr instanceof LinearLayoutManager) { return 1; } return -1; } @SuppressWarnings("all") protected int getItemSpanSize(RecyclerView parent, int childIndex) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex); } else if (mgr instanceof StaggeredGridLayoutManager) { return 1; } else if (mgr instanceof LinearLayoutManager) { return 1; } return -1; } @SuppressWarnings("all") protected int getItemSpanIndex(RecyclerView parent, int childIndex) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount); } else if (mgr instanceof StaggeredGridLayoutManager) { return childIndex % spanCount; } else if (mgr instanceof LinearLayoutManager) { return 0; } return -1; } @SuppressWarnings("all") protected int getOrientation(RecyclerView parent) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof LinearLayoutManager) { return ((LinearLayoutManager) mgr).getOrientation(); } else if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getOrientation(); } else if (mgr instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager) mgr).getOrientation(); } return VERTICAL; } protected boolean isLeftEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) { if (orientation == VERTICAL) { return spanIndex == 0; } else { return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex); } } protected boolean isRightEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) { if (orientation == VERTICAL) { return (spanIndex + itemSpanSize) == spanCount; } else { return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex); } } protected boolean isTopEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) { if (orientation == VERTICAL) { return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex); } else { return spanIndex == 0; } } protected boolean isBottomEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) { if (orientation == VERTICAL) { return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex); } else { return (spanIndex + itemSpanSize) == spanCount; } } protected boolean isFirstItemEdgeValid(boolean isOneOfFirstItems, RecyclerView parent, int childIndex) { int totalSpanArea = 0; if (isOneOfFirstItems) { for (int i = childIndex; i >= 0; i--) { totalSpanArea = totalSpanArea + getItemSpanSize(parent, i); } } return isOneOfFirstItems && totalSpanArea <= spanCount; } protected boolean isLastItemEdgeValid(boolean isOneOfLastItems, RecyclerView parent, int childCount, int childIndex, int spanIndex) { int totalSpanRemaining = 0; if (isOneOfLastItems) { for (int i = childIndex; i < childCount; i++) { totalSpanRemaining = totalSpanRemaining + getItemSpanSize(parent, i); } } return isOneOfLastItems && (totalSpanRemaining <= spanCount - spanIndex); } } 

Espero eso ayude.

El siguiente código manejará StaggeredGridLayoutManager, GridLayoutManager y LinearLayoutManager.

 public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int halfSpace; public SpacesItemDecoration(int space) { this.halfSpace = space / 2; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (parent.getPaddingLeft() != halfSpace) { parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace); parent.setClipToPadding(false); } outRect.top = halfSpace; outRect.bottom = halfSpace; outRect.left = halfSpace; outRect.right = halfSpace; } } 

Entonces úsalo

 mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin)); 

Aquí hay una solución que no requiere “spanCount” (número de columnas) Lo uso porque utilizo GridAutofitLayoutManager (calcula el número de columnas de acuerdo con el tamaño de celda requerido)

(tenga en cuenta que esto solo funcionará en GridLayoutManager )

 public class GridSpacesItemDecoration extends RecyclerView.ItemDecoration { private final boolean includeEdge; private int spacing; public GridSpacesItemDecoration(int spacing, boolean includeEdge) { this.spacing = spacing; this.includeEdge = includeEdge; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() instanceof GridLayoutManager) { GridLayoutManager layoutManager = (GridLayoutManager)parent.getLayoutManager(); int spanCount = layoutManager.getSpanCount(); int position = parent.getChildAdapterPosition(view); // item position int column = position % spanCount; // item column if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } } } 

Aquí está el GridAutofitLayoutManager si alguien está interesado:

 public class GridAutofitLayoutManager extends GridLayoutManager { private int mColumnWidth; private boolean mColumnWidthChanged = true; public GridAutofitLayoutManager(Context context, int columnWidth) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1); setColumnWidth(checkedColumnWidth(context, columnWidth)); } public GridAutofitLayoutManager(Context context,int unit, int columnWidth) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1); int pixColumnWidth = (int) TypedValue.applyDimension(unit, columnWidth, context.getResources().getDisplayMetrics()); setColumnWidth(checkedColumnWidth(context, pixColumnWidth)); } public GridAutofitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) { /* Initially set spanCount to 1, will be changed automatically later. */ super(context, 1, orientation, reverseLayout); setColumnWidth(checkedColumnWidth(context, columnWidth)); } private int checkedColumnWidth(Context context, int columnWidth) { if (columnWidth <= 0) { /* Set default columnWidth value (48dp here). It is better to move this constant to static constant on top, but we need context to convert it to dp, so can't really do so. */ columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); } return columnWidth; } public void setColumnWidth(int newColumnWidth) { if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) { mColumnWidth = newColumnWidth; mColumnWidthChanged = true; } } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { int width = getWidth(); int height = getHeight(); if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0) { int totalSpace; if (getOrientation() == VERTICAL) { totalSpace = width - getPaddingRight() - getPaddingLeft(); } else { totalSpace = height - getPaddingTop() - getPaddingBottom(); } int spanCount = Math.max(1, totalSpace / mColumnWidth); setSpanCount(spanCount); mColumnWidthChanged = false; } super.onLayoutChildren(recycler, state); } } 

Finalmente:

 mDevicePhotosView.setLayoutManager(new GridAutofitLayoutManager(getContext(), getResources().getDimensionPixelSize(R.dimen.item_size))); mDevicePhotosView.addItemDecoration(new GridSpacesItemDecoration(Util.dpToPx(getContext(), 2),true)); 

Copié el código provisto @edwardaa y lo hago perfecto para admitir RTL:

 public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int spanCount; private int spacing; private boolean includeEdge; private int headerNum; private boolean isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) { this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; this.headerNum = headerNum; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view) - headerNum; // item position if (position >= 0) { int column = position % spanCount; // item column if(isRtl) { column = spanCount - 1 - column; } if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } else { outRect.left = 0; outRect.right = 0; outRect.top = 0; outRect.bottom = 0; } } } 

Si desea FIJAR el tamaño de su elemento RecyclerView en todos los dispositivos. Puedes hacer esto

 public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int mSpanCount; private float mItemSize; public GridSpacingItemDecoration(int spanCount, int itemSize) { this.mSpanCount = spanCount; mItemSize = itemSize; } @Override public void getItemOffsets(final Rect outRect, final View view, RecyclerView parent, RecyclerView.State state) { final int position = parent.getChildLayoutPosition(view); final int column = position % mSpanCount; final int parentWidth = parent.getWidth(); int spacing = (int) (parentWidth - (mItemSize * mSpanCount)) / (mSpanCount + 1); outRect.left = spacing - column * spacing / mSpanCount; outRect.right = (column + 1) * spacing / mSpanCount; if (position < mSpanCount) { outRect.top = spacing; } outRect.bottom = spacing; } } 

recyclerview_item.xml

  ...  

dimens.xml

  60dp 

Actividad

 int numberOfColumns = 3; mRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns)); mRecyclerView.setAdapter(...); mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3, getResources().getDimensionPixelSize(R.dimen.recycler_view_item_width))); 

enter image description here enter image description here

Las respuestas anteriores han aclarado formas de configurar el manejo de los márgenes GridLayoutManager y LinearLayoutManager.

Pero para StaggeredGridLayoutManager, la respuesta de Pirdad Sakhizada dice: “Puede que no funcione muy bien con StaggeredGridLayoutManager”. Debería ser el problema sobre IndexOfSpan.

Puedes obtenerlo de esta manera:

 private static class MyItemDecoration extends RecyclerView.ItemDecoration { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int index = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex(); } } 

Solo hay una solución fácil, que puede recordar e implementar donde sea necesario. Sin errores, sin cálculos locos. Ponga el margen al diseño de la tarjeta / elemento y coloque el mismo tamaño que el relleno en RecyclerView:

item_layout.xml

  

activity_layout.xml

  

Existe una solución muy simple pero flexible para este problema que utiliza solo XML que funciona en cada LayoutManager.

Supongamos que quiere un espaciado igual de X (8dp, por ejemplo).

  1. Envuelva su artículo CardView en otro diseño

  2. Dale a la distribución externa un relleno de X / 2 (4dp)

  3. Hacer que el fondo del diseño externo sea transparente

     
  1. Dale a tu RecyclerView un relleno de X / 2 (4dp)

  

y eso es. Tienes espaciado perfecto de X (8dp).

 public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int spanCount; private int spacing; private boolean includeEdge; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); int column = params.getSpanIndex(); if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } } 

Un poco diferente de la respuesta de Edwardaa, la diferencia es cómo se determina la columna, porque en casos como los artículos con varias alturas, la columna no puede determinarse simplemente por% spanCount

Aquí está mi modificación de SpacesItemDecoration que puede tomar numOfColums y espacio por igual en la parte superior, inferior, izquierda y derecha .

 public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int space; private int mNumCol; public SpacesItemDecoration(int space, int numCol) { this.space = space; this.mNumCol=numCol; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { //outRect.right = space; outRect.bottom = space; //outRect.left = space; //Log.d("ttt", "item position" + parent.getChildLayoutPosition(view)); int position=parent.getChildLayoutPosition(view); if(mNumCol<=2) { if (position == 0) { outRect.left = space; outRect.right = space / 2; } else { if ((position % mNumCol) != 0) { outRect.left = space / 2; outRect.right = space; } else { outRect.left = space; outRect.right = space / 2; } } }else{ if (position == 0) { outRect.left = space; outRect.right = space / 2; } else { if ((position % mNumCol) == 0) { outRect.left = space; outRect.right = space/2; } else if((position % mNumCol) == (mNumCol-1)){ outRect.left = space/2; outRect.right = space; }else{ outRect.left=space/2; outRect.right=space/2; } } } if(position 

y use el código a continuación en su lógica.

 recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol)); 

Terminé haciéndolo así para mi RecyclerView con GridLayoutManager y HeaderView .

En el siguiente código, establezco un espacio de 4dp entre cada elemento (2dp alrededor de cada elemento y un relleno de 2dp en toda la vista del reciclador).

layout.xml

  

fragmento / actividad

 GridLayoutManager manager = new GridLayoutManager(getContext(), 3); recyclerView.setLayoutManager(manager); int spacingInPixels = Utils.dpToPx(2); recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels)); 

SpaceItemDecoration.java

 public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int mSpacing; public SpacesItemDecoration(int spacing) { mSpacing = spacing; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) { outRect.left = mSpacing; outRect.top = mSpacing; outRect.right = mSpacing; outRect.bottom = mSpacing; } } 

Utils.java

 public static int dpToPx(final float dp) { return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT)); } 

gracias la respuesta de edwardaa https://stackoverflow.com/a/30701422/2227031

Otro punto a tener en cuenta es que:

si el espaciado total y el ancho total del artículo no son iguales al ancho de la pantalla, también necesita ajustar itemWidth, por ejemplo, en el método adaptadorBindViewHolder

 Utils.init(_mActivity); int width = 0; if (includeEdge) { width = ScreenUtils.getScreenWidth() - spacing * (spanCount + 1); } else { width = ScreenUtils.getScreenWidth() - spacing * (spanCount - 1); } int itemWidth = width / spanCount; ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) holder.imageViewAvatar.getLayoutParams(); // suppose the width and height are the same layoutParams.width = itemWidth; layoutParams.height = itemWidth; holder.imageViewAvatar.setLayoutParams(layoutParams); 

Para realizar el trabajo de solución https://stackoverflow.com/a/29905000/1649371 (arriba) tuve que modificar los siguientes métodos (y todas las llamadas posteriores)

 @SuppressWarnings("all") protected int getItemSpanSize(RecyclerView parent, View view, int childIndex) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex); } else if (mgr instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).isFullSpan() ? spanCount : 1; } else if (mgr instanceof LinearLayoutManager) { return 1; } return -1; } @SuppressWarnings("all") protected int getItemSpanIndex(RecyclerView parent, View view, int childIndex) { RecyclerView.LayoutManager mgr = parent.getLayoutManager(); if (mgr instanceof GridLayoutManager) { return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount); } else if (mgr instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex(); } else if (mgr instanceof LinearLayoutManager) { return 0; } return -1; } 

Este enlace funcionó para mí en todas las situaciones, puedes probar esto.

Una versión de Kotlin que hice basada en la gran respuesta de edwardaa

 class RecyclerItemDecoration(private val spanCount: Int, private val spacing: Int) : RecyclerView.ItemDecoration() { override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { val spacing = Math.round(spacing * parent.context.resources.displayMetrics.density) val position = parent.getChildAdapterPosition(view) val column = position % spanCount outRect.left = spacing - column * spacing / spanCount outRect.right = (column + 1) * spacing / spanCount outRect.top = if (position < spanCount) spacing else 0 outRect.bottom = spacing } } 

Si tiene un interruptor de alternancia que alterna entre la lista y la cuadrícula, no olvide llamar a recyclerView.removeItemDecoration() antes de configurar cualquier decoración de Artículo nuevo. De lo contrario, los nuevos cálculos para el espaciado serían incorrectos.


Algo como esto.

  recyclerView.removeItemDecoration(gridItemDecorator) recyclerView.removeItemDecoration(listItemDecorator) if (showAsList){ recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) recyclerView.addItemDecoration(listItemDecorator) } else{ recyclerView.layoutManager = GridLayoutManager(this, spanCount) recyclerView.addItemDecoration(gridItemDecorator) } 

Esto funcionará para RecyclerView con encabezado también.

 public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { private int spanCount; private int spacing; private boolean includeEdge; private int headerNum; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) { this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; this.headerNum = headerNum; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view) - headerNum; // item position if (position >= 0) { int column = position % spanCount; // item column if (includeEdge) { outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) if (position < spanCount) { // top edge outRect.top = spacing; } outRect.bottom = spacing; // item bottom } else { outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) if (position >= spanCount) { outRect.top = spacing; // item top } } } else { outRect.left = 0; outRect.right = 0; outRect.top = 0; outRect.bottom = 0; } } } }