Compose comes with built-in composables and modifiers for handling common animation use cases.
Built-in animated composables
Animate appearance and disappearance with
AnimatedVisibility
The
AnimatedVisibility
composable animates the appearance and disappearance of its content.
var visible by remember {
mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
// your composable here
// ...
}
By default, the content appears by fading in and expanding, and it disappears by
fading out and shrinking. The transition can be customized by specifying
EnterTransition
and
ExitTransition
.
var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
visible = visible,
enter = slideInVertically {
// Slide in from 40 dp from the top.
with(density) { -40.dp.roundToPx() }
} + expandVertically(
// Expand from the top.
expandFrom = Alignment.Top
) + fadeIn(
// Fade in with the initial alpha of 0.3f.
initialAlpha = 0.3f
),
exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
Text("Hello", Modifier.fillMaxWidth().height(200.dp))
}
As you can see in the example above, you can combine multiple
EnterTransition
or
ExitTransition
objects with a
+
operator, and each accepts optional
parameters to customize its behavior. See the references for more information.
EnterTransition
and
ExitTransition
examples
AnimatedVisibility
also offers a variant that takes a
MutableTransitionState
. This allows you to trigger an animation as soon as the
AnimatedVisibility
is added to the composition tree. It is also useful for
observing the animation state.
// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = true
}
}
Column {
AnimatedVisibility(visibleState = state) {
Text(text = "Hello, world!")
}
// Use the MutableTransitionState to know the current animation state
// of the AnimatedVisibility.
Text(
text = when {
state.isIdle && state.currentState -> "Visible"
!state.isIdle && state.currentState -> "Disappearing"
state.isIdle && !state.currentState -> "Invisible"
else -> "Appearing"
}
)
}
Animate enter and exit for children
Content within
AnimatedVisibility
(direct or indirect children) can use the
animateEnterExit
modifier to specify different animation behavior for each of them. The visual
effect for each of these children is a combination of the animations specified
at the
AnimatedVisibility
composable and the child's own enter and
exit animations.
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
// Fade in/out the background and the foreground.
Box(Modifier.fillMaxSize().background(Color.DarkGray)) {
Box(
Modifier
.align(Alignment.Center)
.animateEnterExit(
// Slide in/out the inner box.
enter = slideInVertically(),
exit = slideOutVertically()
)
.sizeIn(minWidth = 256.dp, minHeight = 64.dp)
.background(Color.Red)
) {
// Content of the notification…
}
}
}
In some cases, you may want to have
AnimatedVisibility
apply no animations at
all so that children can each have their own distinct animations by
animateEnterExit
. To achieve this, specify
EnterTransition.None
and
ExitTransition.None
at the
AnimatedVisibility
composable.
Add custom animation
If you want to add custom animation effects beyond the built-in enter and exit
animations, access the underlying
Transition
instance via the
transition
property inside the content lambda for
AnimatedVisibility
. Any animation
states added to the Transition instance will run simultaneously with the enter
and exit animations of
AnimatedVisibility
.
AnimatedVisibility
waits until
all animations in the
Transition
have finished before removing its content.
For exit animations created independent of
Transition
(such as using
animate*AsState
),
AnimatedVisibility
would not be able to account for them,
and therefore may remove the content composable before they finish.
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) { // this: AnimatedVisibilityScope
// Use AnimatedVisibilityScope#transition to add a custom animation
// to the AnimatedVisibility.
val background by transition.animateColor(label = "color") { state ->
if (state == EnterExitState.Visible) Color.Blue else Color.Gray
}
Box(modifier = Modifier.size(128.dp).background(background))
}
See
updateTransition
for the details about
Transition
.
Animate based on target state with
AnimatedContent
The
AnimatedContent
composable animates its content as it changes based on a
target state.
Row {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Add")
}
AnimatedContent(targetState = count) { targetCount ->
// Make sure to use `targetCount`, not `count`.
Text(text = "Count: $targetCount")
}
}
Note that you should always use the lambda parameter and reflect it to the
content. The API uses this value as the key to identify the content that's
currently shown.
By default, the initial content fades out and then the target content fades in
(this behavior is called
fade through
). You
can customize this animation behavior by specifying a
ContentTransform
object to the
transitionSpec
parameter. You can create
ContentTransform
by combining an
EnterTransition
with an
ExitTransition
using the
with
infix function. You can apply
SizeTransform
to the
ContentTransform
by attaching it with the
using
infix function.
AnimatedContent(
targetState = count,
transitionSpec = {
// Compare the incoming number with the previous number.
if (targetState > initialState) {
// If the target number is larger, it slides up and fades in
// while the initial (smaller) number slides up and fades out.
slideInVertically { height -> height } + fadeIn() with
slideOutVertically { height -> -height } + fadeOut()
} else {
// If the target number is smaller, it slides down and fades in
// while the initial number slides down and fades out.
slideInVertically { height -> -height } + fadeIn() with
slideOutVertically { height -> height } + fadeOut()
}.using(
// Disable clipping since the faded slide-in/out should
// be displayed out of bounds.
SizeTransform(clip = false)
)
}
) { targetCount ->
Text(text = "$targetCount")
}
EnterTransition
defines how the target content should appear, and
ExitTransition
defines how the initial content should disappear. In addition
to all of the
EnterTransition
and
ExitTransition
functions available for
AnimatedVisibility
,
AnimatedContent
offers
slideIntoContainer
and
slideOutOfContainer
.
These are convenient alternatives to
slideInHorizontally/Vertically
and
slideOutHorizontally/Vertically
that calculate the slide distance based on
the sizes of the initial content and the target content of the
AnimatedContent
content.
SizeTransform
defines how the
size should animate between the initial and the target contents. You have
access to both the initial size and the target size when you are creating the
animation.
SizeTransform
also controls whether the content should be clipped
to the component size during animations.
var expanded by remember { mutableStateOf(false) }
Surface(
color = MaterialTheme.colorScheme.primary,
onClick = { expanded = !expanded }
) {
AnimatedContent(
targetState = expanded,
transitionSpec = {
fadeIn(animationSpec = tween(150, 150)) with
fadeOut(animationSpec = tween(150)) using
SizeTransform { initialSize, targetSize ->
if (targetState) {
keyframes {
// Expand horizontally first.
IntSize(targetSize.width, initialSize.height) at 150
durationMillis = 300
}
} else {
keyframes {
// Shrink vertically first.
IntSize(initialSize.width, targetSize.height) at 150
durationMillis = 300
}
}
}
}
) { targetExpanded ->
if (targetExpanded) {
Expanded()
} else {
ContentIcon()
}
}
}
Animate child enter and exit transitions
Just like
AnimatedVisibility
, the
animateEnterExit
modifier is available inside the content lambda of
AnimatedContent
. Use this
to apply
EnterAnimation
and
ExitAnimation
to each of the direct or indirect
children separately.
Add custom animation
Just like
AnimatedVisibility
, the
transition
field is available inside the
content lambda of
AnimatedContent
. Use this to create a custom animation
effect that runs simultaneously with the
AnimatedContent
transition. See
updateTransition
for the details.
Animate between two layouts with
Crossfade
Crossfade
animates between two layouts with a crossfade animation. By toggling
the value passed to the
current
parameter, the content is switched with a
crossfade animation.
var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage) { screen ->
when (screen) {
"A" -> Text("Page A")
"B" -> Text("Page B")
}
}
Built-in animation modifiers
Animate composable size changes with
animateContentSize
The
animateContentSize
modifier animates a size change.
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.background(colorBlue)
.animateContentSize()
.height(if (expanded) 400.dp else 200.dp)
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
expanded = !expanded
}
) {
}
List item animations
If you are looking to animate item reorderings inside a Lazy list or grid, take a look at the
Lazy layout item animation documentation
.
Recommended for you