Search code examples
androidandroid-recyclerviewgridlayoutmanager

How to draw colored internal grid in Recyclerview with GridLayoutManager?


I have to add internal colored grid to my RecyclerView with GridLayoutManager.

Adding space to ViewHolders is not my solution because I have rounded background in recyclerView and background in viewHolders breaks them. Standart DividerItemDecoration gives me exactly what I need, except I can choose ONLY horizontal or vertical lines and not both. I've found answers which gave me internal grid by spacing via getItemOffsets(), but I can't color fill outRect.

What is the best way to achieve this idea?


Solution

  • So I've achieved this task by simplifying standart androidx.recyclerview.widget.DividerItemDecoration by deleting all separation by HORIZONTAL and VERTICAL in code. As the result I have internal horizontal+vertical lines.

    Applying decoration:

    myRecyclerView.addItemDecoration(GridDividerItemDecoration(requireContext()))
    

    The whole decoration class:

    class GridDividerItemDecoration(context: Context) : ItemDecoration() {
        private val mBounds = Rect()
        private var mDivider: Drawable?
    
        fun setDrawable(drawable: Drawable) {
            mDivider = drawable
        }
    
        override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
            if (parent.layoutManager == null || mDivider == null) {
                return
            }
            drawVertical(c, parent)
            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
            }
            val childCount = parent.childCount
            for (i in 0 until childCount) {
                val child = parent.getChildAt(i)
                parent.getDecoratedBoundsWithMargins(child, mBounds)
                val bottom = mBounds.bottom + child.translationY.roundToInt()
                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
            }
            val childCount = parent.childCount
            for (i in 0 until childCount) {
                val child = parent.getChildAt(i)
                parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)
                val right = mBounds.right + child.translationX.roundToInt()
                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[0, 0, 0] = 0
                return
            }
            outRect[0, 0, 0] = mDivider!!.intrinsicHeight
            outRect[0, 0, mDivider!!.intrinsicWidth] = 0
        }
    
        companion object {
            private const val TAG = "DividerItem"
            private val ATTRS = intArrayOf(R.attr.listDivider)
        }
    
        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()
        }
    }