Android Recyclerview GridLayoutManager espacement des colonnes


251

Comment définissez-vous l'espacement des colonnes avec un RecyclerView à l'aide d'un GridLayoutManager? La définition de la marge / remplissage à l'intérieur de ma mise en page n'a aucun effet.


Avez-vous essayé le sous GridLayoutManager-classement generateDefaultLayoutParams()et la substitution et la parenté?
CommonsWare

Je ne l'ai pas fait, je pensais qu'il y aurait eu une méthode que je ne voyais tout simplement pas pour régler l'espacement comme la vue de la grille. Je vais essayer ça
hitch.united


Réponses:


348

RecyclerViews prend en charge le concept de ItemDecoration : décalages spéciaux et dessin 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;
    }
  }
}

Ajoutez-le ensuite via

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

3
Utilisez «outRect.top = espace» et supprimez «outRect.bottom» si vous ne voulez pas jouer avec le «si pour la première position». ; -]
Amio.io

2
@zatziky - oui, si vous utilisez déjà le rembourrage supérieur et inférieur dans le cadre de votre RecyclerView(et de votre utilisation clipToPadding="false"), alors vous pouvez restructurer légèrement les choses. Si vous ne le faites pas cependant, vous déplaceriez simplement la vérification if pour la dernière fois (car vous voudriez toujours le rembourrage inférieur sur le dernier élément).
ianhanniballake

18
@ianhanniballake, bien que cela fonctionne lors de l'utilisation d'un gestionnaire de disposition à travée unique, il échoue pour le gestionnaire de disposition à travées multiples.
Avinash R

4
Si vous le faites de cette façon avec GridLayoutManager - tous les premiers éléments de la 2e, 3e ... nième colonne resteront en haut (car il n'y a pas d'espace). Je pense donc qu'il vaut mieux faire .top = space / 2 et .bottom = space / 2.
Yaroslav

1
Cette réponse ne répond pas à la question d'origine. L'accent est mis sur la question GridLayoutManager . La réponse ne fonctionnera pas sur les dispositions multi-colonnes / lignes
hch

428

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

entrez la description de l'image ici

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

2. avec bord

entrez la description de l'image ici

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

12
Fonctionne sauf si vous avez des éléments qui ont différentes étendues, comme des en-têtes.
Matthew

8
Très bonne réponse; une astuce: l'espacement est en px (vous pouvez donc convertir dp en px en utilisant Math.round (someDpValue * getResources (). getDisplayMetrics (). density))
Kevin Lee

1
Son fonctionne bien mais j'ai un problème, j'utilise GridLayoutManager avec spanCount de 2 (par défaut) mais l'utilisateur peut changer le spanCount donc quand spanCount change de la position par défaut, il y a un remplissage beaucoup plus visible sur certaines positions comme si spanCount sera 3 que rembourrage / marge sur 2,3 8,9 12,13 etc.
Haris Qureshi

2
Fonctionne très bien! Mais j'ai des problèmes avec StaggeredGridLayoutManager. imgur.com/XVutH5u les marges horizontales diffèrent parfois.
Ufkoku

2
Cela ne fonctionne pas lorsque la mise en page est rtl (pour 2 colonnes ou plus). actuellement, l'espace entre les colonnes n'est pas correct en mode rtl. vous devez remplacer: outRect.left par outRect.right quand il est en rtl.
Masoud Mohammadi

83

Voici la solution simple étape par étape si vous souhaitez un espacement égal autour des articles et des tailles d'articles identiques.

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. valeur de décalage d'élément doit être la moitié de la valeur réelle que vous souhaitez ajouter en tant qu'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 de l'élément comme remplissage pour son RecyclerViewet 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"/>

Parfait, c'est simple et efficace.
B.shruti

28

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

Édité

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

Remarque, il semble qu'avec StaggeredGrid, il semble y avoir un bogue où l'index des enfants devient farfelu et difficile à suivre, de sorte que le code ci-dessous pourrait 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.


