---
title: "animateBy"
description: "Animate zoom, pan, and rotation simultaneously and suspend until the animation is finished.

Zoom is animated by a ratio of [zoomFactor] over the current size. Pan is animated by [panOffset]
in pixels. Rotation is animated by the value of [rotationDegrees] clockwise. Any of these
parameters can be set to a no-op value that will result in no animation of that parameter. The
no-op values are the following: `1f` for [zoomFactor], `Offset.Zero` for [panOffset], and `0f`
for [rotationDegrees]."
type: "function"
---

<div class='type'>Function</div>


<a id='references'></a>
<div class='sourceset sourceset-common'>Common</div>


> **Deprecated** Maintained for binary compatibility

```kotlin
suspend fun TransformableState.animateBy(
    zoomFactor: Float,
    panOffset: Offset,
    rotationDegrees: Float,
    zoomAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
    panAnimationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow),
    rotationAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
) =
    animateBy(
        zoomFactor = zoomFactor,
        panOffset = panOffset,
        rotationDegrees = rotationDegrees,
        zoomAnimationSpec = zoomAnimationSpec,
        panAnimationSpec = panAnimationSpec,
        rotationAnimationSpec = rotationAnimationSpec,
        centroid = Offset.Unspecified,
    )
```


Animate zoom, pan, and rotation simultaneously and suspend until the animation is finished.

Zoom is animated by a ratio of `zoomFactor` over the current size. Pan is animated by `panOffset`
in pixels. Rotation is animated by the value of `rotationDegrees` clockwise. Any of these
parameters can be set to a no-op value that will result in no animation of that parameter. The
no-op values are the following: `1f` for `zoomFactor`, `Offset.Zero` for `panOffset`, and `0f`
for `rotationDegrees`.

#### Parameters

| | |
| --- | --- |
| zoomFactor | ratio over the current size by which to zoom. For example, if `zoomFactor` is `3f`, zoom will be increased 3 fold from the current value. |
| panOffset | offset to pan, in pixels |
| rotationDegrees | the degrees by which to rotate clockwise |
| zoomAnimationSpec | `AnimationSpec` to be used for animating zoom |
| panAnimationSpec | `AnimationSpec` to be used for animating offset |
| rotationAnimationSpec | `AnimationSpec` to be used for animating rotation |




<div class='sourceset sourceset-common'>Common</div>


```kotlin
suspend fun TransformableState.animateBy(
    zoomFactor: Float,
    panOffset: Offset,
    rotationDegrees: Float,
    zoomAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
    panAnimationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow),
    rotationAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
    centroid: Offset = Offset.Unspecified,
)
```


Animate zoom, pan, and rotation simultaneously and suspend until the animation is finished.

Zoom is animated by a ratio of `zoomFactor` over the current size. Pan is animated by `panOffset`
in pixels. Rotation is animated by the value of `rotationDegrees` clockwise. Any of these
parameters can be set to a no-op value that will result in no animation of that parameter. The
no-op values are the following: `1f` for `zoomFactor`, `Offset.Zero` for `panOffset`, and `0f`
for `rotationDegrees`.

#### Parameters

| | |
| --- | --- |
| zoomFactor | ratio over the current size by which to zoom. For example, if `zoomFactor` is `3f`, zoom will be increased 3 fold from the current value. |
| panOffset | offset to pan, in pixels |
| rotationDegrees | the degrees by which to rotate clockwise |
| zoomAnimationSpec | `AnimationSpec` to be used for animating zoom |
| panAnimationSpec | `AnimationSpec` to be used for animating offset |
| rotationAnimationSpec | `AnimationSpec` to be used for animating rotation |
| centroid | the `Offset` around which the animation should occur, if any. The default value is `Offset.Unspecified`, which leaves the behavior up to the implementation of the `TransformableState`. |




## Code Examples
### TransformableAnimateBySample
```kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TransformableAnimateBySample() {
    /**
     * Rotates the given offset around the origin by the given angle in degrees.
     *
     * A positive angle indicates a counterclockwise rotation around the right-handed 2D Cartesian
     * coordinate system.
     *
     * See: [Rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix)
     */
    fun Offset.rotateBy(angle: Float): Offset {
        val angleInRadians = angle * (PI / 180)
        val cos = cos(angleInRadians)
        val sin = sin(angleInRadians)
        return Offset((x * cos - y * sin).toFloat(), (x * sin + y * cos).toFloat())
    }
    Box(Modifier.size(200.dp).clipToBounds().background(Color.LightGray)) {
        // set up all transformation states
        var scale by remember { mutableStateOf(1f) }
        var rotation by remember { mutableStateOf(0f) }
        var offset by remember { mutableStateOf(Offset.Zero) }
        val coroutineScope = rememberCoroutineScope()
        var size by remember { mutableStateOf(Size.Zero) }
        // let's create a modifier state to specify how to update our UI state defined above
        val state =
            rememberTransformableState { centroid, zoomChange, offsetChange, rotationChange ->
                val oldScale = scale
                val newScale = max(scale * zoomChange, 1f)
                // If the centroid isn't specified, assume it should be applied from the center
                val effectiveCentroid = centroid.takeIf { it.isSpecified } ?: size.center
                // For natural zooming and rotating, the centroid of the gesture should
                // be the fixed point where zooming and rotating occurs.
                // We compute where the centroid was (in the pre-transformed coordinate
                // space), and then compute where it will be after this delta.
                // We then compute what the new offset should be to keep the centroid
                // visually stationary for rotating and zooming, and also apply the pan.
                offset =
                    (offset + effectiveCentroid / oldScale).rotateBy(rotationChange) -
                        (effectiveCentroid / newScale + offsetChange / oldScale)
                scale = newScale
                rotation += rotationChange
            }
        Box(
            Modifier.onSizeChanged { size = it.toSize() }
                // add transformable to listen to multitouch transformation events after offset
                .transformable(state = state)
                // detect tap gestures:
                // 1) single tap to simultaneously animate zoom, pan, and rotation
                // 2) double tap to animate back to the initial position
                .pointerInput(Unit) {
                    detectTapGestures(
                        onTap = { offset ->
                            coroutineScope.launch {
                                state.animateBy(
                                    zoomFactor = 1.5f,
                                    panOffset = Offset(20f, 20f),
                                    rotationDegrees = 90f,
                                    zoomAnimationSpec = spring(),
                                    panAnimationSpec = tween(durationMillis = 1000),
                                    rotationAnimationSpec = spring(),
                                    centroid = offset,
                                )
                            }
                        },
                        onDoubleTap = { offset ->
                            coroutineScope.launch {
                                state.animateBy(
                                    zoomFactor = 1 / scale,
                                    panOffset = -offset,
                                    rotationDegrees = -rotation,
                                    centroid = offset,
                                )
                            }
                        },
                    )
                }
                .fillMaxSize()
                .border(1.dp, Color.Green)
        ) {
            Text(
                "\uD83C\uDF55",
                fontSize = 32.sp,
                modifier =
                    Modifier.fillMaxSize()
                        .graphicsLayer {
                            translationX = -offset.x * scale
                            translationY = -offset.y * scale
                            scaleX = scale
                            scaleY = scale
                            rotationZ = rotation
                            transformOrigin = TransformOrigin(0f, 0f)
                        }
                        .wrapContentSize(align = Alignment.Center),
            )
        }
    }
}
```

