Page Indicator for RecyclerView

Recycler View Paging

The following code is heavily based on this source.

  • Modified indicator color and margin/padding
  • Changed shape from Line to Circle (line with rounded corner)
class CirclePagerIndicatorDecoration : RecyclerView.ItemDecoration() {    private val colorActive = Color.parseColor("#615A58")    private val colorInactive = Color.parseColor("#BBAFAC")    /**     * Height of the space the indicator takes up at the bottom of the view.     */    private val mIndicatorHeight = (DP * 32).toInt()    /**     * Indicator stroke width.     */    private val mIndicatorStrokeWidth = DP * 8    /**     * Indicator width.     */    private val mIndicatorItemLength = DP * 2    /**     * Padding between indicators.     */    private val mIndicatorItemPadding = DP * 12    /**     * Some more natural animation interpolation     */    private val mInterpolator = AccelerateDecelerateInterpolator()    private val mPaint = Paint()    init {        mPaint.strokeCap = Cap.ROUND        mPaint.strokeWidth = mIndicatorStrokeWidth        mPaint.style = Paint.Style.STROKE        mPaint.isAntiAlias = true    }    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {        super.onDrawOver(c, parent, state)        val itemCount = parent.adapter!!.itemCount        // center horizontally, calculate width and subtract half from center        val totalLength = mIndicatorItemLength * itemCount        val paddingBetweenItems = Math.max(0, itemCount - 1) * mIndicatorItemPadding        val indicatorTotalWidth = totalLength + paddingBetweenItems        val indicatorStartX = (parent.width - indicatorTotalWidth) / 2f        // center vertically in the allotted space        val indicatorPosY = parent.height - mIndicatorHeight / 2f        drawInactiveIndicators(c, indicatorStartX, indicatorPosY, itemCount)        // find active page (which should be highlighted)        val layoutManager = parent.layoutManager as LinearLayoutManager?        val activePosition = layoutManager!!.findFirstVisibleItemPosition()        if (activePosition == RecyclerView.NO_POSITION) {            return        }        // find offset of active page (if the user is scrolling)        val activeChild = layoutManager.findViewByPosition(activePosition)        val left = activeChild!!.left        val width = activeChild.width        // on swipe the active item will be positioned from [-width, 0]        // interpolate offset for smooth animation        val progress = mInterpolator.getInterpolation(left * -1 / width.toFloat())        drawHighlights(c, indicatorStartX, indicatorPosY, activePosition, progress, itemCount)    }    private fun drawInactiveIndicators(c: Canvas, indicatorStartX: Float, indicatorPosY: Float, itemCount: Int) {        mPaint.color = colorInactive        // width of item indicator including padding        val itemWidth = mIndicatorItemLength + mIndicatorItemPadding        var start = indicatorStartX        for (i in 0 until itemCount) {            // draw the line for every item            c.drawLine(start, indicatorPosY, start + mIndicatorItemLength, indicatorPosY, mPaint)            start += itemWidth        }    }    private fun drawHighlights(        c: Canvas, indicatorStartX: Float, indicatorPosY: Float,        highlightPosition: Int, progress: Float, itemCount: Int    ) {        mPaint.color = colorActive        // width of item indicator including padding        val itemWidth = mIndicatorItemLength + mIndicatorItemPadding        if (progress == 0f) {            // no swipe, draw a normal indicator            val highlightStart = indicatorStartX + itemWidth * highlightPosition            c.drawLine(                highlightStart, indicatorPosY,                highlightStart + mIndicatorItemLength, indicatorPosY, mPaint            )        } else {            var highlightStart = indicatorStartX + itemWidth * highlightPosition            // calculate partial highlight            val partialLength = mIndicatorItemLength * progress            // draw the cut off highlight            c.drawLine(                highlightStart + partialLength, indicatorPosY,                highlightStart + mIndicatorItemLength, indicatorPosY, mPaint            )            // draw the highlight overlapping to the next item as well            if (highlightPosition < itemCount - 1) {                highlightStart += itemWidth                c.drawLine(                    highlightStart, indicatorPosY,                    highlightStart + partialLength, indicatorPosY, mPaint                )            }        }    }    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {        super.getItemOffsets(outRect, view, parent, state)        outRect.bottom = mIndicatorHeight    }    companion object {        private val DP = Resources.getSystem().displayMetrics.density    }}

Usage

recylerView.addItemDecoration(CirclePagerIndicatorDecoration())

NOTE: You might want to refer to Android Paging RecylerView as ViewPager.

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.