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

Previous
Previous

Test Coroutine Scheduler

Next
Next

Practical Compose Slot API example