Android Recyclerview GridLayoutManager column spacing
Asked Answered
B

33

322

How do you set the column spacing with a RecyclerView using a GridLayoutManager? Setting the margin/padding inside my layout has no effect.

Bah answered 15/2, 2015 at 22:31 Comment(4)
Have you tried subclassing GridLayoutManager and overriding generateDefaultLayoutParams() and kin?Triacid
I have not, I thought there would have been a method I was just not seeing to set the spacing the like grid view. I will try thatBah
https://mcmap.net/q/100931/-how-to-determine-column-position-in-staggered-grid-layout-managerEmanuele
try this gist.github.com/Arpit0492/cf14df02ddf53741df5dde864002e89cOuzo
C
395

RecyclerViews support the concept of ItemDecoration: special offsets and drawing around each element. As seen in this answer, you can use

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

Then add it via

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));
Caducity answered 16/2, 2015 at 1:17 Comment(14)
Use 'outRect.top = space' and remove 'outRect.bottom' if you don't want to mess with the 'if for the first position'. ;-]Argyres
@zatziky - yep, if you already use top and bottom padding as part of your RecyclerView (and use clipToPadding="false"), then you can restructure things slightly. If you don't however, you'd just be moving the if check to be the last time (as you'd still want the bottom padding on the last item).Caducity
I tried this and unless I added android:clipToPadding="false" and android:padding="@dimen/spacing" to the RecyclerView the outside spacing did not match the spacing between items. I have tried all the answers below and think the solution by @yqritc was the cleanest and simplest.Kame
@ianhanniballake, while this works when using a single span layout manager, it fails for multi-span layout manager.Inquiline
It worth to mention, that you have to modify all fields of outRect. otherwise you can get the offset of the previous view.Gondi
If you do it this way with GridLayoutManager - all first items of the 2nd, 3rd...nth column will stick to top (because there is no space). So I think it is better to do .top = space / 2 and .bottom = space / 2.Dextrin
How you can apply only bettween column and bottom ? [img]spacebetween[img] , for example I solve this problem in gridview normal with " android:horizontalSpacing="2dp" android:verticalSpacing="2dp"" in recycle I don't know howFredericksburg
getChildAdapterPosition (instead of getChildLayoutPosition) is better in most cases, I was getting problems refreshing itemDecorations after views had switched positions - the new layout hasn't finished while the getItemOffsets is called....Hinze
Do not forget set grid item root layout layout_width="match_parent"Coniferous
this parent.getChildLayoutPosition(view) == 0 puts second item in first row a little lower. I think it should be editedKarly
This answer does not answer the original question. The emphasis of question is on GridLayoutManager . The answer will not work on multi-column/row layoutsEmanuele
you didn't consider span countHalloran
only first item from the first row will have top margin. the second wont. change the if to: if (parent.getChildLayoutPosition(view) < columnsCount) and take columnsCount as argument. or use edwardaa answer.Cystolith
for some reason I have to set outRect.bottom and outRect.top to -20 to get to some acceptable row spacing.Disforest
B
559

Following code works well, and each column has same width:

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. no edge

enter image description here

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

2. with edge

enter image description here

int spanCount = 3; // 3 columns
int spacing = 50; // 50px
boolean includeEdge = true;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
Birkle answered 8/6, 2015 at 4:51 Comment(19)
Works unless you have items that have various spans, like headers.Jeopardize
Great answer; one tip: spacing is in px (so you can convert dp to px using Math.round(someDpValue * getResources().getDisplayMetrics().density))Mourning
Its working fine but i am having a problem, i am using GridLayoutManager with spanCount of 2(default) but user can change the spanCount so when spanCount changes from default position there is a much more visible padding on some positions like if spanCount will 3 than padding/margin on 2,3 8,9 12,13 etc.Chadd
Works great! But I have some problems with StaggeredGridLayoutManager. imgur.com/XVutH5u horizontal margins sometimes differs.Turgot
@HarisQureshi I had same problem. You have to remove item decoration every time you change spanCount.Castora
Works great, thank you, however there is a problem with odd number of columns. The right hand column does not align with the edge of the screen, it has one or two pixels of padding. So I take the padding, reset it to zero, then I distribute to the middle column. This is done by dividing the distributable amount by two, adding to the left/right and adding any remainder to the right. For this to work I pre-calculate all of the spacings once in my constructor (instead of each time the list scrolls) and store them in an array of left/right values. Hope this helpsPneumoencephalogram
You should use StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); int position = lp.getSpanIndex(); to get the position instead of parent.getChildAdapterPosition(view); this will solve the issues with bad calculations.Whitby
Best solution there is, thank you. If you have problems with first item in recycler view, from the code you put in for GridSpacingItemDecoration, remove last if(position >= spanCount){outRect.top = spacing;} and you are good to go. Many thanks to @edwardaaSwen
I have question in your answer. I have the same situation that can not make Images center. cause I have many sizes images that load in ImageView and I put attribute with a fixed size in dp and CropCenter but it not actually center. why it happends?Cajolery
This doesn't work when layout is rtl (for 2 columns or more). currently the space between columns is not right when in rtl mode. you need to replace: outRect.left with outRect.right when it is in rtl.Ashlieashlin
@Jeopardize Including Header is much easier than you think, As you can see, in "getItemOffsets", it's setting it's own position. So what you would do is, make ItemDecoration with boolean value "withHeader", and in "getItemOffsets", >> if( withHeader) { if( position == 0 ) return; else position--; }Seeley
If you have a toggle switch that toggles between list to grid, don't forget to call recyclerView.removeItemDecoration() before setting any new Item decoration. If not then the new calculations for the spacing would be incorrect.Bastille
This decision has a problem: items can be not the same width. It depends on space and column count. So i've made some changes in this answer: https://mcmap.net/q/98745/-android-recyclerview-gridlayoutmanager-column-spacingByrom
excellent calculation for the left & right. perfect same spacingOruro
This is the more generic answer and should have been the marked oneVancevancleave
It worked, but I can't understand the calculation, can someone explain in more detail? Why can't I set left for all items except the last one and set right for only last one?Calutron
@Jeopardize Have you found another solution for multiple spans? This one works fine but as soon as I have .setSpanSizeLookup Some of the spacing is messed upMagog
Add parenthesis around (spacing / spanCount) for more stable integer division rounding. Without them I had first column 161px wide, second and third 160px and the last one 159px.Bonsai
This answer has a bug, when the Adapter performs the notifyItemRemoved animation, the removed item's will flicker. You need to use the following code to get the correct position in getItemOffsets. int position = parent.getChildAdapterPosition(view); // item position if (position == RecyclerView.NO_POSITION) { position = parent.getChildLayoutPosition(view); } if (position == RecyclerView.NO_POSITION) { return; }Mesothorax
C
395