1
J'ai une double portée juste après la première ligne d'articles. Cela se produit parce que parent.getChildCount () renvoie 1 pour le premier élément, 2 pour le second et ainsi de suite. Donc, je suggère d'ajouter de l'espace aux éléments du bord supérieur comme: outRect.top = childIndex <spanCount? spacingInPixels: 0; Et ajoutez un espace inférieur pour chaque élément: outRect.bottom = spacingInPixels;
IvanP

Au moment du défilement de RecyclerView, l'espacement a changé.
Yugesh

3
Je pense que parent.getChildCount () devrait être changé en "parent.getLayoutManager (). GetItemCount ()". De plus, la fonction isBottomEdge doit être remplacée par "return childIndex> = childCount - spanCount + spanIndex". Après les avoir modifiés, j'ai obtenu un espacement égal. Mais veuillez noter que cette solution ne me donne pas la même taille d'article si le nombre de plages est supérieur à 2 car la valeur de décalage est différente selon la position.
yqritc

1
@yqritc merci pour la parenthèse nocturne parent.getChildCount (). J'ai mis à jour ma réponse pour utiliser le parent.getLayoutManager (). GetItemCount ()
Pirdad Sakhizada

2
Cela a très bien fonctionné hors de la boîte, même avec des portées variables, félicitations et merci!
Pierre-Luc Paour

21

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

Ensuite, utilisez-le

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

1
Ceci est le plus simple. Une chose importante est que vous devez également ajouter le remplissage au parent dans le xml. Dans mon cas, cela fonctionne de cette façon. Merci.
Samir

Le SpaceItemDecorationajoute en fait le remplissage au parent (la vue du recycleur).
Mark Hetherington

seul le halfSpacerembourrage est apparu (sur le côté droit) alors que je n'avais pas défini le rembourrage pour le parent en xml
Samir

Il ne manquait que du côté droit? Il se peut que vous ayez déjà défini un demi-espace comme leftPadding sur le côté gauche dans le xml et ce code vérifie uniquement si le remplissage gauche est défini sur RecyclerView ou non.
Mark Hetherington du

Eh bien, je n'ai pas de rembourrage défini dans le xml.
Samir

11

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 qui intéresse tout le monde:

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

Salut. Cela fonctionne très bien mais j'utilise un en-tête avec votre solution. Pouvez-vous suggérer comment obtenir un en-tête pleine largeur?
Ajeet

avec bonté, vous pouvez vérifier la position avec le gestionnaire de mise en page comme suit layoutManager.getPosition(view)après cette vérification si la position est zéro qui sera votre en-tête .. également, cette façon vous permettra d'ajouter un autre en-tête à toutes les positions que vous voulez :)
Mina Samir

9

Il n'y a qu'une seule solution simple, que vous pouvez mémoriser et mettre en œuvre partout où vous en avez besoin. Pas de bugs, pas de calculs fous. Mettez de la marge dans la disposition de la carte / de l'article et mettez la même taille que le rembourrage dans 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"/>

METTRE À JOUR: entrez la description de l'image ici


Ça fonctionne bien! pourriez-vous développer la question, s'il vous plaît?
elyar abad

Merci beaucoup! Je cherchais une raison technique pour laquelle une telle coopération entre le rembourrage du recycleur et la marge de l'article est nécessaire. De toute façon, tu as tant fait pour moi. . .
elyar abad

7

Si vous souhaitez FIXER la taille de votre RecyclerViewarticle sur tous les appareils. Vous pouvez 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)));

entrez la description de l'image ici entrez la description de l'image ici


cela fonctionnera-t-il en fonction de la taille de l'écran signifie que la façon dont il est affiché sur l'écran de 5 pouces, ils ont la même apparence sur d'autres tailles d'écran?
Sunil

la taille de l'article sera fixe mais l'espace entre les articles peut différer, vous pouvez aussi voir 2 images ci-dessus pour comprendre
Phan Van Linh

Ils ont l'air différents sur différentes tailles d'écran, de toute façon mais fonctionnent merci
Sunil

6

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). Je me suis donc retrouvé avec ce code qui augmente un peu l'espace, donc les articles 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
            }
        }
    }

}

J'ajouterais les lignes suivantes, si quelqu'un comme moi utilisant GridLayoutManager avec spanCount = 1 columnCount == 1 -> { outRect.left = space outRect.right = space }
massivemadness

