Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import androidx.compose.foundation.Canvas
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
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.translate
import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.unit.dp
@Composable
fun SimpleSlider(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colors.primary
) {
val dragArea = mutableStateOf(Rect.Zero)
Canvas(
modifier = modifier.dragGestureFilter(
startDragImmediately = true,
dragObserver = dragObserver(dragArea)
)
) {
if (initIndicatorArea(dragArea)) return@Canvas
drawProgress(dragArea.value.center.x, color)
drawIndicator(dragArea.value.center.x, color)
}
}
@Composable
fun dragObserver(indicatorArea: MutableState<Rect>): DragObserver {
var isDragging = false
val indicatorAreaRadius = indicatorRadiusPixel()
return object : DragObserver {
override fun onStart(downPosition: Offset) {
if (indicatorArea.value.contains(downPosition)) {
isDragging = true
indicatorArea.value = Rect(
center = downPosition,
radius = indicatorAreaRadius
)
}
}
override fun onDrag(dragDistance: Offset): Offset {
if (isDragging) {
indicatorArea.value = indicatorArea.value.translateX(dragDistance.x)
}
return super.onDrag(dragDistance)
}
override fun onStop(velocity: Offset) {
isDragging = false
}
}
}
private fun DrawScope.initIndicatorArea(dragArea: MutableState<Rect>): Boolean {
val notInitialized = dragArea.value == Rect.Zero
if (notInitialized) {
val circleRadiusPixel = indicatorRadiusPixel()
dragArea.value = Rect(
0f,
verticalCenter - circleRadiusPixel,
circleRadiusPixel * 2,
verticalCenter + circleRadiusPixel
)
}
return notInitialized
}
private fun DrawScope.drawProgress(
dragPosition: Float,
color: Color
) {
val strokeWidth = 12
drawLine(
color = color,
strokeWidth = strokeWidth.dp.toPx(),
start = Offset(0f, verticalCenter),
end = Offset(dragPosition, verticalCenter)
)
drawLine(
color = color.copy(alpha = 0.3f),
strokeWidth = strokeWidth.dp.toPx(),
start = Offset(dragPosition, verticalCenter),
end = Offset(size.width, verticalCenter)
)
}
private fun DrawScope.drawIndicator(
dragPosition: Float,
color: Color
) {
val circleRadiusPixel = indicatorRadiusPixel()
val canvasDragPosition = dragPosition - size.width / 2
val minPosition = -size.width / 2 + circleRadiusPixel
val maxPosition = size.width / 2 - circleRadiusPixel
val indicatorPosition = canvasDragPosition.coerceIn(minPosition, maxPosition)
translate(left = indicatorPosition) {
drawCircle(color = color, radius = circleRadiusPixel)
}
}
@Composable
private fun indicatorRadiusPixel(): Float = with(DensityAmbient.current) { 16.dp.toPx() }
private fun DrawScope.indicatorRadiusPixel(): Float = 16.dp.toPx()
private val DrawScope.verticalCenter get() = size.height / 2
@Stable
private fun Rect.translateX(translateX: Float): Rect {
return Rect(
left + translateX,
top,
right + translateX,
bottom
)
}
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.