RecyclerViews support the concept of ItemDecoration: special offsets and drawing around each element. As seen in this answer, you can use

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

Then add it via

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));
Caducity answered 16/2, 2015 at 1:17 Comment(14)
Use 'outRect.top = space' and remove 'outRect.bottom' if you don't want to mess with the 'if for the first position'. ;-]Argyres
@zatziky - yep, if you already use top and bottom padding as part of your RecyclerView (and use clipToPadding="false"), then you can restructure things slightly. If you don't however, you'd just be moving the if check to be the last time (as you'd still want the bottom padding on the last item).Caducity
I tried this and unless I added android:clipToPadding="false" and android:padding="@dimen/spacing" to the RecyclerView the outside spacing did not match the spacing between items. I have tried all the answers below and think the solution by @yqritc was the cleanest and simplest.Kame
@ianhanniballake, while this works when using a single span layout manager, it fails for multi-span layout manager.Inquiline
It worth to mention, that you have to modify all fields of outRect. otherwise you can get the offset of the previous view.Gondi
If you do it this way with GridLayoutManager - all first items of the 2nd, 3rd...nth column will stick to top (because there is no space). So I think it is better to do .top = space / 2 and .bottom = space / 2.Dextrin
How you can apply only bettween column and bottom ? [img]spacebetween[img] , for example I solve this problem in gridview normal with " android:horizontalSpacing="2dp" android:verticalSpacing="2dp"" in recycle I don't know howFredericksburg
getChildAdapterPosition (instead of getChildLayoutPosition) is better in most cases, I was getting problems refreshing itemDecorations after views had switched positions - the new layout hasn't finished while the getItemOffsets is called....Hinze
Do not forget set grid item root layout layout_width="match_parent"Coniferous
this parent.getChildLayoutPosition(view) == 0 puts second item in first row a little lower. I think it should be editedKarly
This answer does not answer the original question. The emphasis of question is on GridLayoutManager . The answer will not work on multi-column/row layoutsEmanuele
you didn't consider span countHalloran
only first item from the first row will have top margin. the second wont. change the if to: if (parent.getChildLayoutPosition(view) < columnsCount) and take columnsCount as argument. or use edwardaa answer.Cystolith
for some reason I have to set outRect.bottom and outRect.top to -20 to get to some acceptable row spacing.Disforest
N
91

The following is the step-by-step simple solution if you want the equal spacing around items and equal item sizes.

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

Implementation

In your source code, add ItemOffsetDecoration to your RecyclerView. Item offset value should be half size of the actual value you want to add as space between items.

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

Also, set item offset value as padding for itsRecyclerView, and specify 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"/>
Natka answered 12/6, 2015 at 1:36 Comment(1)
Perfect, it's simple and effective.Socket
K
35

Try this. It'll take care of equal spacing all around. Works both with List, Grid, and StaggeredGrid.

Edited

The updated code should handle most of the corner cases with spans, orientation, etc. Note that if using setSpanSizeLookup() with GridLayoutManager, setting setSpanIndexCacheEnabled() is recommended for performance reasons.

Note, it seems that with StaggeredGrid, there's seems to be a bug where the index of the children gets wacky and hard to track so the code below might not work very well with 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);
  }
}

