web-dev-qa-db-fra.com

Espacement des colonnes Android Recyclerview GridLayoutManager

Comment définir l'espacement des colonnes avec RecyclerView à l'aide de GridLayoutManager? Le réglage de la marge/remplissage dans ma mise en page n'a aucun effet.

182
hitch.united

RecyclerViews soutient le concept de ItemDecoration : des décalages spéciaux et des dessins autour de chaque élément Comme vu dans cette réponse , vous pouvez utiliser 

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

Puis ajoutez le via

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

Le code suivant fonctionne bien et chaque colonne a la même largeur:

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

Usage

1. pas de bord

enter image description here

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

2. avec bord

enter image description here

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

Voici la solution simple, étape par étape, si vous souhaitez un espacement égal entre les éléments et une taille d’élément égale.

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

La mise en oeuvre

Dans votre code source, ajoutez ItemOffsetDecoration à votre RecyclerView. La valeur de décalage d'élément doit correspondre à la moitié de la valeur réelle que vous souhaitez ajouter sous forme d'espace entre les éléments.

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

Définissez également la valeur de décalage d’élément comme un bourrage pour saRecyclerView et spécifiez 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"/>
76
yqritc

Essaye ça. Cela prendra soin d'un espacement égal tout autour. Fonctionne à la fois avec List, Grid et StaggeredGrid.

Edité

Le code mis à jour doit gérer la plupart des cas d'angle avec des plages, une orientation, etc. .. Notez que si vous utilisez setSpanSizeLookup () avec GridLayoutManager, la définition de setSpanIndexCacheEnabled () est recommandée pour des raisons de performances. 

Notez, il semble qu'avec StaggeredGrid, il semble y avoir un bogue qui fait que l'index des enfants devient farfelu et difficile à suivre. Le code ci-dessous risque de ne pas fonctionner très bien avec 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);
  }
}

J'espère que ça aide.

26
Pirdad Sakhizada

Le code suivant gérera StaggeredGridLayoutManager, GridLayoutManager et 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;
    }
}

Puis l'utiliser

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));
13
Mark Hetherington

Voici une solution qui ne nécessite pas "spanCount" (nombre de colonnes) Je l'utilise car j'utilise GridAutofitLayoutManager (calcule le nombre de colonnes en fonction de la taille de cellule requise)

(attention, cela ne fonctionnera que sur 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
                }
            }

        }

    }
}

Voici le GridAutofitLayoutManager si quelqu'un est intéressé:

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

Finalement:

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

Il n’existe qu’une solution simple, que vous pouvez retenir et mettre en œuvre à tout moment. Pas de bugs, pas de calculs fous. Placez la marge dans la présentation de la carte/de l’article et placez la même taille que le rembourrage dans le 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"/>
5
Galya

Le code copié par @edwardaa et je le rend parfait pour supporter 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;
        }
    }
}
5
Xingxing

Si vous voulez FIXER la taille de votre élément RecyclerView dans tous les périphériques. Tu peux faire comme ça

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>

Activité

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

4
Linh

Les réponses ci-dessus ont clarifié les manières de définir la gestion des marges GridLayoutManager et LinearLayoutManager.

Mais pour StaggeredGridLayoutManager, la réponse de Pirdad Sakhizada dit: "Cela pourrait ne pas fonctionner très bien avec StaggeredGridLayoutManager". Ce devrait être le problème d'IndexOfSpan.

Vous pouvez l'obtenir de cette façon:

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();
    }
}
4
wklbeta
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
    }
  }
}
3
fraggjkee

Pour ceux qui ont des problèmes avec staggeredLayoutManager (comme https://imgur.com/XVutH5u )

méthodes de recyclerView:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

renvoyez parfois -1 en tant qu'index afin que nous puissions rencontrer des problèmes lors de la définition de itemDecor. Ma solution est de remplacer la méthode obsolète ItemDecoration:

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

au lieu du débutant:

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

comme ça:

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

Semble travailler pour moi jusqu'à présent :)

1
navid

La réponse sélectionnée est presque parfaite, mais en fonction de l'espace, la largeur des éléments peut ne pas être égale. (Dans mon cas c'était critique). Donc, j'ai fini avec ce code qui augmente un peu l'espace, donc les objets ont tous la même largeur.

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

}

1
Kuva
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 peu différent de la réponse d'edwardaa, la différence réside dans le mode de détermination de la colonne, car dans des cas tels que des éléments de hauteurs différentes, la colonne ne peut pas être déterminée simplement par% spanCount

1
Jonathan Lee

Voici ma modification de SpacesItemDecoration qui peut prendre numOfColums et espace également en haut, en bas, à gauche et à droite

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

et utilisez le code ci-dessous sur votre logique. 

recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol));
1
andy anexinet

Il existe une solution très simple et flexible pour résoudre ce problème en utilisant uniquement XML qui fonctionne sur chaque LayoutManager.

Supposons que vous souhaitiez un espacement égal de X (8dp par exemple).

  1. Enveloppez votre élément CardView dans une autre mise en page

  2. Donner à la disposition extérieure un remplissage de X/2 (4dp)

  3. Rendre le fond de la disposition externe transparent

...

<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. Donnez à votre recycleur un rembourrage de X/2 (4dp)

...

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

et c'est tout. Vous avez un espacement parfait de X (8dp).

1
kaveh gh

merci la réponse de edwardaa https://stackoverflow.com/a/30701422/2227031

Un autre point à noter est que:

si l'espacement total et la largeur d'élément totale ne sont pas égaux à la largeur de l'écran, vous devez également ajuster l'élément de largeur, par exemple, sur la méthode onBindViewHolder de adapter

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

J'ai fini par le faire comme ça pour mon RecyclerView avec GridLayoutManager et HeaderView .

Dans le code ci-dessous, j'indique un espace de 4dp entre chaque élément (2dp autour de chaque élément et 2dp autour de la vue de recyclage complète). 

layout.xml

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

fragment/activité

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));
}
0
Simon Schubert

Pour faire https://stackoverflow.com/a/29905000/1649371 (ci-dessus), le travail de solution, je devais modifier les méthodes suivantes (et tous les appels suivants)

@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;
}
0
Damián Arrillaga

Une version de Kotlin que j'ai créée sur la base de l'excellente réponse d'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
  }

}
0
Leon

Si vous avez un commutateur qui bascule entre les listes, vous n’oubliez pas d’appeler recyclerView.removeItemDecoration() avant de définir une nouvelle décoration pour un élément. Sinon, les nouveaux calculs d'espacement seraient incorrects.


Quelque chose comme ça.

        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)
        }
0
Sahil Patel

Si vous utilisez En-tête avec GridLayoutManager utilisez ce code écrit en kotlin pour l'espacement entre les grilles:

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

Et passez ItemDecoration à recyclerview comme

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))
0
Kapil Rajput

This Link a fonctionné pour moi dans toutes les situations.

0
Vivek Barai

Cela fonctionnera aussi pour RecyclerView avec en-tête.

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