DeferredTargetAnimation

Class

Common
@ExperimentalAnimatableApi
public class DeferredTargetAnimation<T, V : AnimationVector>(
    private val vectorConverter: TwoWayConverter<T, V>
)

DeferredTargetAnimation is intended for animations where the target is unknown at the time of instantiation. Such use cases include, but are not limited to, size or position animations created during composition or the initialization of a Modifier.Node, yet the target size or position stays unknown until the later measure and placement phase.

DeferredTargetAnimation offers a declarative updateTarget function, which requires a target to either set up the animation or update the animation, and to read the current value of the animation.

Functions

public fun updateTarget(
        target: T,
        coroutineScope: CoroutineScope,
        animationSpec: FiniteAnimationSpec<T> = spring(),
    ): T

updateTarget sets up an animation, or updates an already running animation, based on the target in the given coroutineScope. pendingTarget will be updated to track the last seen target.

updateTarget will return the current value of the animation after launching the animation in the given coroutineScope.

Returns

current value of the animation

Code Examples

DeferredTargetAnimationSample

@OptIn(ExperimentalAnimatableApi::class)
@Composable
fun DeferredTargetAnimationSample() {
    // Creates a custom modifier that animates the constraints and measures child with the
    // animated constraints. This modifier is built on top of `Modifier.approachLayout` to approach
    // th destination size determined by the lookahead pass. A resize animation will be kicked off
    // whenever the lookahead size changes, to animate children from current size to destination
    // size. Fixed constraints created based on the animation value will be used to measure
    // child, so the child layout gradually changes its animated constraints until the approach
    // completes.
    fun Modifier.animateConstraints(
        sizeAnimation: DeferredTargetAnimation<IntSize, AnimationVector2D>,
        coroutineScope: CoroutineScope,
    ) =
        this.approachLayout(
            isMeasurementApproachInProgress = { lookaheadSize ->
                // Update the target of the size animation.
                sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
                // Return true if the size animation has pending target change or is currently
                // running.
                !sizeAnimation.isIdle
            }
        ) { measurable, _ ->
            // In the measurement approach, the goal is to gradually reach the destination size
            // (i.e. lookahead size). To achieve that, we use an animation to track the current
            // size, and animate to the destination size whenever it changes. Once the animation
            // finishes, the approach is complete.
            // First, update the target of the animation, and read the current animated size.
            val (width, height) = sizeAnimation.updateTarget(lookaheadSize, coroutineScope)
            // Then create fixed size constraints using the animated size
            val animatedConstraints = Constraints.fixed(width, height)
            // Measure child with animated constraints.
            val placeable = measurable.measure(animatedConstraints)
            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
        }
    var fullWidth by remember { mutableStateOf(false) }
    // Creates a size animation with a target unknown at the time of instantiation.
    val sizeAnimation = remember { DeferredTargetAnimation(IntSize.VectorConverter) }
    val coroutineScope = rememberCoroutineScope()
    Row(
        (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp))
            .height(200.dp)
            // Use the custom modifier created above to animate the constraints passed
            // to the child, and therefore resize children in an animation.
            .animateConstraints(sizeAnimation, coroutineScope)
            .clickable { fullWidth = !fullWidth }
    ) {
        Box(Modifier.weight(1f).fillMaxHeight().background(Color(0xffff6f69)))
        Box(Modifier.weight(2f).fillMaxHeight().background(Color(0xffffcc5c)))
    }
}