Hope it helps.

Kaif answered 27/4, 2015 at 20:7 Comment(7)
I've got double span just after first line of items. It happens because parent.getChildCount() returns 1 for first item, 2 for second and so on. So, I suggest add space to items of the top edge like: outRect.top = childIndex < spanCount ? spacingInPixels : 0; And add bottom space for each item: outRect.bottom = spacingInPixels;Crossjack
At the time of scrolling RecyclerView, spacing changed.Zonazonal
I think parent.getChildCount() should be changed to "parent.getLayoutManager().getItemCount()". Also, isBottomEdge function need to be changed to "return childIndex >= childCount - spanCount + spanIndex". After changing these, I got equal spacing. But please note that this solution does not give me equal item sizes if span count is greater than 2 since offset value is different depending on position.Natka
@Natka thanks for nocticing parent.getChildCount(). I've updated my answer to use the parent.getLayoutManager().getItemCount()Kaif
@Crossjack I've update the code to handle the issue you're facing. Let me know if you're still seeing it.Kaif
This worked very well out of the box even with variable spans, congratulations and thank you!Bunin
@Pirdad Sakhizada The solution works nice for Portrait, but if I change the orientation of phone to landscape it messes the spacing. Can you please suggest me where I am going wrong.French
S
34

There is only one easy solution, that you can remember and implement wherever needed. No bugs, no crazy calculations. Put margin to the card / item layout and put the same size as padding to the 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"/>

UPDATE: enter image description here

Sorption answered 9/8, 2017 at 17:48 Comment(7)
It works fine! could you elaborate on the issue, please?Escent
Thank you so much! I was looking for some technical reason why such a cooperation between recycler's padding and item's margin is needed. Any way you did so much for me . . .Escent
perfect solution!Thyself
Bro, you're genius _/.Ranged
What is it? A simple vertical RV without grid?Credulous
What we are doing here is simple compressing the view so that the margin between the cells is reduced as there wont be space. Good one mate!Crowl
this has worked like a charm.Malign
P
23

The following code will handle StaggeredGridLayoutManager, GridLayoutManager, and 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;
    }
}

Then use it

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));
Prophet answered 24/8, 2015 at 20:49 Comment(7)
This is the most simple one. One important thing is you also got to add the padding to the parent in the xml. In my case, it work that way. Thanks.Fishbolt
The SpaceItemDecoration actually adds the padding to the parent (the recycler view).Prophet
only halfSpace padding appeared(to the right side) when I had not set the padding to the parent in xmlFishbolt
It was only missing on the right side? It may be that you have half space set as the leftPadding on the left side already in the xml and this code only checks if the left padding is set on the RecyclerView or not.Prophet
Well I don't have any padding set in the xml.Fishbolt
what is mMargin you are passing in? what value should i passRepute
@praneethkumar that number is spacing in pixels you wantProphet
E
13

