DividerItemDecoration supports both vertical and horizontal lists. Takes optional res id as a divider drawable as parameters
GridItemDecorator respects margins between grid items in a RecyclerView backed by GridLayoutManager
item_devider.xml
HorizontalSpaceItemDecoration and VerticalSpaceItemDecoration
VerticalSpaceItemDeoration
ItemOffsetDecoration
class ItemOffsetDecoration(private val spacingDp: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State) {
val context = parent.context
val spacingPx = context.dp(spacingDp.toFloat())
super.getItemOffsets(outRect, view, parent, state)
outRect.set(spacingPx, spacingPx, spacingPx, spacingPx)
}
private fun Context.dp(dp: Float) = Math.round(dp * resources.displayMetrics.density)
}
class GridItemDecorator(private val gridSize: Int,
private val spacingDp: Int = 0,
private val top: Int = 0,
private val sides: Boolean = false) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val context = parent.context
val spacingPx = context.dp(spacingDp.toFloat())
val bit = if (spacingPx > gridSize || spacingPx == 0f) Math.round(spacingPx / gridSize) else 1
val itemPosition = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition
outRect.top = if (itemPosition < gridSize) context.dp(top) else bit * gridSize
val rowPosition = itemPosition % gridSize
if (sides) {
outRect.left = (gridSize * bit) - (rowPosition * bit)
outRect.right = (rowPosition + 1) * bit
} else {
outRect.left = rowPosition * bit
outRect.right = (gridSize - rowPosition - 1) * bit
}
}
private fun Context.dp(dp: Int) = Math.round(dp * resources.displayMetrics.density)
private fun Context.dp(dp: Float) = Math.round(dp * resources.displayMetrics.density).toFloat()
}
// Add bottom margin through layout
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:background="@color/colorGrayLight"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
// Supports both vertical and horizontal lists.
// Takes optional res id as a divider drawable
class MyDividerItemDecoration(context: Context, resId: Int? = null, orientation: Int = VERTICAL_LIST) : RecyclerView.ItemDecoration() {
private val divider: Drawable
private var orientation: Int = 0
init {
if (resId == null) {
val a = context.obtainStyledAttributes(ATTRS)
divider = a.getDrawable(0)
a.recycle()
} else {
divider = ContextCompat.getDrawable(context, resId)
?: throw IllegalArgumentException("Drawable with id $resId doesn't exist!")
}
setOrientation(orientation)
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (orientation == VERTICAL_LIST) {
drawVertical(c, parent)
} else {
drawHorizontal(c, parent)
}
}
private fun drawVertical(c: Canvas, parent: RecyclerView) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
val params = child
.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
divider.setBounds(left, top, right, bottom)
divider.draw(c)
}
}
private fun drawHorizontal(c: Canvas, parent: RecyclerView) {
val top = parent.paddingTop
val bottom = parent.height - parent.paddingBottom
val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
val params = child
.layoutParams as RecyclerView.LayoutParams
val left = child.right + params.rightMargin
val right = left + divider.intrinsicHeight
divider.setBounds(left, top, right, bottom)
divider.draw(c)
}
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
if (orientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, divider.intrinsicHeight)
} else {
outRect.set(0, 0, divider.intrinsicWidth, 0)
}
}
private fun setOrientation(orientation: Int) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw IllegalArgumentException("invalid orientation")
}
this.orientation = orientation
}
companion object {
private val ATTRS = intArrayOf(android.R.attr.listDivider)
val HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL
val VERTICAL_LIST = LinearLayoutManager.VERTICAL
}
}
class VerticalSpaceItemDecoration(resources: Resources,
vSpaceDip: Int,
headerDip: Int = -1,
footerDip: Int = -1,
val isDip: Boolean = true) : RecyclerView.ItemDecoration() {
private val header = if (headerDip == -1) vSpaceDip else headerDip
private val footer = if (footerDip == -1) vSpaceDip else footerDip
private val mVerticalSpaceHeight = if (isDip) dp(vSpaceDip, resources) else vSpaceDip
private val mHeaderHeight = if (isDip) dp(header, resources) else header
private val mFooterHeight = if (isDip) dp(footer, resources) else footer
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
when {
parent.getChildAdapterPosition(view) == 0 -> {
// vertical spaces for the first element
outRect.top = mHeaderHeight
outRect.bottom = mVerticalSpaceHeight
// vertical spaces for the last element
}
parent.getChildAdapterPosition(view) == parent.adapter.itemCount - 1 -> outRect.bottom = mFooterHeight
else -> // vertical spaces for other elements
outRect.bottom = mVerticalSpaceHeight
}
}
}
class HorizontalItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val spacePx = dp(space, view.context.resources)
outRect.right = spacePx
// Add top margin only for the first item to avoid double space between items
if (parent.getChildLayoutPosition(view) == 0)
outRect.left = spacePx
}
}
fun dp(dp: Int, resources: Resources) = Math.round(dp * resources.displayMetrics.density)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
<solid android:color="@color/colorGray" />
</shape>