{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

AnimatedContentKt

public final class AnimatedContentKt


Summary

Public methods

static final void
@Composable
<S extends Object> AnimatedContent(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

AnimatedContent is a container that automatically animates its content when Transition.targetState changes.

static final void
@Composable
<S extends Object> AnimatedContent(
    @NonNull S targetState,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull String label,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

AnimatedContent is a container that automatically animates its content when targetState changes.

static final @NonNull SizeTransform
SizeTransform(
    boolean clip,
    @NonNull Function2<@NonNull IntSize, @NonNull IntSize, @NonNull FiniteAnimationSpec<@NonNull IntSize>> sizeAnimationSpec
)

This creates a SizeTransform with the provided clip and sizeAnimationSpec.

static final @NonNull ContentTransform
togetherWith(
    @NonNull EnterTransition receiver,
    @NonNull ExitTransition exit
)

This creates a ContentTransform using the provided EnterTransition and exit, where the enter and exit transition will be running simultaneously.

static final @NonNull ContentTransform

This method is deprecated. Infix fun EnterTransition.with(ExitTransition) has been renamed to togetherWith

Public methods

AnimatedContent

@Composable
public static final void <S extends Object> AnimatedContent(
    @NonNull Transition<@NonNull S> receiver,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

AnimatedContent is a container that automatically animates its content when Transition.targetState changes. Its content for different target states is defined in a mapping between a target state and a composable function.

IMPORTANT: The targetState parameter for the content lambda should always be taken into account in deciding what composable function to return as the content for that state. This is critical to ensure a successful lookup of all the incoming and outgoing content during content transform.

When Transition.targetState changes, content for both new and previous targetState will be looked up through the content lambda. They will go through a ContentTransform so that the new target content can be animated in while the initial content animates out. Meanwhile the container will animate its size as needed to accommodate the new content, unless SizeTransform is set to null. Once the ContentTransform is finished, the outgoing content will be disposed.

If Transition.targetState is expected to mutate frequently and not all mutations should be treated as target state change, consider defining a mapping between Transition.targetState and a key in contentKey. As a result, transitions will be triggered when the resulting key changes. In other words, there will be no animation when switching between Transition.targetStates that share the same same key. By default, the key will be the same as the targetState object.

By default, the ContentTransform will be a delayed fadeIn of the target content and a delayed scaleIn a fadeOut of the initial content, using a SizeTransform to animate any size change of the content. This behavior can be customized using transitionSpec. If desired, different ContentTransforms can be defined for different pairs of initial content and target content.

AnimatedContent displays only the content for Transition.targetState when not animating. However, during the transient content transform, there will be more than one sets of content present in the AnimatedContent container. It may be sometimes desired to define the positional relationship among different content and the style of overlap. This can be achieved by defining contentAlignment and zOrder. By default, contentAlignment aligns all content to Alignment.TopStart, and the zIndex for all the content is 0f. Note: The target content will always be placed last, therefore it will be on top of all the other content unless zIndex is specified.

Different content in AnimatedContent will have access to their own AnimatedContentScope. This allows content to define more local enter/exit transitions via AnimatedContentScope.animateEnterExit and AnimatedContentScope.transition. These custom enter/exit animations will be triggered as the content enters/leaves the container.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.with
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

@Composable
fun CollapsedCart() { /* Some content here */
}

@Composable
fun ExpandedCart() { /* Some content here */
}

// enum class CartState { Expanded, Collapsed }
var cartState by remember { mutableStateOf(CartState.Collapsed) }
// Creates a transition here to animate the corner shape and content.
val cartOpenTransition = updateTransition(cartState, "CartOpenTransition")
val cornerSize by cartOpenTransition.animateDp(
    label = "cartCornerSize",
    transitionSpec = {
        when {
            CartState.Expanded isTransitioningTo CartState.Collapsed ->
                tween(durationMillis = 433, delayMillis = 67)
            else ->
                tween(durationMillis = 150)
        }
    }
) { if (it == CartState.Expanded) 0.dp else 24.dp }

Surface(
    Modifier.shadow(8.dp, CutCornerShape(topStart = cornerSize))
        .clip(CutCornerShape(topStart = cornerSize)),
    color = Color(0xfffff0ea),
) {
    // Creates an AnimatedContent using the transition. This AnimatedContent will
    // derive its target state from cartOpenTransition.targetState. All the animations
    // created inside of AnimatedContent for size change, enter/exit will be added to the
    // Transition.
    cartOpenTransition.AnimatedContent(
        transitionSpec = {
            fadeIn(animationSpec = tween(150, delayMillis = 150))
                .with(fadeOut(animationSpec = tween(150)))
                .using(
                    SizeTransform { initialSize, targetSize ->
                        // Using different SizeTransform for different state change
                        if (CartState.Collapsed isTransitioningTo CartState.Expanded) {
                            keyframes {
                                durationMillis = 500
                                // Animate to full target width and by 200px in height at 150ms
                                IntSize(targetSize.width, initialSize.height + 200) at 150
                            }
                        } else {
                            keyframes {
                                durationMillis = 500
                                // Animate 1/2 the height without changing the width at 150ms.
                                // The width and rest of the height will be animated in the
                                // timeframe between 150ms and duration (i.e. 500ms)
                                IntSize(
                                    initialSize.width,
                                    (initialSize.height + targetSize.height) / 2
                                ) at 150
                            }
                        }
                    }
                ).apply {
                    targetContentZIndex = when (targetState) {
                        // This defines a relationship along z-axis during the momentary
                        // overlap as both incoming and outgoing content is on screen. This
                        // fixed zOrder will ensure that collapsed content will always be on
                        // top of the expanded content - it will come in on top, and
                        // disappear over the expanded content as well.
                        CartState.Expanded -> 1f
                        CartState.Collapsed -> 2f
                    }
                }
        }
    ) {
        // This defines the mapping from state to composable. It's critical to use the state
        // parameter (i.e. `it`) that is passed into this block of code to ensure correct
        // content lookup.
        when (it) {
            CartState.Expanded -> ExpandedCart()
            CartState.Collapsed -> CollapsedCart()
        }
    }
}

AnimatedContent

@Composable
public static final void <S extends Object> AnimatedContent(
    @NonNull S targetState,
    @NonNull Modifier modifier,
    @ExtensionFunctionType @NonNull Function1<@NonNull AnimatedContentTransitionScope<@NonNull S>, @NonNull ContentTransform> transitionSpec,
    @NonNull Alignment contentAlignment,
    @NonNull String label,
    @NonNull Function1<@NonNull targetState, Object> contentKey,
    @Composable @ExtensionFunctionType @NonNull Function2<@NonNull AnimatedContentScope, @NonNull targetState, Unit> content
)

AnimatedContent is a container that automatically animates its content when targetState changes. Its content for different target states is defined in a mapping between a target state and a composable function.

IMPORTANT: The targetState parameter for the content lambda should always be taken into account in deciding what composable function to return as the content for that state. This is critical to ensure a successful lookup of all the incoming and outgoing content during content transform.

When targetState changes, content for both new and previous targetState will be looked up through the content lambda. They will go through a ContentTransform so that the new target content can be animated in while the initial content animates out. Meanwhile the container will animate its size as needed to accommodate the new content, unless SizeTransform is set to null. Once the ContentTransform is finished, the outgoing content will be disposed.

If targetState is expected to mutate frequently and not all mutations should be treated as target state change, consider defining a mapping between targetState and a key in contentKey. As a result, transitions will be triggered when the resulting key changes. In other words, there will be no animation when switching between targetStates that share the same same key. By default, the key will be the same as the targetState object.

By default, the ContentTransform will be a delayed fadeIn of the target content and a delayed scaleIn a fadeOut of the initial content, using a SizeTransform to animate any size change of the content. This behavior can be customized using transitionSpec. If desired, different ContentTransforms can be defined for different pairs of initial content and target content.

AnimatedContent displays only the content for targetState when not animating. However, during the transient content transform, there will be more than one set of content present in the AnimatedContent container. It may be sometimes desired to define the positional relationship among the different content and the overlap. This can be achieved by defining contentAlignment and zOrder. By default, contentAlignment aligns all content to Alignment.TopStart, and the zIndex for all the content is 0f. Note: The target content will always be placed last, therefore it will be on top of all the other content unless zIndex is specified.

Different content in AnimatedContent will have access to their own AnimatedContentScope. This allows content to define more local enter/exit transitions via AnimatedContentScope.animateEnterExit and AnimatedContentScope.transition. These custom enter/exit animations will be triggered as the content enters/leaves the container.

label is an optional parameter to differentiate from other animations in Android Studio.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

// enum class ContentState { Foo, Bar, Baz }
@Composable
fun Foo() {
    Box(Modifier.size(200.dp).background(Color(0xffffdb00)))
}

@Composable
fun Bar() {
    Box(Modifier.size(40.dp).background(Color(0xffff8100)))
}

@Composable
fun Baz() {
    Box(Modifier.size(80.dp, 20.dp).background(Color(0xffff4400)))
}

var contentState: ContentState by remember { mutableStateOf(ContentState.Foo) }
AnimatedContent(contentState) {
    when (it) {
        // Specifies the mapping between a given ContentState and a composable function.
        ContentState.Foo -> Foo()
        ContentState.Bar -> Bar()
        ContentState.Baz -> Baz()
    }
}

Below is an example of customizing transitionSpec to imply a spatial relationship between the content for different states:

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.with
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

Column(Modifier.padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally) {
    var count by remember { mutableStateOf(0) }
    // The `AnimatedContent` below uses an integer count as its target state. So when the
    // count changes, it will animate out the content associated with the previous count, and
    // animate in the content associated with the target state.
    AnimatedContent(
        targetState = count,
        transitionSpec = {
            // We can define how the new target content comes in and how initial content
            // leaves in the ContentTransform. Here we want to create the impression that the
            // different numbers have a spatial relationship - larger numbers are
            // positioned (vertically) below smaller numbers.
            if (targetState > initialState) {
                // If the incoming number is larger, new number slides up and fades in while
                // the previous (smaller) number slides up to make room and fades out.
                slideInVertically { it } + fadeIn() with slideOutVertically { -it } + fadeOut()
            } else {
                // If the incoming number is smaller, new number slides down and fades in while
                // the previous number slides down and fades out.
                slideInVertically { -it } + fadeIn() with slideOutVertically { it } + fadeOut()
                // Disable clipping since the faded slide-out is desired out of bounds, but
                // the size transform is still needed from number getting longer
            }.using(SizeTransform(clip = false)) // Using default spring for the size change.
        }
    ) { targetCount ->
        // This establishes a mapping between the target state and the content in the form of a
        // Composable function. IMPORTANT: The parameter of this content lambda should
        // *always* be used. During the content transform, the old content will be looked up
        // using this lambda with the old state, until it's fully animated out.

        // Since AnimatedContent differentiates the contents using their target states as the
        // key, the same composable function returned by the content lambda like below will be
        // invoked under different keys and therefore treated as different entities.
        Text("$targetCount", fontSize = 20.sp)
    }
    Spacer(Modifier.size(20.dp))
    Row(horizontalArrangement = Arrangement.SpaceAround) {
        Button(onClick = { count-- }) { Text("Minus") }
        Spacer(Modifier.size(60.dp))
        Button(onClick = { count++ }) { Text("Plus ") }
    }
}

SizeTransform

public static final @NonNull SizeTransform SizeTransform(
    boolean clip,
    @NonNull Function2<@NonNull IntSize, @NonNull IntSize, @NonNull FiniteAnimationSpec<@NonNull IntSize>> sizeAnimationSpec
)

This creates a SizeTransform with the provided clip and sizeAnimationSpec. By default, clip will be true. This means during the size animation, the content will be clipped to the animated size. sizeAnimationSpec defaults to return a spring animation.

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.with
import androidx.compose.ui.unit.IntSize

// enum class CartState { Expanded, Collapsed }
val transitionSpec: AnimatedContentScope<CartState>.() -> ContentTransform =
    {
        // Fade in with a delay so that it starts after fade out
        fadeIn(animationSpec = tween(150, delayMillis = 150))
            .with(fadeOut(animationSpec = tween(150)))
            .using(
                SizeTransform { initialSize, targetSize ->
                    // Using different SizeTransform for different state change
                    if (CartState.Collapsed isTransitioningTo CartState.Expanded) {
                        keyframes {
                            durationMillis = 500
                            // Animate to full target width and by 200px in height at 150ms
                            IntSize(targetSize.width, initialSize.height + 200) at 150
                        }
                    } else {
                        keyframes {
                            durationMillis = 500
                            // Animate 1/2 the height without changing the width at 150ms.
                            // The width and rest of the height will be animated in the
                            // timeframe between 150ms and duration (i.e. 500ms)
                            IntSize(
                                initialSize.width,
                                (initialSize.height + targetSize.height) / 2
                            ) at 150
                        }
                    }
                }
            )
    }

togetherWith

public static final @NonNull ContentTransform togetherWith(
    @NonNull EnterTransition receiver,
    @NonNull ExitTransition exit
)

This creates a ContentTransform using the provided EnterTransition and exit, where the enter and exit transition will be running simultaneously. For example:

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.with
import androidx.compose.ui.unit.IntSize

// enum class CartState { Expanded, Collapsed }
val transitionSpec: AnimatedContentScope<CartState>.() -> ContentTransform =
    {
        // Fade in with a delay so that it starts after fade out
        fadeIn(animationSpec = tween(150, delayMillis = 150))
            .with(fadeOut(animationSpec = tween(150)))
            .using(
                SizeTransform { initialSize, targetSize ->
                    // Using different SizeTransform for different state change
                    if (CartState.Collapsed isTransitioningTo CartState.Expanded) {
                        keyframes {
                            durationMillis = 500
                            // Animate to full target width and by 200px in height at 150ms
                            IntSize(targetSize.width, initialSize.height + 200) at 150
                        }
                    } else {
                        keyframes {
                            durationMillis = 500
                            // Animate 1/2 the height without changing the width at 150ms.
                            // The width and rest of the height will be animated in the
                            // timeframe between 150ms and duration (i.e. 500ms)
                            IntSize(
                                initialSize.width,
                                (initialSize.height + targetSize.height) / 2
                            ) at 150
                        }
                    }
                }
            )
    }

with

@ExperimentalAnimationApi
public static final @NonNull ContentTransform with(@NonNull EnterTransition receiver, @NonNull ExitTransition exit)