Here is a solution that doesn't require "spanCount" (number of columns) I use it because I use GridAutofitLayoutManager(calculates the number of columns according to the required cell size)

(beware that this will only work on 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
                }
            }

        }

    }
}

Here is the GridAutofitLayoutManager is anyone is interested:

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

Finally:

mDevicePhotosView.setLayoutManager(new GridAutofitLayoutManager(getContext(), getResources().getDimensionPixelSize(R.dimen.item_size)));
mDevicePhotosView.addItemDecoration(new GridSpacesItemDecoration(Util.dpToPx(getContext(), 2),true));
Explore answered 9/12, 2016 at 8:3 Comment(2)
Hi. This works amazing but I am using a header with your solution. Can you suggest how can achieve full-width header?Polyphonic
kindly you can check the position with layout manager as the following layoutManager.getPosition(view) after that check if the position is zero that will be your header .. also, this way will enable you to add another header at any positions you want :)Achromic
B
8

If you want to FIXED the size of your RecyclerView item in all devices. You can do like this

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>

Activity

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

Biskra answered 4/7, 2017 at 6:54 Comment(3)
will it work according to screen size means the way is it showing on the 5-inch screen, they look same on other screen sizes also?Octroi
the size of item will fixed but the space between item may different, you can see too 2 images above for understandBiskra
They look different on different screen sizes.any way but working thank youOctroi
B
7

The selected answer is almost perfect, but depending on the space, items width can be not equal. (In my case it was critical). So i've ended up with this code which increases space a little bit, so items are all the same width.

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

}
Byrom answered 27/9, 2018 at 13:52 Comment(1)
I would add following lines, if someone like me using GridLayoutManager with spanCount=1 columnCount == 1 -> { outRect.left = space outRect.right = space }Persse
B
7

When using CardView for children problem with spaces between items can by solved by setting app:cardUseCompatPadding to true.

For bigger margins enlarge item elevation. CardElevation is optional (use default value).

<androidx.cardview.widget.CardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardUseCompatPadding="true"
    app:cardElevation="2dp">
Bobbee answered 28/4, 2020 at 14:32 Comment(0)
F
6

Copied @edwardaa provided code and I make it perfect to support 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;
        }
    }
}
Flamen answered 9/10, 2016 at 8:18 Comment(1)
every one could copy code from gist.github.com/xingstarx/f2525ef32b04a5e67fecc5c0b5c4b939Flamen
R
6
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
    }
  }
}
Renaldorenard answered 26/10, 2018 at 6:53 Comment(0)
S
4

Answers above have clarified ways to set margin handling GridLayoutManager and LinearLayoutManager.

But for StaggeredGridLayoutManager, Pirdad Sakhizada's answer says: "It might not work very well with StaggeredGridLayoutManager". It should be the problem about IndexOfSpan.

You can get it by this way:

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();
    }
}
Salangi answered 29/12, 2015 at 2:42 Comment(0)
B
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
            }
        }
    }
}

A little bit different from edwardaa's answer, the difference is how the column is determined, because in cases such as items with various heights, the column can not be determined by simply % spanCount

Bullhead answered 26/5, 2016 at 8:38 Comment(0)
T
4

yqritc's answer worked perfectly for me. I was using Kotlin however so here is the equivalent of that.

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

everything else is the same.

Tomfool answered 29/11, 2019 at 6:25 Comment(0)
C
4

This is more flexible version I wrote in Kotlin, you can set your parameters in dp.

