Skip to content

Instantly share code, notes, and snippets.

@Veeyikpong
Last active October 31, 2024 11:36
Show Gist options
  • Save Veeyikpong/a376c6128e603b0dee316670a74b345b to your computer and use it in GitHub Desktop.
Save Veeyikpong/a376c6128e603b0dee316670a74b345b to your computer and use it in GitHub Desktop.
MiddleDividerItemDecoration, suitable for both LinearLayoutManager and GridLayoutManager.
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.recyclerview.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
/**
* MiddleDividerItemDecoration is a [RecyclerView.ItemDecoration] that can be used as a divider
* between items of a [LinearLayoutManager]. It supports both [.HORIZONTAL] and
* [.VERTICAL] orientations.
* It can also supports [.ALL], included both the horizontal and vertical. Mainly used for GridLayout.
* <pre>
* For normal usage with LinearLayout,
* val mItemDecoration = MiddleDividerItemDecoration(context!!,DividerItemDecoration.VERTICAL)
* For GridLayoutManager with inner decorations,
* val mItemDecoration = MiddleDividerItemDecoration(context!!,MiddleDividerItemDecoration.ALL)
* recyclerView.addItemDecoration(mItemDecoration);
</pre> *
*/
class MiddleDividerItemDecoration
/**
* Creates a divider [RecyclerView.ItemDecoration] that can be used with a
* [LinearLayoutManager].
*
* @param context Current context, it will be used to access resources.
* @param orientation Divider orientation. Should be [.HORIZONTAL] or [.VERTICAL].
*/
(context: Context, orientation: Int) : RecyclerView.ItemDecoration() {
private var mDivider: Drawable? = null
/**
* Current orientation. Either [.HORIZONTAL] or [.VERTICAL].
*/
private var mOrientation: Int = 0
private val mBounds = Rect()
init {
val a = context.obtainStyledAttributes(ATTRS)
mDivider = a.getDrawable(0)
if (mDivider == null) {
Log.w(
TAG,
"@android:attr/listDivider was not set in the theme used for this " + "DividerItemDecoration. Please set that attribute all call setDrawable()"
)
}
a.recycle()
setOrientation(orientation)
}
fun setDividerColor(color: Int) {
mDivider!!.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
}
/**
* Sets the orientation for this divider. This should be called if
* [RecyclerView.LayoutManager] changes orientation.
*
* @param orientation [.HORIZONTAL] or [.VERTICAL]
*/
fun setOrientation(orientation: Int) {
if (orientation != HORIZONTAL && orientation != VERTICAL && orientation != ALL) {
throw IllegalArgumentException(
"Invalid orientation. It should be either HORIZONTAL or VERTICAL"
)
}
mOrientation = orientation
}
/**
* Sets the [Drawable] for this divider.
*
* @param drawable Drawable that should be used as a divider.
*/
fun setDrawable(drawable: Drawable) {
if (drawable == null) {
throw IllegalArgumentException("Drawable cannot be null.")
}
mDivider = drawable
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null || mDivider == null) {
return
}
when (mOrientation) {
ALL -> {
drawVertical(c, parent)
drawHorizontal(c, parent)
}
VERTICAL -> drawVertical(c, parent)
else -> drawHorizontal(c, parent)
}
}
private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
canvas.save()
val left: Int
val right: Int
if (parent.clipToPadding) {
left = parent.paddingLeft
right = parent.width - parent.paddingRight
canvas.clipRect(
left, parent.paddingTop, right,
parent.height - parent.paddingBottom
)
} else {
left = 0
right = parent.width
}
var childCount = parent.childCount
if (parent.layoutManager is GridLayoutManager) {
var leftItems = childCount % (parent.layoutManager as GridLayoutManager).spanCount
if (leftItems == 0) {
leftItems = (parent.layoutManager as GridLayoutManager).spanCount
}
//Identify last row, and don't draw border for these items
childCount -= leftItems
}
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i) ?: return
parent.getDecoratedBoundsWithMargins(child, mBounds)
val bottom = mBounds.bottom + Math.round(child.translationY)
val top = bottom - mDivider!!.intrinsicHeight
mDivider!!.setBounds(left, top, right, bottom)
mDivider!!.draw(canvas)
}
canvas.restore()
}
private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
canvas.save()
val top: Int
val bottom: Int
if (parent.clipToPadding) {
top = parent.paddingTop
bottom = parent.height - parent.paddingBottom
canvas.clipRect(
parent.paddingLeft, top,
parent.width - parent.paddingRight, bottom
)
} else {
top = 0
bottom = parent.height
}
var childCount = parent.childCount
if (parent.layoutManager is GridLayoutManager) {
childCount = (parent.layoutManager as GridLayoutManager).spanCount
}
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i) ?: return
parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)
val right = mBounds.right + Math.round(child.translationX)
val left = right - mDivider!!.intrinsicWidth
mDivider!!.setBounds(left, top, right, bottom)
mDivider!!.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State
) {
if (mDivider == null) {
outRect.set(0, 0, 0, 0)
return
}
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider!!.intrinsicHeight)
} else {
outRect.set(0, 0, mDivider!!.intrinsicWidth, 0)
}
}
companion object {
val HORIZONTAL = LinearLayout.HORIZONTAL
val VERTICAL = LinearLayout.VERTICAL
//mainly used for GridLayoutManager
val ALL = 2
private val TAG = "DividerItem"
private val ATTRS = intArrayOf(android.R.attr.listDivider)
}
}
@GaelCO
Copy link

GaelCO commented May 6, 2020

Hi,
Thanks for your sample of ItemDecoration.
I see a little bug. The ALL case is forgotten in getItemOffsets.
You can replace :
if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider!!.intrinsicHeight) } else { outRect.set(0, 0, mDivider!!.intrinsicWidth, 0) }
by
when (mOrientation) { ALL -> { outRect.set(0, 0, mDivider!!.intrinsicWidth, mDivider!!.intrinsicHeight) } VERTICAL -> {outRect.set(0, 0, 0, mDivider!!.intrinsicHeight) } else -> { outRect.set(0, 0, mDivider!!.intrinsicWidth, 0) } }

@WSAyan
Copy link

WSAyan commented Sep 30, 2020

This is a very cool solution. Thanks a lot.

@shenguojun
Copy link

shenguojun commented Jul 25, 2022

  var childCount = parent.childCount
        if (parent.layoutManager is GridLayoutManager) {
            childCount = (parent.layoutManager as GridLayoutManager).spanCount
        }

        for (i in 0 until childCount - 1) {
              val child = parent.getChildAt(i)

Here will NPE when children count is small than span count, shoud change to

for (i in 0 until (childCount - 1).coerceAtMost(parent.childCount)) {
            val child = parent.getChildAt(i)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment