3

I find myself in this situation quite often. I have some value like plates in the sample below, and I want to show/hide it depending on if its null or not. But hiding it always fails since nothing is rendered whenever its null, and the animation just snaps to empty nothingness.

How can I make this work? Id like to keep plates around until the animation finishes.

    AnimatedVisibility(
        visible = plates != null,
        content = {
            if (plates != null) {
                // Render plates
            } else {
                // The animation snaps to nothingness, as opposed to animating out
            }
        })
zoltish
  • 2,122
  • 19
  • 37
  • If you want the content to show during the "exit" portion of the animation, the content cannot be null. The content must be the exact same content when the "enter" phase was executed, otherwise, the animation will show a very quick change. Why is your plates variable null all of a sudden if it wasn't null when the "enter" phase was executed? – Johann Dec 01 '21 at 15:47
  • Exactly my thinking. So I should remember the content, and update it everytime a non-null value comes in - then use its value in the animatedVisibility block, while specifying its visibility by the last value being null or not? As for the value becoming null again, it just indicates that it shouldnt be shown any longer. – zoltish Dec 01 '21 at 15:57
  • 1
    That is the correct solution. Just cache your last item and reuse it when hiding. Also, if your screen uses vertical scrolling, you should place the scrollstate in your viewmodel and pass that to your composable because animation destroys the scrollstate. Using a local state like rememberScrollState will not work. It gets destroyed. The exit will look smoother without jumping back to the top of the screen that is exiting. – Johann Dec 01 '21 at 16:05

3 Answers3

6

This is what I ended up doing, and it works as expected!

@Composable
inline fun <T> AnimatedValueVisibility(
    value: T?,
    enter: EnterTransition = fadeIn(
        animationSpec = FloatSpec
    ) + expandVertically(
        animationSpec = SizeSpec,
        clip = false
    ),
    exit: ExitTransition = fadeOut(
        animationSpec = FloatSpec
    ) + shrinkVertically(
        animationSpec = SizeSpec,
        clip = false
    ),
    crossinline content: @Composable (T) -> Unit
) {
    val ref = remember {
        Ref<T>()
    }

    ref.value = value ?: ref.value

    AnimatedVisibility(
        visible = value != null,
        enter = enter,
        exit = exit,
        content = {
            ref.value?.let { value ->
                content(value)
            }
        }
    )
}
zoltish
  • 2,122
  • 19
  • 37
1

I might be a little late to the party, but there is also the AnimatedContent composable. It still requires an opt-in to the ExperimentalAnimationApi, however it will pass the model back down through its composable lambda parameter, and you can check that one for nullability. Here's how it would work:

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.runtime.Composable

@Composable
@OptIn(ExperimentalAnimationApi::class)
fun AnimatedPlates(plates: Plates?) {
    AnimatedContent(plates) { plates ->
        if (plates != null) {
            // Render plates
        } else {
            // Render either nothing, or some empty state.
        }
    }
}

I hope this helps!

sindrenm
  • 3,254
  • 3
  • 24
  • 30
-1

Animation always requires content even when there is nothing to show. Just display a Surface when the content is null (or empty):

AnimatedVisibility(
        visible = plates != null,
        content = {
            if (plates != null) {
                // Render plates
            } else {
                Surface(modifier = Modifier.fillMaxSize()) { }
            }
        })
Johann
  • 27,536
  • 39
  • 165
  • 279
  • 1
    This has the same effect, when plates becomes null, the empty surface is rendered and it looks like the plates immediately disappear. – zoltish Dec 01 '21 at 15:31