Get Thumb position (x, y) of Slider in Jetpack Compose

947 Views Asked by At

Three sliders are defined in Jetpack Compose and the thumb positions of these three sliders are needed to connect them using a Path in a Canvas behind them. How to get the position PointF(x,y) of the thumb in each Slider to draw the Path or any other way to achieve this? Below is sample code snippet and image of sample implementation to achieve.

@Composable
fun MySliderDemo() {
    var sliderPosition1 = remember { mutableStateOf(0f) }
    var sliderPosition2 = remember { mutableStateOf(0f) }
    var sliderPosition3 = remember { mutableStateOf(0f) }

    Slider(value = sliderPosition1.value, onValueChange = { sliderPosition1.value = it })
    Slider(value = sliderPosition2.value, onValueChange = { sliderPosition2.value = it })
    Slider(value = sliderPosition3.value, onValueChange = { sliderPosition3.value = it })
}

Sample implementation

2

There are 2 best solutions below

9
user496854 On

var pos1 by remember { mutableStateOf(0f) } var pos2 by remember { mutableStateOf(0f) } var pos3 by remember { mutableStateOf(0f) }

var layoutCoordinates = 0

Slider(value = pos1, onValueChangeFinished = { newPos1 ->
    // you now have 3 slider positions:
    // newPos1, pos2, and pos3
}, onValueChange = { pos1 = it },
    modifier = Modifier.onGloballyPositioned {
        layoutCoordinates = it
    })

Slider(value = pos2, onValueChangeFinished = { newPos2 ->
    // you now have 3 slider positions:
    // pos1, newPos2, and pos3
}, onValueChange = { pos2 = it })

Slider(value = pos3, onValueChangeFinished = { newPos3 ->
    // you now have 3 slider positions:
    // pos1, pos2, and newPos3
}, onValueChange = { pos3 = it })

with every Slider's onValueChangeFinished, you get each of their values. You can also get the LayoutCoordinates of the Slider (I assume you only need to get it from one of them since they're all the same size, but if not, you can just keep track of all 3) from Modifier.onGloballyPositioned. From there, you should be able to calculate the position of each slider based on the Slider value and the layout coordinates (which give you position, size, etc.)

0
Astro On

Generally, you can determine the position of an element(thumb) within a component(slider) by finding the start and the end of the component(slider), then by using a fraction of the current progress of the component, you could use linear interpolation to determine the exact position. Values of offsetStartExtra and offsetEndExtra variables are are based on experiments just to get the thumb axies right, you may want to change them to other values based on your padding, thumb size, parent position, etc. If needed, play around with them until you get it rigth within your layout.

Slider (Horizontal):

Notice I calculate the end and the start of the slider on onGloballyPositioned

var sliderValue by remember { mutableStateOf(0f) }

var start by remember { mutableStateOf(Offset.Zero) }
var end by remember { mutableStateOf(Offset.Zero) }
var thumbCoordinates by remember { mutableStateOf(Offset.Zero) }

Box(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
) {
    Slider(
        value = sliderValue,
        onValueChange = { newValue ->
            sliderValue = newValue
            // updating the thumbCoordinate using linear interpolation
            thumbCoordinates = start + (end - start) * (sliderValue / maxSliderValue)
        },
        valueRange = minSliderValue..maxSliderValue,
        modifier = Modifier
            .align(Alignment.Center)
            .onGloballyPositioned { coordinates ->
                // calculating start and end of the slider
                val posInRoot = coordinates.positionInRoot()

                val offsetStartExtra = Offset(x = -28f, y = 10f)
                start = posInRoot + offsetStartExtra


                val offsetEndExtra = Offset(x = -83f, 10f)
                end = posInRoot.copy(posInRoot.x + coordinates.size.width.toFloat()) + offsetEndExtra
                thumbCoordinates=start + (end - start) * (sliderValue/range.endInclusive)

            }
    )

    Canvas(modifier = Modifier.fillMaxSize()) {
        // using the calculated coordinates here...
        drawRect(color = Color.Red, topLeft = thumbCoordinates, size = Size(24f, 24f))
    }

}

Result:

enter image description here

Slider (Vertical)

If you have a vertical slider (check this answer), then you might need to tweak some things for start and end variables as well as the offset extras in onGloballyPositioned modifier.

....
.onGloballyPositioned { coordinates ->
                val posInRoot = coordinates.positionInRoot()

                val startOffsetExtra = Offset(x = 54f, y = -40f)
                start = posInRoot + startOffsetExtra
                
                val endOffsetExtra = Offset(x = 54f, y = 15f)
                end = posInRoot.copy(y = posInRoot.y - coordinates.size.width.toFloat()) + endOffsetExtra
            }
....

Result

enter image description here

Now for your case, you could draw a line with canvas provding startPoint as position of first thumb, and end point the position of the 2nd thumb, then you could draw another line starting from 2nd thumb and ending on the 3rd. Or you could just draw a path with canvas providing all the thumb's positions.