Skip to content

Instantly share code, notes, and snippets.

@thebino
Created April 5, 2023 07:42
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 thebino/e6949bd00ec2f881e8f113e7dfbdef79 to your computer and use it in GitHub Desktop.
Save thebino/e6949bd00ec2f881e8f113e7dfbdef79 to your computer and use it in GitHub Desktop.
A center slider in Jetpack compose starting in the center of the slider and having percentage values
package composables
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.roundToInt
/**
* Slider with a progress starting in the center instead of left/start
*
* e.g.: having a progress of 0.25
*
* 0% 50% 100%
* |----------xxxxxxxxxx|--------------------|
* center
*
* or a progress of 0.75
*
* 0% 50% 100%
* |---------------------|----------xxxxxxxxxx|
* center
*/
@Composable
fun CenterSlider(
modifier: Modifier = Modifier,
progress: Float = 0.25f,
onProgressChanged: (Float) -> Unit,
) {
val trackColor: Color = Color(0xFF9494A4)
val trackProgressColor: Color = Color(0xFF432EE0)
val trackHeight: Dp = 6.dp
val thumbColor: Color = Color.White
val thumbBorderColor: Color = trackProgressColor
val thumbBorderWidth: Dp = 3.dp
val tickHeight: Dp = 10.dp
val tickColor: Color = trackColor
val tickWidth: Dp = 2.dp
var isPressed by remember { mutableStateOf(false) }
var offsetX by remember { mutableStateOf(10f) }
var parentSize by remember { mutableStateOf(IntSize.Zero) }
Box(modifier = Modifier) {
Canvas(modifier = modifier
.background(Color.LightGray)
.fillMaxSize()
.padding(20.dp)
.onSizeChanged {
parentSize = it
}.draggable(orientation = Orientation.Horizontal, state = rememberDraggableState { delta ->
val isInsideBounds = (offsetX + delta) >= 0 && (offsetX + delta) <= parentSize.width
// limit to track boundaries
if (isInsideBounds) {
offsetX += delta
}
}, onDragStopped = { isPressed = false }, onDragStarted = { isPressed = true })) {
val canvasWidth = size.width
val canvasHeight = size.height
val canvasCenterX = canvasWidth / 2
val canvasCenterY = canvasHeight / 2
// map offset to canvasWidth to get the progress %
onProgressChanged(
if (offsetX in 0.0..canvasWidth.toDouble()) {
(offsetX / canvasWidth) * 100
} else {
0f
}
)
// Track
drawLine(
start = Offset(x = 0f, y = canvasCenterY),
end = Offset(x = canvasWidth, y = canvasCenterY),
color = trackColor.copy(alpha = 0.3f),
strokeWidth = trackHeight.toPx(),
cap = StrokeCap.Square
)
// helper for the vertical center
drawLine(
color = tickColor,
start = Offset(x = canvasCenterX, y = canvasCenterY - (tickHeight.toPx())),
end = Offset(x = canvasCenterX, y = canvasCenterY + (tickHeight.toPx())),
strokeWidth = tickWidth.toPx(),
)
// Progress Track
val trackStart = if (offsetX < canvasCenterX) {
Offset(x = offsetX, y = canvasCenterY)
} else {
Offset(x = canvasCenterX + tickWidth.toPx(), y = canvasCenterY)
}
val trackEnd = if (offsetX < canvasCenterX) {
Offset(x = canvasCenterX - tickWidth.toPx(), y = canvasCenterY)
} else {
Offset(x = offsetX, y = canvasCenterY)
}
drawLine(
start = trackStart,
end = trackEnd,
color = trackProgressColor,
strokeWidth = trackHeight.toPx(),
cap = StrokeCap.Square
)
// Thumb
drawCircle(
color = thumbBorderColor,
center = Offset(x = offsetX, y = canvasCenterY),
radius = trackHeight.plus(thumbBorderWidth).toPx()
)
drawCircle(
color = thumbColor, center = Offset(x = offsetX, y = canvasCenterY), radius = trackHeight.toPx()
)
}
// current value above the thumb
Text(
text = "${progress.roundToInt()} %",
fontSize = 20.sp,
modifier = Modifier
.align(Alignment.CenterStart)
.offset {
IntOffset(offsetX.toInt(), -80)
}
)
// center value
Text(
text = "50 %",
fontSize = 20.sp,
modifier = Modifier
.align(Alignment.CenterStart)
.offset {
IntOffset(parentSize.center.x, +80)
}
)
}
}
@thebino
Copy link
Author

thebino commented Apr 5, 2023

Here is a preview in compose for desktop:

Preview

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment