Skip to content

Instantly share code, notes, and snippets.

@TalbotGooday
Last active February 24, 2022 01:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TalbotGooday/539a0b0dcfb90c892acd0878c6cf0ad7 to your computer and use it in GitHub Desktop.
Save TalbotGooday/539a0b0dcfb90c892acd0878c6cf0ad7 to your computer and use it in GitHub Desktop.
Android iOS-like progress
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.util.DisplayMetrics
import androidx.annotation.ColorInt
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.roundToInt
import kotlin.math.sin
class ProgressDrawable(
private val context: Context,
sizeDp: Int = 24,
private val dashThicknessDp: Int = 2,
private val dashesCount: Int = 8,
private val tickTimeMillis: Long = 80,
) : Drawable() {
private val size = context.dp(sizeDp)
private var innerCircleRadius = 0f
private var circleRadius = 0f
private var shaderRadius = 0f
private var circleCenter = 0f
private var start = 0f
private var end = 0f
private var top = 0f
private var bottom = 0f
private val handler = Handler(Looper.getMainLooper())
private val updateRunnable = UpdateRunnable()
private val angle = 360 / dashesCount
private var currentDegree = 0f
private var tick = 0
@ColorInt
private var colorLight: Int = Color.WHITE
@ColorInt
private var colorDark: Int = Color.LTGRAY
private var paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.color = Color.WHITE
this.strokeWidth = context.dp(dashThicknessDp).toFloat()
this.strokeCap = Paint.Cap.ROUND
this.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
}
init {
updateRunnable.run()
}
fun setColors(@ColorInt colorLight: Int, @ColorInt colorDark: Int) {
this.colorLight = colorLight
this.colorDark = colorDark
}
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setBounds(left, top, right, bottom)
initSize(right)
}
override fun draw(canvas: Canvas) {
(0..dashesCount).forEach {
val radian = (it * angle / 180f * PI).toFloat()
canvas.drawLine(
circleCenter + circleRadius * cos(radian),
circleCenter + circleRadius * sin(radian),
circleCenter + innerCircleRadius * cos(radian),
circleCenter + innerCircleRadius * sin(radian),
paint
)
}
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
@Deprecated(message = "use setColors instead")
override fun setColorFilter(colorFilter: ColorFilter?) {
invalidateSelf()
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
private fun initSize(width: Int) {
shaderRadius = (size / 2.0f)
innerCircleRadius = size / 6f
circleRadius = shaderRadius - innerCircleRadius
circleCenter = width / 2f
top = circleCenter - size / 2f
bottom = top + size
start = top
end = bottom
rotateShader(22f)
invalidateSelf()
}
private fun rotateShader(degree: Float) {
paint.shader = SweepGradient(
circleCenter,
circleCenter,
colorLight,
colorDark
).apply {
colorFilter
val gradientMatrix = Matrix()
gradientMatrix.preRotate(degree, circleCenter, circleCenter)
setLocalMatrix(gradientMatrix)
}
}
inner class UpdateRunnable : Runnable {
override fun run() {
currentDegree = (tick * angle).toFloat()
rotateShader(currentDegree + angle / 2f)
if (tick >= dashesCount) {
tick = 0
}
tick++
invalidateSelf()
handler.postDelayed(updateRunnable, tickTimeMillis)
}
}
}
fun Context.dp(dp: Int): Int {
return dp(dp.toFloat())
}
fun Context.dp(dp: Float): Int {
val metrics = resources.displayMetrics
return (dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat())).roundToInt()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment