Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.