Arpit Shukla
Arpit Shukla

Reputation: 10493

How to rotate a composable and add a progress listener to the rotation?

I am trying to convert my View based code to Compose. I have a composable which takes an image (Painter) as argument and displays it using Image composable. What I want is that whenever the argument value changes, my Image should do a 360 degree rotation and the image should change while angle is approx. 180 degree (i.e. mid-way in the animation)

This is the composable I made.

@Composable
fun MyImage(displayImage: Painter) {
    Image(
        painter = displayImage,
        contentDescription = null,
        modifier = Modifier
            .size(36.dp)
            .clip(CircleShape)
    )
}

Right now when the displayImage changes, the new image is displayed immediately without any animation (obviously). How can I achieve the desired animation?

The code that I am trying to convert looks like this:

fun onImageChange(imageRes: Int) {
    ObjectAnimator.ofFloat(imageView, View.ROTATION, 0f, 360f)
        .apply {
            addUpdateListener {
                if (animatedFraction == 0.5f) {
                    imageView.setImageResource(imageRes)
                }
            }
            start()
        }
}

Upvotes: 3

Views: 6406

Answers (1)

Phil Dukhov
Phil Dukhov

Reputation: 88024

It can be done using Animatable.

Compose animations are based on coroutines, so you can wait for the animateTo suspend function to complete, change the image and run another animation. Here's a basic example:

var flag by remember { mutableStateOf(true) }
val resourceId = remember(flag) { if (flag) R.drawable.profile else R.drawable.profile_inverted }
val rotation = remember { Animatable(0f) }
val scope = rememberCoroutineScope()

Column(Modifier.padding(30.dp)) {
    Button(onClick = {
        scope.launch {
            rotation.animateTo(
                targetValue = 180f,
                animationSpec = tween(1000, easing = LinearEasing)
            )
            flag = !flag
            rotation.animateTo(
                targetValue = 360f,
                animationSpec = tween(1000, easing = LinearEasing)
            )
            rotation.snapTo(0f)
        }
    }) {
        Text("Rotate")
    }
    Image(
        painterResource(id = resourceId),
        contentDescription = null,
        modifier = Modifier
            .size(300.dp)
            .rotate(rotation.value)
    )
}

Output:

If you want to animate the changing images, you have to put two images in a Box and animate the opacity of both as they rotate using one more Animatable.

Upvotes: 10

Related Questions