separation recyclerview item decoration columns android gridview android-recyclerview recycler-adapter gridlayoutmanager

android - item - space between elements recyclerview



Android Recyclerview GridLayoutManager espaciado de columnas (24)

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


Este Link funcionó para mí en todas las situaciones, puede intentar esto.


Esto también funcionará RecyclerView con el encabezado.

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; } } } }


Para que la solución https://.com/a/29905000/1649371 (arriba) funcionara, 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; }


Si está utilizando Encabezado con GridLayoutManager, use este código escrito en kotlin para espaciar entre las cuadrículas:

inner class SpacesItemDecoration(itemSpace: Int) : RecyclerView.ItemDecoration() { var space: Int = itemSpace override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) { super.getItemOffsets(outRect, view, parent, state) val position = parent!!.getChildAdapterPosition(view) val viewType = parent.adapter.getItemViewType(position) //check to not to set any margin to header item if (viewType == GridViewAdapter.TYPE_HEADER) { outRect!!.top = 0 outRect.left = 0 outRect.right = 0 outRect.bottom = 0 } else { outRect!!.left = space outRect.right = space outRect.bottom = space if (parent.getChildLayoutPosition(view) == 0) { outRect.top = space } else { outRect.top = 0 } } } }

Y pasar ItemDecoration a recyclerview como

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))


Si tiene un interruptor de alternancia que alterna entre la lista y la cuadrícula, no se olvide de llamar recyclerView.removeItemDecoration() antes de configurar cualquier nueva decoración de artículos. 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) }


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 individual y un relleno de 2dp alrededor de toda la vista del reciclador).

layout.xml

<android.support.v7.widget.RecyclerView android:id="@+id/recycleview" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="2dp" />

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)); }


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 } }


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

Otro punto a tener en cuenta es que:

si el espaciado total y el ancho total del elemento no son iguales al ancho de la pantalla, también debe ajustar el ancho del elemento, por ejemplo, en el método del adaptador onBindViewHolder

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);


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<mNumCol){ outRect.top=space; }else{ outRect.top=0; } // Add top margin only for the first item to avoid double space between items /* if (parent.getChildLayoutPosition(view) == 0 ) { } else { outRect.top = 0; }*/ } }

y use el siguiente código en su lógica.

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


Aquí hay una solución que no requiere "spanCount" (número de columnas). Lo uso porque uso 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 proporcionado por @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; } } }


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 filo

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

2. con borde

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


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));


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

Suponga que desea un espaciado igual de X (8dp por ejemplo).

  1. Envuelva su elemento CardView en otro diseño

  2. Dele al diseño exterior un relleno de X / 2 (4dp)

  3. Haga que el fondo del diseño exterior sea transparente

...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="200dp" android:layout_height="200dp" android:background="@android:color/transparent" android:padding="4dip"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.CardView> </LinearLayout>

  1. Dele a su RecyclerView un relleno de X / 2 (4dp)

...

<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" />

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


La respuesta seleccionada es casi perfecta, pero dependiendo del espacio, el ancho de los elementos puede no ser igual. (En mi caso fue crítico). Así que terminé con este código que aumenta un poco el espacio, por lo que todos los elementos tienen el mismo ancho.

class GridSpacingItemDecoration(private val columnCount: Int, @Px preferredSpace: Int, private val includeEdge: Boolean): RecyclerView.ItemDecoration() { /** * In this algorithm space should divide by 3 without remnant or width of items can have a difference * and we want them to be exactly the same */ private val space = if (preferredSpace % 3 == 0) preferredSpace else (preferredSpace + (3 - preferredSpace % 3)) override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) { val position = parent.getChildAdapterPosition(view) if (includeEdge) { when { position % columnCount == 0 -> { outRect.left = space outRect.right = space / 3 } position % columnCount == columnCount - 1 -> { outRect.right = space outRect.left = space / 3 } else -> { outRect.left = space * 2 / 3 outRect.right = space * 2 / 3 } } if (position < columnCount) { outRect.top = space } outRect.bottom = space } else { when { position % columnCount == 0 -> outRect.right = space * 2 / 3 position % columnCount == columnCount - 1 -> outRect.left = space * 2 / 3 else -> { outRect.left = space / 3 outRect.right = space / 3 } } if (position >= columnCount) { outRect.top = space } } } }


La siguiente es la solución simple paso a paso si desea un espacio igual alrededor de los elementos y tamaños de elementos iguales.

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 desplazamiento del elemento debe ser la mitad del valor real que desea agregar como espacio entre 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 desplazamiento del elemento como relleno para su RecyclerView y especifique android:clipToPadding=false .

<android.support.v7.widget.RecyclerView android:id="@+id/recyclerview_grid" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:padding="@dimen/item_offset"/>


Las respuestas anteriores han aclarado formas de establecer el manejo de 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.

Puede 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(); } }


Para aquellos que tienen problemas con staggeredLayoutManager (como https://imgur.com/XVutH5u )

Los métodos de recyclerView:

getChildAdapterPosition(view) getChildLayoutPosition(view)

a veces devuelve -1 como índice, por lo que podríamos tener problemas para configurar itemDecor. Mi solución es anular el método obsoleto de ItemDecoration:

public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

en lugar del novato:

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

Me gusta esto:

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { TheAdapter.VH vh = (TheAdapter.VH) recyclerView.findViewHolderForAdapterPosition(itemPosition); View itemView = vh.itemView; //itemView is the base view of viewHolder //or instead of the 2 lines above maybe it''s possible to use View itemView = layoutManager.findViewByPosition(itemPosition) ... NOT TESTED StaggeredGridLayoutManager.LayoutParams itemLayoutParams = (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams(); int spanIndex = itemLayoutParams.getSpanIndex(); if (spanIndex == 0) ... else ... } });

Parece que funciona para mí hasta ahora :)


Prueba esto. Se encargará de un espacio igual alrededor. Funciona tanto 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 razones 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 loco y difícil de rastrear, por lo que el código a continuación 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.


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éguelo 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));


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

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="@dimen/recycler_view_item_width" ... > ... </LinearLayout>

dimens.xml

<dimen name="recycler_view_item_width">60dp</dimen>

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)));


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

item_layout.xml

<CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:margin="10dp">

activity_layout.xml

<RecyclerView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp"/>

ACTUALIZAR:


class VerticalGridSpacingDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() { override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: State ) { val layoutManager = parent.layoutManager as? GridLayoutManager if (layoutManager == null || layoutManager.orientation != VERTICAL) { return super.getItemOffsets(outRect, view, parent, state) } val spanCount = layoutManager.spanCount val position = parent.getChildAdapterPosition(view) val column = position % spanCount with(outRect) { left = if (column == 0) 0 else spacing / 2 right = if (column == spanCount.dec()) 0 else spacing / 2 top = if (position < spanCount) 0 else spacing } } }


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 elementos con varias alturas, la columna no se puede determinar simplemente por% spanCount