class ItemDividerGrid(private val numberOfColumns: Int, private val rowSpacingDP: Float = 0f, private val columnSpacingDP: Float = 0f, private val edgeSpacingVerticalDP: Float = 0f, private val edgeSpacingHorizontalDP: Float = 0f) : ItemDecoration() {

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val position = parent.getChildAdapterPosition(view)
        val numberOfRows = (parent.adapter?.itemCount?:-1)/numberOfColumns
        val column = position % numberOfColumns
        val row = position / numberOfColumns
        val context = view.context
        ///horizontal
        when(column){
            0 -> {
                outRect.left = convertDpToPixel(edgeSpacingVerticalDP,context)
                outRect.right = convertDpToPixel(columnSpacingDP/2, context)
            }
            numberOfColumns-1 -> {
                outRect.left = convertDpToPixel(columnSpacingDP/2, context)
                outRect.right = convertDpToPixel(edgeSpacingVerticalDP, context)
            }
            else -> {
                outRect.left = convertDpToPixel(columnSpacingDP/2, context)
                outRect.right = convertDpToPixel(columnSpacingDP/2, context)
            }
        }
        //vertical
        when(row){
            0  -> {
                outRect.top = convertDpToPixel(edgeSpacingHorizontalDP,context)
                outRect.bottom = convertDpToPixel(rowSpacingDP/2, context)
            }
            numberOfRows -> {
                outRect.top = convertDpToPixel(rowSpacingDP/2, context)
                outRect.bottom = convertDpToPixel(edgeSpacingHorizontalDP, context)
            }
            else -> {
                outRect.top = convertDpToPixel(rowSpacingDP/2, context)
                outRect.bottom = convertDpToPixel(rowSpacingDP/2, context)
            }
        }
    }
    fun convertDpToPixel(dp: Float, context: Context?): Int {
        return if (context != null) {
            val resources = context.resources
            val metrics = resources.displayMetrics
            (dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
        } else {
            val metrics = Resources.getSystem().displayMetrics
            (dp * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
        }
    }
}
Carom answered 23/3, 2021 at 20:5 Comment(0)
O
3

Here is my modification of SpacesItemDecoration which can take numOfColums and space equally on top, bottom, left and right.

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

and use below code on your logic.

recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol));
Outclass answered 10/2, 2016 at 17:18 Comment(0)
E
3

For those who have problems with staggeredLayoutManager (like https://i.stack.imgur.com/J1gjG.jpg)

recyclerView's methods:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

sometimes return -1 as index so we might face troubles setting itemDecor. My solution is to override deprecated ItemDecoration's method:

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

instead of the newbie:

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

like this:

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

Seems to work for me so far :)

Elena answered 11/10, 2018 at 12:14 Comment(1)
Great answer man! Works for all cases, including not symmetrical "regular" GridLayoutManager where you have an header item between items. Thanks!Seigler
S
3

If you've scrolled far enough to reach this answer, I wrote a library for equal spacing which supports Vertical/Horizontal, LTR/RTL, LinearLayout/GridLayout manager and Edge inclusion. Its basically a single file, so you can copy paste that file into your code.

enter image description here

I tried to support StaggeredGridLayout but span index returned by this layout is not reliable. I would be glad to hear any suggestion for that.

Susquehanna answered 19/12, 2020 at 9:4 Comment(3)
Interesting library, I'll give it a try.Philander
Well, I tried your library. More specifically, I adapted your code to make it work only with the VERTICAL orientation for both linear and grid layout managers and it works great.Philander
@Philander Glad you liked it.Susquehanna
C
2

There is a very simple and yet flexible solution for this problem using only XML which works on every LayoutManager.

Assume you want an equal spacing of X (8dp for example).

  1. Wrap your CardView item in another Layout

  2. Give the outer Layout a padding of X/2 (4dp)

  3. Make the outer Layout background 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. Give your RecyclerView a padding of X/2 (4dp)

...

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

and thats it. You have perfect spacing of X (8dp).

Cathrine answered 25/2, 2016 at 9:1 Comment(0)
S
2

A Kotlin version I made based on the great answer by 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
  }

}
Strongwilled answered 24/2, 2018 at 19:59 Comment(0)
A
2

The answers on for this question seem more complex than they should be. Here's my take on this.

Let's say you want 1dp spacing between grid items. Do the following:

  1. Add a padding of 0.5dp to each item
  2. Add a padding of -0.5dp to the RecycleView
  3. That's it! :)
Alonzoaloof answered 19/12, 2019 at 11:1 Comment(2)
between space is 2x ! its problem !Rete
Best... Solution.. Easy oneNagana
R
2

for anyone like me, who want the best answer but in kotlin, here it is:

