Skip to content

Instantly share code, notes, and snippets.

@txusballesteros
Created July 13, 2018 07:59
Show Gist options
  • Save txusballesteros/54d0d857479a593527e9293410bf2d08 to your computer and use it in GitHub Desktop.
Save txusballesteros/54d0d857479a593527e9293410bf2d08 to your computer and use it in GitHub Desktop.
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.animation.DecelerateInterpolator
class OnboardingPageIndicatorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : View(context, attrs, defStyle) {
companion object {
private const val PADDING_IN_DP = 2f
private const val MINIMUM_VALUE = 0f
private const val MAXIMUM_VALUE = 100f
private const val ANIMATION_DURATION_IN_MS = 1000L
}
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var backgroundLeftCenter: PointF = PointF(0f, 0f)
private var backgroundRightCenter: PointF = PointF(0f, 0f)
private var progressLeftCenter: PointF = PointF(0f, 0f)
private var progressRightCenter: PointF = PointF(0f, 0f)
private lateinit var backgroundLeftRect: RectF
private lateinit var backgroundRightRect: RectF
private lateinit var progressLeftRect: RectF
private lateinit var progressRightRect: RectF
private lateinit var backgroundPath: Path
private lateinit var progressPath: Path
private lateinit var animator: ValueAnimator
var value: Float = 33.3f
set(value) {
if (value in MINIMUM_VALUE..MAXIMUM_VALUE) {
field = value
playAnimation()
}
}
private val padding: Float
get() = dp2px(PADDING_IN_DP)
private val progressRadious: Float
get() = ((measuredHeight.toFloat() - (padding * 2)) / 2f)
private val backgroundRadious: Float
get() = (measuredHeight.toFloat() / 2f)
private val progressOffset: Float
get() = (measuredWidth.toFloat() / (MAXIMUM_VALUE - MINIMUM_VALUE))
private val calculatedProgressX: Float
get() = ((value * progressOffset) - progressRadious - padding)
init {
backgroundPaint.color = resources.getColor(R.color.purple)
backgroundPaint.style = Paint.Style.FILL
progressPaint.color = resources.getColor(R.color.light_purple)
progressPaint.style = Paint.Style.FILL
}
private fun playAnimation() {
if (::animator.isInitialized) animator.cancel()
val currentCenterX = progressRightCenter.x
val newCenterX = calculatedProgressX
animator = ValueAnimator.ofFloat(currentCenterX, newCenterX)
animator.duration = ANIMATION_DURATION_IN_MS
animator.interpolator = DecelerateInterpolator()
animator.addUpdateListener {
val positionX = (animator.animatedValue as Float)
calculateProgressCenters(positionX)
invalidate()
}
animator.start()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
setMeasuredDimension(width, height)
calculateBackgroundCenters()
calculateBackgroundRects()
calculateBackgroundPath()
calculateProgressCenters(calculatedProgressX)
}
private fun calculateBackgroundCenters() {
backgroundLeftCenter = PointF(backgroundRadious, backgroundRadious)
backgroundRightCenter = PointF(measuredWidth - backgroundRadious, backgroundRadious)
}
private fun calculateProgressCenters(rightX: Float) {
val progressLeftX = (padding + progressRadious)
var progressRightX = rightX
if (progressRightX < progressLeftX) progressRightX = progressLeftX
progressLeftCenter = PointF(progressLeftX, padding + progressRadious)
progressRightCenter = PointF(progressRightX, padding + progressRadious)
}
private fun calculateBackgroundRects() {
backgroundLeftRect = RectF(
backgroundLeftCenter.x - backgroundRadious,
backgroundLeftCenter.y - backgroundRadious,
backgroundLeftCenter.x + backgroundRadious,
backgroundLeftCenter.y + backgroundRadious
)
backgroundRightRect = RectF(
backgroundRightCenter.x - backgroundRadious,
backgroundRightCenter.y - backgroundRadious,
backgroundRightCenter.x + backgroundRadious,
backgroundRightCenter.y + backgroundRadious
)
}
private fun calculateProgressRects() {
progressLeftRect = RectF(
progressLeftCenter.x - progressRadious,
progressLeftCenter.y - progressRadious,
progressLeftCenter.x + progressRadious,
progressLeftCenter.y + progressRadious
)
progressRightRect = RectF(
progressRightCenter.x - progressRadious,
progressRightCenter.y - progressRadious,
progressRightCenter.x + progressRadious,
progressRightCenter.y + progressRadious
)
}
private fun calculateBackgroundPath() {
backgroundPath = Path()
backgroundPath.arcTo(backgroundLeftRect, 90f, 180f)
backgroundPath.arcTo(backgroundRightRect, 270f, 180f)
backgroundPath.close()
}
private fun calculateProgressPath() {
progressPath = Path()
progressPath.arcTo(progressLeftRect, 90f, 180f)
progressPath.arcTo(progressRightRect, 270f, 180f)
progressPath.close()
}
override fun onDraw(canvas: Canvas) {
calculateProgressRects()
calculateProgressPath()
canvas.drawPath(backgroundPath, backgroundPaint)
canvas.drawPath(progressPath, progressPaint)
}
private fun dp2px(value: Float): Float =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment