[AnimatedVisibility] composable animates the appearance and disappearance of its content, as [visible] value changes.
AVColumnScopeWithMutableTransitionState
@Composable
fun AVColumnScopeWithMutableTransitionState() {
var visible by remember { mutableStateOf(true) }
val colors = remember { listOf(Color(0xff2a9d8f), Color(0xffe9c46a), Color(0xfff4a261)) }
Column {
repeat(3) {
AnimatedVisibility(
visibleState =
remember {
// This sets up the initial state of the AnimatedVisibility to false to
// guarantee an initial enter transition. In contrast, initializing this
// as
// `MutableTransitionState(visible)` would result in no initial enter
// transition.
MutableTransitionState(initialState = false)
}
.apply {
// This changes the target state of the visible state. If it's different
// than
// the initial state, an enter/exit transition will be triggered.
targetState = visible
}
) { // Content that needs to appear/disappear goes here:
Box(Modifier.fillMaxWidth().height(100.dp).background(colors[it]))
}
}
}
}
AddAnimatedVisibilityToGenericTransitionSample
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AddAnimatedVisibilityToGenericTransitionSample() {
@Composable
fun ItemMainContent() {
Row(Modifier.height(100.dp).fillMaxWidth(), Arrangement.SpaceEvenly) {
Box(
Modifier.size(60.dp)
.align(Alignment.CenterVertically)
.background(Color(0xffcdb7f6), CircleShape)
)
Column(Modifier.align(Alignment.CenterVertically)) {
Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun SelectableItem() {
// This sample animates a number of properties, including AnimatedVisibility, as a part of
// the Transition going between selected and unselected.
Box(Modifier.padding(15.dp)) {
var selected by remember { mutableStateOf(false) }
// Creates a transition to animate visual changes when `selected` is changed.
val selectionTransition = updateTransition(selected)
// Animates the border color as a part of the transition
val borderColor by
selectionTransition.animateColor { isSelected ->
if (isSelected) Color(0xff03a9f4) else Color.White
}
// Animates the background color when selected state changes
val contentBackground by
selectionTransition.animateColor { isSelected ->
if (isSelected) Color(0xffdbf0fe) else Color.White
}
// Animates elevation as a part of the transition
val elevation by
selectionTransition.animateDp { isSelected -> if (isSelected) 10.dp else 2.dp }
Surface(
shape = RoundedCornerShape(10.dp),
border = BorderStroke(2.dp, borderColor),
modifier = Modifier.clickable { selected = !selected },
color = contentBackground,
elevation = elevation,
) {
Column(Modifier.fillMaxWidth()) {
ItemMainContent()
// Creates an AnimatedVisibility as a part of the transition, so that when
// selected it's visible. This will hoist all the animations that are internal
// to AnimatedVisibility (i.e. fade, slide, etc) to the transition. As a result,
// `selectionTransition` will not finish until all the animations in
// AnimatedVisibility as well as animations added directly to it have finished.
selectionTransition.AnimatedVisibility(
visible = { it },
enter = expandVertically(),
exit = shrinkVertically(),
) {
Box(Modifier.fillMaxWidth().padding(10.dp)) {
Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed" +
" eiusmod tempor incididunt labore et dolore magna aliqua. " +
"Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
"irure dolor."
)
}
}
}
}
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun ColumnScope.AnimatedFloatingActionButton() {
var expanded by remember { mutableStateOf(true) }
FloatingActionButton(
onClick = { expanded = !expanded },
modifier = Modifier.align(Alignment.CenterHorizontally),
) {
Row(Modifier.padding(start = 12.dp, end = 12.dp)) {
Icon(
Icons.Default.Favorite,
contentDescription = "Favorite",
modifier = Modifier.align(Alignment.CenterVertically),
)
AnimatedVisibility(expanded, modifier = Modifier.align(Alignment.CenterVertically)) {
Text(modifier = Modifier.padding(start = 12.dp), text = "Favorite")
}
}
}
Spacer(Modifier.requiredHeight(20.dp))
}
AnimatedVisibilityLazyColumnSample
@Composable
fun AnimatedVisibilityLazyColumnSample() {
val turquoiseColors =
listOf(
Color(0xff07688C),
Color(0xff1986AF),
Color(0xff50B6CD),
Color(0xffBCF8FF),
Color(0xff8AEAE9),
Color(0xff46CECA),
)
// MyModel class handles the data change of the items that are displayed in LazyColumn.
class MyModel {
private val _items: MutableList<ColoredItem> = mutableStateListOf()
private var lastItemId = 0
val items: List<ColoredItem> = _items
// Each item has a MutableTransitionState field to track as well as to mutate the item's
// visibility. When the MutableTransitionState's targetState changes, corresponding
// transition will be fired. MutableTransitionState allows animation lifecycle to be
// observed through it's [currentState] and [isIdle]. See below for details.
inner class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
val color: Color
get() = turquoiseColors.let { it[itemId % it.size] }
}
fun addNewItem() {
lastItemId++
_items.add(
ColoredItem(
// Here the initial state of the MutableTransitionState is set to false, and
// target state is set to true. This will result in an enter transition for
// the newly added item.
MutableTransitionState(false).apply { targetState = true },
lastItemId,
)
)
}
fun removeItem(item: ColoredItem) {
// By setting the targetState to false, this will effectively trigger an exit
// animation in AnimatedVisibility.
item.visible.targetState = false
}
fun pruneItems() {
// Inspect the animation status through MutableTransitionState. If isIdle == true,
// all animations have finished for the transition.
_items.removeAll(
items.filter {
// This checks that the animations have finished && the animations are exit
// transitions. In other words, the item has finished animating out.
it.visible.isIdle && !it.visible.targetState
}
)
}
fun removeAll() {
_items.forEach { it.visible.targetState = false }
}
}
@Composable
fun AnimatedVisibilityInLazyColumn() {
Column {
val model = remember { MyModel() }
Row(Modifier.fillMaxWidth()) {
Button({ model.addNewItem() }, modifier = Modifier.padding(15.dp).weight(1f)) {
Text("Add")
}
}
// This sets up a flow to check whether any item has finished animating out. If yes,
// notify the model to prune the list.
LaunchedEffect(model) {
snapshotFlow {
model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
}
.collect {
if (it != null) {
model.pruneItems()
}
}
}
LazyColumn {
items(model.items, key = { it.itemId }) { item ->
AnimatedVisibility(
item.visible,
enter = expandVertically(),
exit = shrinkVertically(),
) {
Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
Button(
{ model.removeItem(item) },
modifier = Modifier.align(Alignment.CenterEnd).padding(15.dp),
) {
Text("Remove")
}
}
}
}
}
Button({ model.removeAll() }, modifier = Modifier.align(Alignment.End).padding(15.dp)) {
Text("Clear All")
}
}
}
}
AnimatedVisibilityWithBooleanVisibleParamNoReceiver
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedVisibilityWithBooleanVisibleParamNoReceiver() {
var visible by remember { mutableStateOf(true) }
Box(modifier = Modifier.clickable { visible = !visible }) {
AnimatedVisibility(
visible = visible,
modifier = Modifier.align(Alignment.Center),
enter = fadeIn(),
exit = fadeOut(animationSpec = tween(200)) + scaleOut(),
) { // Content that needs to appear/disappear goes here:
// Here we can optionally define a custom enter/exit animation by creating an animation
// using the Transition<EnterExitState> object from AnimatedVisibilityScope:
// As a part of the enter transition, the corner radius will be animated from 0.dp to
// 50.dp.
val cornerRadius by
transition.animateDp {
when (it) {
EnterExitState.PreEnter -> 0.dp
EnterExitState.Visible -> 50.dp
// No corner radius change when exiting.
EnterExitState.PostExit -> 50.dp
}
}
Box(
Modifier.background(Color.Red, shape = RoundedCornerShape(cornerRadius))
.height(100.dp)
.fillMaxWidth()
)
}
}
}
ColumnAnimatedVisibilitySample
@Composable
fun ColumnAnimatedVisibilitySample() {
var itemIndex by remember { mutableStateOf(0) }
val colors = listOf(Color.Red, Color.Green, Color.Blue)
Column(Modifier.fillMaxWidth().clickable { itemIndex = (itemIndex + 1) % colors.size }) {
colors.forEachIndexed { i, color ->
// By default ColumnScope.AnimatedVisibility expands and shrinks new content while
// fading.
AnimatedVisibility(i <= itemIndex) {
Box(Modifier.requiredHeight(40.dp).fillMaxWidth().background(color))
}
}
}
}
FullyLoadedTransition
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun FullyLoadedTransition() {
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = visible,
enter =
slideInVertically(
// Start the slide from 40 (pixels) above where the content is supposed to go, to
// produce a parallax effect
initialOffsetY = { -40 }
) +
expandVertically(expandFrom = Alignment.Top) +
scaleIn(
// Animate scale from 0f to 1f using the top center as the pivot point.
transformOrigin = TransformOrigin(0.5f, 0f)
) +
fadeIn(initialAlpha = 0.3f),
exit = slideOutVertically() + shrinkVertically() + fadeOut() + scaleOut(targetScale = 1.2f),
) {
// Content that needs to appear/disappear goes here:
Text("Content to appear/disappear", Modifier.fillMaxWidth().requiredHeight(200.dp))
}
}