5

Copié le code fourni par @edwardaa et je le rend parfait pour prendre en charge 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;
        }
    }
}


4

Les réponses ci-dessus ont clarifié les moyens 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 avec 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
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érente de la réponse d'edwardaa, la différence est la façon dont la colonne est déterminée, car dans des cas tels que des éléments de différentes hauteurs, la colonne ne peut pas être déterminée simplement par% spanCount


4
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

Voici ma modification SpacesItemDecorationqui 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));

2

Il existe une solution très simple et pourtant flexible pour 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. Donnez à la disposition extérieure un rembourrage de X / 2 (4dp)

  3. Rendre l'arrière-plan de mise en page 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 RecyclerView 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).


2

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

Méthodes de recyclerView:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

retourne parfois -1 comme 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 de 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 fonctionner pour moi jusqu'à présent :)


Grande réponse homme! Fonctionne pour tous les cas, y compris le GridLayoutManager "normal" non symétrique où vous avez un élément d'en-tête entre les éléments. Merci!
Shirane85

2

Les réponses à cette question semblent plus complexes qu'elles ne devraient l'être. Voici mon point de vue à ce sujet.

Disons que vous voulez un espacement de 1dp entre les éléments de la grille. Procédez comme suit:

  1. Ajoutez un rembourrage de 0,5 dp à chaque élément
  2. Ajoutez un remplissage de -0,5dp à RecycleView
  3. C'est tout! :)

1

Cela fonctionnera également RecyclerViewavec l'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;
        }
    }
    }
}

Qu'est-ce que headerNum?
Tim Kranen

1

La réponse de yqritc a parfaitement fonctionné pour moi. J'utilisais Kotlin cependant voici donc l'équivalent de cela.

class ItemOffsetDecoration : RecyclerView.ItemDecoration  {

    // amount to add to padding
    private val _itemOffset: Int

    constructor(itemOffset: Int) {
        _itemOffset = itemOffset
    }

    constructor(@NonNull context: Context, @DimenRes itemOffsetId: Int){
       _itemOffset = context.resources.getDimensionPixelSize(itemOffsetId)
    }

    /**
     * Applies padding to all sides of the [Rect], which is the container for the view
     */
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        outRect.set(_itemOffset, _itemOffset, _itemOffset, _itemOffset)
    }
}

Tout le reste est identique.


1

Pour les utilisateurs de StaggeredGridLayoutManager , faites attention, beaucoup de réponses ici, y compris la plus votée, calculent la colonne de l'élément avec le code ci-dessous:

int column = position % spanCount

ce qui suppose que les articles 1er / 3ème / 5ème / .. sont toujours situés à gauche et que les articles 2ème / 4ème / 6ème / .. sont toujours situés à droite. Cette hypothèse est-elle toujours vraie? Non.

Supposons que votre 1er élément soit de 100dp de haut et que le 2ème ne soit que de 50dp, devinez où se trouve votre 3e élément, à gauche ou à droite?


1

Lorsque vous utilisez CardView pour les enfants, le problème des espaces entre les éléments peut être résolu en définissant app: cardUseCompatPadding sur true.

Pour des marges plus importantes, augmentez l'élévation de l'article. CardElevation est facultatif (utilisez la valeur par défaut).

<androidx.cardview.widget.CardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardUseCompatPadding="true"
    app:cardElevation="2dp">

0

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

Dans le code ci-dessous, j'ai défini un espace de 4dp entre chaque élément (2dp autour de chaque élément et un rembourrage 2dp autour de l'ensemble du recyclage).

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

Pour que https://stackoverflow.com/a/29905000/1649371 (ci-dessus) fonctionne, j'ai dû 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

Si vous avez un interrupteur à bascule qui bascule entre la liste et la grille, n'oubliez pas d'appeler recyclerView.removeItemDecoration()avant de définir une nouvelle décoration d'é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

Si vous utilisez Header 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 passer ItemDecorationà recyclerviewas

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

-1

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

Un autre point à noter est que:

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

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

-1

Une version Kotlin que j'ai faite sur la base de la grande 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
  }

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.