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.