18

In my Android app I'm using a RecyclerView to display items in a grid by using a GridLayoutManager. In a GridView, in order to specify the spacing between elements, I would set the horizontalSpacing and verticalSpacing properties.

So, how can I do the same on a RecyclerView?

N J
  • 27,217
  • 13
  • 76
  • 96
Ernestina Juan
  • 957
  • 1
  • 9
  • 24

5 Answers5

17

I haven't tested it with a GridLayout but for a LinearLayout, I simply set a margin to each listitem's root layout. I guess if you want the same space between all items (let's say 8dp), you would have to do this:

  1. Set layout_padding 4 dp to the RecyclerView.
  2. Set layout_margin 4 dp to each listitem.

This way you would have a constant 8 dp around every item.


The code below is a horizontal RecyclerView that displays images with a correct spacing of 8 dp:

<!-- fragment_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        tools:listitem="@layout/list_item" />

</LinearLayout>

<!-- list_item.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:layout_margin="4dp">

    <ImageView
        android:layout_width="@dimen/thumbnail_size"
        android:layout_height="@dimen/thumbnail_size"
        android:contentDescription="@string/image_content_desc" />

</LinearLayout>

EDIT: I realised that the item view need to have a parent ViewGroup, so I updated the snippet.

josemigallas
  • 3,761
  • 1
  • 29
  • 68
7

you have to use a ItemDecorator for that:

like this:

public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {

    private final int mSpaceHeight;

    public EqualSpaceItemDecoration(int mSpaceHeight) {
        this.mSpaceHeight = mSpaceHeight;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                               RecyclerView.State state) {
        outRect.bottom = mSpaceHeight;
        outRect.top = mSpaceHeight;
        outRect.left = mSpaceHeight;
        outRect.right = mSpaceHeight;
    }
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
JAAD
  • 12,349
  • 7
  • 36
  • 57
  • 1
    You copy/paste answer not say nothing about "horizontalSpacing and verticalSpacing" :) –  Apr 04 '16 at 14:03
  • top+bottom=vertical, left+right+horizontal spacing , i thought it was easy enough so didn't mention it ,also its name suggest it is equalSpace means whatever you pass in it vertical and horizontal spacing will be same – JAAD Apr 04 '16 at 14:33
  • I read this article "http://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview" and your solution not can't apply only space between 2 or more columns, this solution is do margins on a single object, not vertical or horitzontal from columns! , Note : exist a power library that not have 4 lines as your example. github.com/lucasr/twoway-view –  Apr 04 '16 at 14:43
  • nice and simple, tnx – Eggcellentos Mar 22 '18 at 15:52
5

In RecyclerView, spacing between items could be added using ItemDecoration. Before that let's do some maths to find out margin around list items.

Here we get equally spaced areas. In each area, we have to find their margins and return it in the outRect. To be equally spaced and equally sized, if you do the maths correctly, you will find that these offset values are not constants, they get changed based on their column index and the row index. Let's derive equations for the offset values.

With Outer Edge

In the first case, we consider margin around the edges.

enter image description here

For 0 and 1,
d + w + a = d − a + w + c
c = 2a

For 0 and 2,
d + w + a = d − c + w + e
e = a + c
e = 3a

If you see the pattern here,
right = a, 2a, 3a, ... , (n + 1) × a
right = (nᵢ + 1) × a

na = d
a = d ÷ n
right = (nᵢ + 1) × d ÷ n

Pattern of left side,
left = d, d − a, d − 2a, d − 3a, ... , d − na
left = d − nᵢ × a
left = d − nᵢ × d ÷ n

nᵢ - Column index

Without Outer Edge

Here we do our maths without considering edges.

enter image description here

For 0 and 1,
w + a = d − a + w + c
c = 2a − d

For 0 and 2,
w + a = d − c + w + e
e = 3a − 2d

The pattern here is,
right = a, 2a − d, 3a − 2d, ... , (n + 1) × a - nd
right = (nᵢ + 1) × a - nᵢd

(n + 1) × a − nd = 0
a = nd ÷ (n + 1)

∴ right = d − d × (nᵢ + 1) ÷ q
q = n + 1
q - Column count
nᵢ - Column index

left = 0, d − a, 2(d − a), ... , n(d − a)
left = nᵢ(d − a)
left = nᵢ × d ÷ q

Let's write code for this...

class SpacingItemDecoration(
    private val spacing: Int,
    private val includeEdge: Boolean,
    private val headerRowCount: Int = 0
) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val columnCount = getColumnCount(parent)
        val position = parent.getChildAdapterPosition(view) - headerRowCount * columnCount
        if (position >= 0) {
            val rowCount = getRowCount(parent, columnCount)
            val orientation = getOrientation(parent)
            val columnIndex = position % columnCount
            val rowIndex = position / columnCount
            if (includeEdge) {
                when (orientation) {
                    RecyclerView.VERTICAL -> {
                        outRect.left = getStartOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.right = getEndOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.top = getStartOffsetWithEdge(spacing, rowIndex, rowCount)
                        outRect.bottom = getEndOffsetWithEdge(spacing, rowIndex, rowCount)
                    }
                    RecyclerView.HORIZONTAL -> {
                        outRect.top = getStartOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.bottom = getEndOffsetWithEdge(spacing, columnIndex, columnCount)
                        outRect.left = getStartOffsetWithEdge(spacing, rowIndex, rowCount)
                        outRect.right = getEndOffsetWithEdge(spacing, rowIndex, rowCount)
                    }
                }
            } else {
                when (orientation) {
                    RecyclerView.VERTICAL -> {
                        outRect.left = getStartOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.right = getEndOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.top = getStartOffsetWithoutEdge(spacing, rowIndex, rowCount)
                        outRect.bottom = getEndOffsetWithoutEdge(spacing, rowIndex, rowCount)
                    }
                    RecyclerView.HORIZONTAL -> {
                        outRect.top = getStartOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.bottom = getEndOffsetWithoutEdge(spacing, columnIndex, columnCount)
                        outRect.left = getStartOffsetWithoutEdge(spacing, rowIndex, rowCount)
                        outRect.right = getEndOffsetWithoutEdge(spacing, rowIndex, rowCount)
                    }
                }
            }
        } else {
            outRect.left = 0
            outRect.right = 0
            outRect.top = 0
            outRect.bottom = 0
        }
    }

    private fun getColumnCount(parent: RecyclerView) = when (val layoutManager = parent.layoutManager) {
        is GridLayoutManager -> layoutManager.spanCount
        is StaggeredGridLayoutManager -> layoutManager.spanCount
        else -> 1
    }

    private fun getRowCount(parent: RecyclerView, columnCount: Int) =
        parent.adapter?.itemCount?.div(columnCount)?.minus(headerRowCount)

    private fun getOrientation(parent: RecyclerView) = when (val layoutManager = parent.layoutManager) {
        is LinearLayoutManager -> layoutManager.orientation
        is GridLayoutManager -> layoutManager.orientation
        is StaggeredGridLayoutManager -> layoutManager.orientation
        else -> RecyclerView.VERTICAL
    }

    private fun getStartOffsetWithEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return spacing
        return spacing - spacing * columnIndex / columnCount
    }

    private fun getEndOffsetWithEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return 0
        return spacing * (columnIndex + 1) / columnCount
    }

    private fun getStartOffsetWithoutEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return 0
        return spacing * columnIndex / columnCount
    }

    private fun getEndOffsetWithoutEdge(spacing: Int, columnIndex: Int, columnCount: Int?): Int {
        if (columnCount == null) return spacing
        return spacing - spacing * (columnIndex + 1) / columnCount
    }

}
UdaraWanasinghe
  • 2,622
  • 2
  • 21
  • 27