class GridItemDecoration(
    val spacing: Int,
    private val spanCount: Int,
    private val includeEdge: Boolean
) :
    RecyclerView.ItemDecoration() {

    /**
     * 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
    ) {
        val position = parent.getChildAdapterPosition(view) // item position
        val 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
            }
        }
    }
}

plus if you want to get the number from dimens.xml and then convert it to raw pixel you can do it easily using getDimensionPixelOffset easily like this:

recyclerView.addItemDecoration(
                GridItemDecoration(
                    resources.getDimensionPixelOffset(R.dimen.h1),
                    3,
                    true
                )
            )
Rosmunda answered 10/8, 2020 at 15:17 Comment(0)
P
1

This will work for RecyclerView with header as well.

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;
        }
    }
    }
}
Prostrate answered 9/1, 2018 at 7:52 Comment(1)
What is headerNum?Uprising
B
1

If you have a toggle switch that toggles between list to grid, don't forget to call recyclerView.removeItemDecoration(..) before setting any new Item decoration. If not then the new calculations for the spacing would be incorrect.

Something like this:

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)
}
Bastille answered 2/4, 2018 at 16:49 Comment(0)
D
1

For StaggeredGridLayoutManager users, be careful, lots of answers here including the most voted one calculates the item column with below code:

int column = position % spanCount

which assumes that the 1st/3rd/5th/.. items are always located at left side and 2nd/4th/6th/.. items are always located at right side. Is this assumption always true? No.

Let's say your 1st item is 100dp high and 2nd is only 50dp, guess where is your 3rd item located, left or right?

Doucet answered 16/1, 2020 at 7:11 Comment(0)
L
1

Make sure you implement this in your gradle module:

implementation 'com.github.grzegorzojdana:SpacingItemDecoration:1.1.0'

Create this simple function:

public static int dpToPx(Context c, int dp) {
    Resources r = c.getResources();
    return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
}

And lastly implement the spacing as shown in this line below:

photosRecycler.addItemDecoration(new SpacingItemDecoration(2,  dpToPx(this, 4), true));
Level answered 8/5, 2021 at 15:39 Comment(0)
C
1

Well for me the perfect solution was setting the width of RecyclerView who has layoutmanager as GridLayoutManager to "wrap_content"

Crowl answered 31/10, 2022 at 11:33 Comment(0)
A
0

I ended up doing it like that for my RecyclerView with GridLayoutManager and HeaderView.

In the code below I set a 4dp space between every item (2dp around every single item and 2dp padding around the whole RecyclerView).

In layout.xml:

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

In your fragment/activity:

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

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

In Utils.java:

public static int dpToPx(final float dp) {
    return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
Agnostic answered 10/11, 2016 at 13:24 Comment(0)
S
0

To made https://mcmap.net/q/98745/-android-recyclerview-gridlayoutmanager-column-spacing (above) solution work I had to modify the following methods (and all subsequent calls)

@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;
}
Stradivari answered 24/8, 2017 at 18:15 Comment(0)
P
0

If you are using Header with GridLayoutManager use this code written in Kotlin for spacing between the grids:

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

And pass ItemDecoration to RecyclerView as:

gridView.addItemDecoration(SpacesItemDecoration(10))
Pious answered 7/3, 2019 at 11:15 Comment(1)
Better use Elvis operator (?:) instead of !!Gintz
T
0

This is what finally ended up working for me:

binding.rows.addItemDecoration(object: RecyclerView.ItemDecoration() {
    val px = resources.getDimensionPixelSize(R.dimen.grid_spacing)
    val spanCount = 2

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val index = parent.getChildLayoutPosition(view)
        val isLeft = (index % spanCount == 0)
        outRect.set(
            if (isLeft) px else px/2,
            0,
            if (isLeft) px/2 else px,
            px
        )
    }
})

Since there are only 2 columns for me (val spanCount = 2), I can do with just isLeft. If there were > 2 columns, then I'd need a isMiddle as well, and the value for both sides would be px/2.

I wish there was a way to get the app:spanCount from directly from the RecyclerView, but I don't believe there is.

Topee answered 9/7, 2020 at 3:37 Comment(0)
S
-1

thanks edwardaa's answer https://mcmap.net/q/98745/-android-recyclerview-gridlayoutmanager-column-spacing

Another point to note is that:

if total spacing and total itemWidth are not equal to the screen width, you also need to adjust itemWidth, for example, on adapter onBindViewHolder method

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);
Smythe answered 22/6, 2017 at 10:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.