Skip to content

Instantly share code, notes, and snippets.

@AdrianoCelentano
Last active October 24, 2023 10:44
Show Gist options
  • Save AdrianoCelentano/23593701dd45af8d125255dd79586e4e to your computer and use it in GitHub Desktop.
Save AdrianoCelentano/23593701dd45af8d125255dd79586e4e to your computer and use it in GitHub Desktop.
Circular Slider with Jetpack Compose
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.gesture.DragObserver
import androidx.compose.ui.gesture.dragGestureFilter
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.unit.dp
import kotlin.math.*
//Math
//r = √(x2 + y2)
//x = r * cos(phi) -> arcos(x/r) = phi
//y = r * sin (phi)
//r - radius
//phi - angle
@Composable
fun RoundSlider(modifier: Modifier = Modifier) {
val dragPosition = mutableStateOf(Offset.Zero)
Canvas(
modifier = modifier.dragGestureFilter(
dragObserver = object : DragObserver {
override fun onStart(downPosition: Offset) {
dragPosition.value = downPosition
}
override fun onDrag(dragDistance: Offset): Offset {
dragPosition.value = dragPosition.value + dragDistance
return super.onDrag(dragDistance)
}
}
)
) {
val (indicatorX, indicatorY) = calculateIndicatorPosition(dragPosition)
translate(indicatorX, indicatorY) {
drawCircle(
color = Color.Magenta,
radius = indicatorCircleRadius(),
style = Fill
)
}
drawCircle(
color = Color.Magenta.copy(alpha = 0.4f),
radius = outerCircleRadius(),
style = Stroke(width = 6.dp.toPx())
)
}
}
private fun DrawScope.calculateIndicatorPosition(dragPosition: MutableState<Offset>): Offset {
val dragXOnCanvas = dragPosition.value.x - horizontalCenter
val dragYOnCanvas = dragPosition.value.y - verticalCenter
val radius = radiusForPoint(dragXOnCanvas, dragYOnCanvas)
val angle = acos(dragXOnCanvas / radius)
val adjustedAngle = if (dragYOnCanvas < 0) angle * -1 else angle
val xOnCircle = outerCircleRadius() * cos(adjustedAngle)
val yOnCircle = outerCircleRadius() * sin(adjustedAngle)
return Offset(xOnCircle, yOnCircle)
}
fun radiusForPoint(x: Float, y: Float): Float {
return sqrt(x.pow(2) + y.pow(2))
}
fun DrawScope.indicatorCircleRadius(): Float {
return outerCircleRadius() / 12
}
private fun DrawScope.outerCircleRadius(): Float {
return (horizontalCenter).coerceAtMost(verticalCenter)
}
private val DrawScope.horizontalCenter get() = size.width / 2
private val DrawScope.verticalCenter get() = size.height / 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment