TransformableState


State of transformable. Allows for a granular control of how different gesture transformations are consumed by the user as well as to write custom transformation methods using transform suspend function.

Summary

Public functions

suspend Unit
transform(transformPriority: MutatePriority, block: suspend TransformScope.() -> Unit)

Call this function to take control of transformations and gain the ability to send transform events via TransformScope.transformBy.

Cmn

Public properties

Boolean

Whether this TransformableState is currently transforming by gesture or programmatically or not.

Cmn

Extension functions

suspend Unit
TransformableState.animateBy(
    zoomFactor: Float,
    panOffset: Offset,
    rotationDegrees: Float,
    zoomAnimationSpec: AnimationSpec<Float>,
    panAnimationSpec: AnimationSpec<Offset>,
    rotationAnimationSpec: AnimationSpec<Float>
)

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

Cmn
suspend Unit
TransformableState.animatePanBy(
    offset: Offset,
    animationSpec: AnimationSpec<Offset>
)

Animate pan by offset Offset in pixels and suspend until its finished

Cmn
suspend Unit
TransformableState.animateRotateBy(
    degrees: Float,
    animationSpec: AnimationSpec<Float>
)

Animate rotate by a ratio of degrees clockwise and suspend until its finished.

Cmn
suspend Unit
TransformableState.animateZoomBy(
    zoomFactor: Float,
    animationSpec: AnimationSpec<Float>
)

Animate zoom by a ratio of zoomFactor over the current size and suspend until its finished.

Cmn
suspend Unit

Pan without animation by a offset Offset in pixels and suspend until it's set.

Cmn
suspend Unit

Rotate without animation by a degrees degrees and suspend until it's set.

Cmn
suspend Unit

Stop and suspend until any ongoing TransformableState.transform with priority terminationPriority or lower is terminated.

Cmn
suspend Unit

Zoom without animation by a ratio of zoomFactor over the current size and suspend until it's set.

Cmn

Public functions

transform

suspend fun transform(
    transformPriority: MutatePriority = MutatePriority.Default,
    block: suspend TransformScope.() -> Unit
): Unit

Call this function to take control of transformations and gain the ability to send transform events via TransformScope.transformBy. All actions that change zoom, pan or rotation values must be performed within a transform block (even if they don't call any other methods on this object) in order to guarantee that mutual exclusion is enforced.

If transform is called from elsewhere with the transformPriority higher or equal to ongoing transform, ongoing transform will be canceled.

Public properties

isTransformInProgress

val isTransformInProgressBoolean

Whether this TransformableState is currently transforming by gesture or programmatically or not.

Extension functions

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)
): Unit

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.

import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.animateBy
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

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()
    // let's create a modifier state to specify how to update our UI state defined above
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        // note: scale goes by factor, not an absolute difference, so we need to multiply it
        // for this example, we don't allow downscaling, so cap it to 1f
        scale = max(scale * zoomChange, 1f)
        rotation += rotationChange
        offset += offsetChange
    }
    Box(
        Modifier
            // apply pan offset state as a layout transformation before other modifiers
            .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
            // 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 = {
                        coroutineScope.launch {
                            state.animateBy(
                                zoomFactor = 1.5f,
                                panOffset = Offset(20f, 20f),
                                rotationDegrees = 90f,
                                zoomAnimationSpec = spring(),
                                panAnimationSpec = tween(durationMillis = 1000),
                                rotationAnimationSpec = spring()
                            )
                        }
                    },
                    onDoubleTap = {
                        coroutineScope.launch { state.animateBy(1 / scale, -offset, -rotation) }
                    }
                )
            }
            .fillMaxSize()
            .border(1.dp, Color.Green),
        contentAlignment = Alignment.Center
    ) {
        Text(
            "\uD83C\uDF55",
            fontSize = 32.sp,
            // apply other transformations like rotation and zoom on the pizza slice emoji
            modifier =
                Modifier.graphicsLayer {
                    scaleX = scale
                    scaleY = scale
                    rotationZ = rotation
                }
        )
    }
}
Parameters
zoomFactor: Float

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

offset to pan, in pixels

rotationDegrees: Float

the degrees by which to rotate clockwise

zoomAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for animating zoom

panAnimationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for animating offset

rotationAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for animating rotation

suspend fun TransformableState.animatePanBy(
    offset: Offset,
    animationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow)
): Unit

Animate pan by offset Offset in pixels and suspend until its finished

Parameters
offset: Offset

offset to pan, in pixels

animationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for pan animation

animateRotateBy

suspend fun TransformableState.animateRotateBy(
    degrees: Float,
    animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
): Unit

Animate rotate by a ratio of degrees clockwise and suspend until its finished.

Parameters
degrees: Float

the degrees by which to rotate clockwise

animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for animation

suspend fun TransformableState.animateZoomBy(
    zoomFactor: Float,
    animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
): Unit

Animate zoom by a ratio of zoomFactor over the current size and suspend until its finished.

Parameters
zoomFactor: Float

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.

animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)

AnimationSpec to be used for animation

suspend fun TransformableState.panBy(offset: Offset): Unit

Pan without animation by a offset Offset in pixels and suspend until it's set.

Parameters
offset: Offset

offset in pixels by which to pan

suspend fun TransformableState.rotateBy(degrees: Float): Unit

Rotate without animation by a degrees degrees and suspend until it's set.

Parameters
degrees: Float

degrees by which to rotate

stopTransformation

suspend fun TransformableState.stopTransformation(
    terminationPriority: MutatePriority = MutatePriority.Default
): Unit

Stop and suspend until any ongoing TransformableState.transform with priority terminationPriority or lower is terminated.

Parameters
terminationPriority: MutatePriority = MutatePriority.Default

transformation that runs with this priority or lower will be stopped

suspend fun TransformableState.zoomBy(zoomFactor: Float): Unit

Zoom without animation by a ratio of zoomFactor over the current size and suspend until it's set.

Parameters
zoomFactor: Float

ratio over the current size by which to zoom