Hi ! Thanks to blogs and samples, like this exampl...
# compose
l
Hi ! Thanks to blogs and samples, like this example https://github.com/nikit19/AnimationsJepackCompose/blob/master/app/src/main/java/com/example/animationjepackcompose/MainActivity.kt I know that in order to make an animation I can use a Transition Composable. But in my Component, I also want the user to be able to drag an item (I am using a modifier with dragGestureFilter) and make the item go back with an animation as soon as the drag and drop is finished.
Here the component, with the drag feature but without the animation
Copy code
@Preview
@Composable
fun DragNDropComponent() {
    Column(modifier = Modifier.size(100.dp).background(Color.Red)) {
        var x by remember{ mutableStateOf(0f)}
        var y by remember { mutableStateOf(0f)}
        val offsetX = with(DensityAmbient.current) {
            x.toDp()
        }
        val offsetY = with(DensityAmbient.current) {
            y.toDp()
        }
        Column(modifier = Modifier
            .offset(offsetX, offsetY)
            .size(10.dp)
            .background(Color.Blue)
            .dragGestureFilter(
                object : DragObserver {
                    override fun onDrag(dragDistance: Offset): Offset {
                        x += dragDistance.x
                        y += dragDistance.y
                        return dragDistance
                    }

                    override fun onCancel() {
                        // Here a need an animation instead

                        x = 0f
                        y = 0f
                    }

                    override fun onStart(downPosition: Offset) {
                        x = 0f
                        y = 0f
                    }

                    override fun onStop(velocity: Offset) {
                        // Here a need an animation instead

                        x = 0f
                        y = 0f
                    }
                }
            )
        ){}
    }
}
How should I combine both ideas ? Letting the user make a drag and drop, and also applying an animation in order to go back ? What disturb me is that adding a Transtion composable may interfere with the DragNDrop feature.
j
I guess you can use instead of a transition, an AnimatedFloat for the position
๐Ÿ‘ 1
then when you drag, you update the target value of animated float
๐Ÿ‘ 1
and when you release, you set the target value back to 0
๐Ÿ‘ 1
you can choose different animation styles (so I guess the spring based one would be good if you combine it with dragging)
๐Ÿ‘ 1
l
Thank you very much ๐Ÿ˜ƒ Indeed, AnimatedFloat seems better suited to my case. I'm trying right now.
I'm still stuck with
<https://developer.android.com/reference/kotlin/androidx/compose/animation/core/AnimationClockObservable|AnimationClockObservable>
I need to instantiate as a parameter of AnimatedFloat.
I've progressed : but the animation always start from the same location. And if I set startX/startY to x/y : all i get is a start from the origin.
Copy code
@Preview
@Composable
fun DragNDropComponent() {
    Column(modifier = Modifier.size(100.dp).background(Color.Red)) {
        var x by remember { mutableStateOf(0f) }
        var y by remember { mutableStateOf(0f) }
        var animationActive by remember { mutableStateOf(false) }

        val animatedX = FloatPropKey()
        val animatedY = FloatPropKey()

        val positionAnimation = transitionDefinition<DndResetState> {
            state(DndResetState.Start) {
                this[animatedX] = 100f // setting to x does not help either
                this[animatedY] = 200f // setting to y does not help either
            }
            state(DndResetState.End) {
                this[animatedX] = 0f
                this[animatedY] = 0f
            }

            transition {
                animatedX using tween(durationMillis = 700, easing = FastOutSlowInEasing)
                animatedY using tween(durationMillis = 700, easing = FastOutSlowInEasing)

            }
        }

        val positionAnimationState = transition(
            definition = positionAnimation,
            initState = DndResetState.Start,
            toState = DndResetState.End,
            onStateChangeFinished = {
                animationActive = false
            }
        )

        val offsetX = with(DensityAmbient.current) {
            (
                    if (animationActive) positionAnimationState[animatedX]
                    else x
                    ).toDp()
        }
        val offsetY = with(DensityAmbient.current) {
            (
                    if (animationActive) positionAnimationState[animatedY]
                    else y
                    ).toDp()
        }


        Column(modifier = Modifier
            .offset(offsetX, offsetY)
            .size(10.dp)
            .background(Color.Blue)
            .dragGestureFilter(
                object : DragObserver {
                    override fun onDrag(dragDistance: Offset): Offset {
                        x += dragDistance.x
                        y += dragDistance.y
                        return dragDistance
                    }

                    override fun onCancel() {
                        animationActive = true
                        x = 0f
                        y = 0f
                    }

                    override fun onStart(downPosition: Offset) {
                        x = 0f
                        y = 0f
                    }

                    override fun onStop(velocity: Offset) {
                        animationActive = true
                        x = 0f
                        y = 0f
                    }
                }
            )
        ) {}
    }
}
Solved ! It was because I was setting x/y to 0 as soon as drag and drop was ended, meanwhile I needed to use x/y values for initializing the animation. A kind of synchro error.
Copy code
@Preview
@Composable
fun DragNDropComponent() {
    Column(modifier = Modifier.size(300.dp).background(Color.Red)) {
        var x by remember { mutableStateOf(0f) }
        var y by remember { mutableStateOf(0f) }
        var animationActive by remember { mutableStateOf(false) }

        val animatedX = FloatPropKey()
        val animatedY = FloatPropKey()

        val positionAnimation = transitionDefinition<DndResetState> {
            state(DndResetState.Start) {
                this[animatedX] = x
                this[animatedY] = y
            }
            state(DndResetState.End) {
                this[animatedX] = 0f
                this[animatedY] = 0f
            }

            transition {
                animatedX using tween(durationMillis = 700, easing = FastOutSlowInEasing)
                animatedY using tween(durationMillis = 700, easing = FastOutSlowInEasing)

            }
        }

        val positionAnimationState = transition(
            definition = positionAnimation,
            initState = DndResetState.Start,
            toState = DndResetState.End,
            onStateChangeFinished = {
                animationActive = false
                x = 0f
                y = 0f
            }
        )

        val offsetX = with(DensityAmbient.current) {
            (
                    if (animationActive) positionAnimationState[animatedX]
                    else x
                    ).toDp()
        }
        val offsetY = with(DensityAmbient.current) {
            (
                    if (animationActive) positionAnimationState[animatedY]
                    else y
                    ).toDp()
        }


        Column(modifier = Modifier
            .offset(offsetX, offsetY)
            .size(10.dp)
            .background(Color.Blue)
            .dragGestureFilter(
                object : DragObserver {
                    override fun onDrag(dragDistance: Offset): Offset {
                        x += dragDistance.x
                        y += dragDistance.y
                        return dragDistance
                    }

                    override fun onCancel() {
                        animationActive = true
                    }

                    override fun onStart(downPosition: Offset) {
                        x = 0f
                        y = 0f
                    }

                    override fun onStop(velocity: Offset) {
                        animationActive = true
                    }
                }
            )
        ) {}
    }
}
j
To get back at animatedFloat, there is a builder for that which should serve your needs and might be better than this transitional way. if you use the function
animatedFloat
you don't have to worry about the clock or remembering
๐Ÿ‘ 1
Copy code
@Preview
@Composable
fun DragAndDropComponent() {
    Column(modifier = Modifier.size(300.dp).background(Color.Red)) {
        val x = animatedFloat(0f)
        val y = animatedFloat(0f)
        Box(modifier = Modifier
            .offset(
                with(DensityAmbient.current) { x.value.toDp() },
                with(DensityAmbient.current) { y.value.toDp() })
            .size(10.dp)
            .background(Color.Blue)
            .dragGestureFilter(
                object : DragObserver {
                    override fun onDrag(dragDistance: Offset): Offset {
                        x.animateTo(x.targetValue + dragDistance.x)
                        y.animateTo(y.targetValue + dragDistance.y)
                        return super.onDrag(dragDistance)
                    }

                    override fun onStop(velocity: Offset) {
                        super.onStop(velocity)
                        x.animateTo(0f)
                        y.animateTo(0f)
                    }

                    override fun onCancel() {
                        super.onCancel()
                        x.animateTo(0f)
                        y.animateTo(0f)
                    }
                },
                startDragImmediately = true
            )
        )
    }
}
๐Ÿ‘ 1
crazy how we can interact with the preview ๐Ÿ™‚