2
public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {

private final int mSpaceHeight;

public EqualSpaceItemDecoration(int mSpaceHeight) {
    this.mSpaceHeight = mSpaceHeight;
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
        RecyclerView.State state) {
    outRect.bottom = mSpaceHeight;
outRect.top = mSpaceHeight;
outRect.left = mSpaceHeight;
outRect.right = mSpaceHeight;
}

The above answer will produce unequal margin widths where the images meet.

The below solution produces even margins


 public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        
        int position= parent.getChildAdapterPosition (view);
        int column = position% numberOfColumns;       
            outRect.left= margin-column*spacingPx/numberOfColumns;
            outRect.right= (column+1)* margin/numberOfColumns;
            outRect.top= margin;           

        }

    }
Thejashwini Dev
  • 321
  • 1
  • 4
  • 9
2

You can use the addItemDecoration method in the recycler view.

ItemDecoration Class file:-

class ItemDecoration(space: Int) : RecyclerView.ItemDecoration() {
      private val halfSpace: Int
      override fun getItemOffsets(outRect: Rect,
                            view: View,
                            parent: RecyclerView,
                            state: RecyclerView.State) {
             if (parent.paddingLeft != halfSpace) {
                parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace)
                parent.clipToPadding = false
             }
             outRect.top = halfSpace
             outRect.bottom = halfSpace
             outRect.left = halfSpace
             outRect.right = halfSpace
    }

    init {
       halfSpace = space / 2
    }
}

Implementation in recyclerview:

rvFeeds.apply {
            layoutManager = mLM
            adapter = mAdapter
            itemAnimator = null
            addItemDecoration(ItemDecoration(resources.getDimension(R.dimen.margin_very_small).toInt())) 
}
Thakar Sahil
  • 248
  • 1
  • 7