Reputation: 967
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
?
Upvotes: 18
Views: 53341
Reputation: 269
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()))
}
Upvotes: 3
Reputation: 2852
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.
In the first case, we consider margin around the edges.
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
Here we do our maths without considering edges.
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
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
}
}
Upvotes: 7
Reputation: 12379
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;
}
}
Upvotes: 7
Reputation: 331
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;
}
}
Upvotes: 2
Reputation: 3909
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:
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.
Upvotes: 17