animateBy
Function
Common
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),
)
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 |
Code Examples
TransformableAnimateBySample
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TransformableAnimateBySample() {
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
},
)
}
}
}