Animating visibility vs alpha in Compose
June 12, 2022
Let’s say you are building a layout that contains a Column
with an Image
and a Button
below it and the button triggers fade in and fade out of the image. Jetpack Compose makes it very easy to do with help of AnimatedVisibility
composable.
AnimatedVisibility
setContent { MyApplicationTheme { Surface( modifier = Modifier .fillMaxSize(), color = MaterialTheme.colors.background ) { var imageVisible by remember { mutableStateOf(false) } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { AnimatedVisibility( visible = imageVisible, enter = fadeIn(animationSpec = tween(2000)), exit = fadeOut(animationSpec = tween(2000)) ) { Image( painter = painterResource(id = R.drawable.boats), contentDescription = "Boats", modifier = Modifier .padding(horizontal = 16.dp) ) } Button( onClick = { imageVisible = !imageVisible }) { Text("Click me") } } } } }
We get this UI as a result:
Notice that the Column
composable sets verticalArrangement = Arrangement.Center
for its children. Because of that, in order to stay in the center vertically, the button changes its position every time the image appears and disappears.
Let’s examine the behavior using Layout Inspector.
When AnimatedVisibility
is not a part of the composition (no content to display, i.e. no Image
to display), the Column
contains Button
only and it’s centered within its container. See below:
When AnimatedVisibility
is a part of the composition (i.e. Image
is displayed), the Column contains AnimatedVisibility
>Image
and Button
. See below:
Here is the relevant portion of the AnimatedVisibility
documentation:
[AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom layout is determined by the largest width and largest height of the children. Once the exit transition is finished, the [content] composable will be removed from the tree, and disposed.
In the View system, this is similar to using Visibility.GONE
on the Image—when set, the view gets removed from the View hierarchy.
Let’s look at an alternative way to accomplish this but ensure the button always stays in the same position.
animateFloatAsState
One of the ways to accomplish this is to always add the Image
to the composition but show and hide it using animated alpha
using animateFloatAsState
:
Here is the code that animates image alpha
rather than animating image visibility.
setContent { MyApplicationTheme { Surface( modifier = Modifier .fillMaxSize(), color = MaterialTheme.colors.background ) { var imageVisible by remember { mutableStateOf(false) } val imageAlpha: Float by animateFloatAsState( targetValue = if (imageVisible) 1f else 0f, animationSpec = tween( durationMillis = 2000, easing = LinearEasing, ) ) Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image( painter = painterResource(id = R.drawable.boats), contentDescription = "Boats", alpha = imageAlpha, modifier = Modifier .padding(horizontal = 16.dp) ) Button( onClick = { imageVisible = !imageVisible }) { Text("Click me") } } } } } }
Let’s look at the Layout Inspector again.
Here is the layout when the Image alpha
is 0.0
And here is the layout when the alpha
is 1.0
In both cases, animating alpha
value causes the Image
composable function to recompose itself. Because the Image
is always a part of the composition during the state changes, the Button
never changes its position.
Learn about other animate*AsState
and get more examples of Animation APIs in Compose at https://developer.android.com/jetpack/compose/animation