Created
April 5, 2023 07:42
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a preview in compose for desktop: