DeferredTargetAnimation



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.

import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.VectorConverter
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

// 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))
    )
}

Summary

Public constructors

<T : Any?, V : AnimationVector> DeferredTargetAnimation(
    vectorConverter: TwoWayConverter<T, V>
)
Cmn

Public functions

T
updateTarget(
    target: T,
    coroutineScope: CoroutineScope,
    animationSpec: FiniteAnimationSpec<T>
)

updateTarget sets up an animation, or updates an already running animation, based on the target in the given coroutineScope.

Cmn

Public properties

Boolean

isIdle returns true when the animation has finished running and reached its pendingTarget, or when the animation has not been set up (i.e. updateTarget has never been called).

Cmn
T?

Returns the target value from the most recent updateTarget call.

Cmn

Public constructors

DeferredTargetAnimation

<T : Any?, V : AnimationVector> DeferredTargetAnimation(
    vectorConverter: TwoWayConverter<T, V>
)

Public functions

updateTarget

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
T

current value of the animation

Public properties

isIdle

val isIdleBoolean

isIdle returns true when the animation has finished running and reached its pendingTarget, or when the animation has not been set up (i.e. updateTarget has never been called).

pendingTarget

val pendingTarget: T?

Returns the target value from the most recent updateTarget call.