Page Indicator for RecyclerView
May 4, 2019The 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.
- algo-trading
- algolia
- analytics
- android
- android-ktx
- android-permission
- android-studio
- apps-script
- bash
- binance
- bootstrap
- bootstrapvue
- chartjs
- chrome
- cloud-functions
- coding-interview
- contentresolver
- coroutines
- crashlytics
- crypto
- css
- dagger2
- datastore
- datetime
- docker
- eslint
- firebase
- firebase-auth
- firebase-hosting
- firestore
- firestore-security-rules
- flask
- fontawesome
- fresco
- git
- github
- glide
- godot
- google-app-engine
- google-cloud-storage
- google-colab
- google-drive
- google-maps
- google-places
- google-play
- google-sheets
- gradle
- html
- hugo
- inkscape
- java
- java-time
- javascript
- jetpack-compose
- jetson-nano
- kotlin
- kotlin-serialization
- layout
- lets-encrypt
- lifecycle
- linux
- logging
- lubuntu
- markdown
- mate
- material-design
- matplotlib
- md5
- mongodb
- moshi
- mplfinance
- mysql
- navigation
- nginx
- nodejs
- npm
- nuxtjs
- nvm
- pandas
- payment
- pip
- pwa
- pyenv
- python
- recylerview
- regex
- room
- rxjava
- scoped-storage
- selenium
- social-media
- ssh
- ssl
- static-site-generator
- static-website-hosting
- sublime-text
- ubuntu
- unit-test
- uwsgi
- viewmodel
- viewpager2
- virtualbox
- vue-chartjs
- vue-cli
- vue-router
- vuejs
- vuelidate
- vuepress
- web-development
- web-hosting
- webpack
- windows
- workmanager
- wsl
- yarn