Reset offset animation on draggable item in Jetpack Compose
Asked Answered
T

1

7

I have a green square that I can drag vertically. But whenever I stop dragging it, I want it to reset the offset to the start with an animation. I tried it like this, but I can't figure it out. Does someone know how to do it?

@Composable
fun DraggableSquare() {
    var currentOffset by remember { mutableStateOf(0F) }
    val resetAnimation by animateIntOffsetAsState(targetValue = IntOffset(0, currentOffset.roundToInt()))

    var shouldReset = false

    Box(contentAlignment = Alignment.TopCenter, modifier = Modifier.fillMaxSize()) {
        Surface(
            color = Color(0xFF34AB52),
            modifier = Modifier
                .size(100.dp)
                .offset {
                    when {
                        shouldReset -> resetAnimation
                        else -> IntOffset(0, currentOffset.roundToInt())
                    }
                }
                .draggable(
                    state = rememberDraggableState { delta -> currentOffset += delta },
                    orientation = Orientation.Vertical,
                    onDragStopped = {
                        shouldReset = true
                        currentOffset = 0F
                    }
                )
        ) {}
    }
}
Totality answered 28/4, 2021 at 16:51 Comment(0)
S
17

You can define the offset as an Animatable.
While dragging use the method snapTo to update the current value as the initial value and the onDragStopped to start the animation.

val coroutineScope = rememberCoroutineScope()
val offsetY  =  remember { Animatable(0f) }

Box(contentAlignment = Alignment.TopCenter, modifier = Modifier.fillMaxSize()) {
    Surface(
        color = Color(0xFF34AB52),
        modifier = Modifier
            .size(100.dp)
            .offset {
                IntOffset(0, offsetY.value.roundToInt())
            }
            .draggable(
                state = rememberDraggableState { delta ->
                    coroutineScope.launch {
                        offsetY.snapTo(offsetY.value + delta)
                    }
                },
                orientation = Orientation.Vertical,
                onDragStopped = {
                    coroutineScope.launch {
                        offsetY.animateTo(
                            targetValue = 0f,
                            animationSpec = tween(
                                durationMillis = 3000,
                                delayMillis = 0
                            )
                        )
                    }
                }
            )
    ) {
    }
}

enter image description here

Saloop answered 28/4, 2021 at 17:4 Comment(6)
Nice, this fixes the reset part, but I also want the reset to happen with a smooth animation. Is that possible?Totality
@DonnyRozendal I've updated the answer to add the animation.Saloop
You sir are a true hero, thank you! Really clean and readable. My other solution involves multiple states that created race conditions, so putting everything in one Animatable is the way to go. I had this idea as well, but didn't want to animate the dragging. But I see that you solved that with snapTo, which is genius.Totality
Also, inside the snapTo function, it should say offsetY and not offsetX. But I can't request an edit since it's not enough characters.Totality
Please explain to me why the Coroutine Scope is necessary here ?Ria
@Ir058 both snapTo and animateTo are suspend functions, the scope is needed to call themWorkshop

© 2022 - 2024 — McMap. All rights reserved.