Search code examples
android-jetpack-composeandroid-animation

Trigger an animation without state change from outside a Composable


In a Composable it is easy to animate between state changes. But is there a good way to just trigger an animation without state change, e.g. let an element blink when there is a signal from outside.

Example:

@Composable
fun AnimatableText(play: State<Boolean>) {
    val alpha = remember { Animatable(0f) }

    LaunchedEffect(play.value) {
        alpha.snapTo(0f)
        alpha.animateTo(1f, animationSpec = tween(2000))
    }

    Text(
        text = "TEST",
        modifier = Modifier.alpha(alpha.value)
    )
}

In this example, the text disappears and fades in again, when the state of play changes. However, in this example the animation also plays, when the Composable is created. Also, the state of play must change, where it would be nicer to just have a function outside the Composable, where we can just call play(). There might be workarounds to avoid animation on creation, but I wonder if there is a simple and reliable way to trigger an animation, which does not reflect a state change.


Solution

  • You can do for example something like this:

    class AnimatableTextState(
        initialAlpha: Float = 1F,
    ) {
        private val animatable = Animatable(initialAlpha)
        val alpha: Float get() = animatable.value
    
        suspend fun play() {
            animatable.snapTo(0F)
            animatable.animateTo(1F, animationSpec = tween(2000))
        }
    }
    
    @Composable
    fun AnimatableText(state: AnimatableTextState) {
        Text(
            text = "TEST",
            modifier = Modifier.alpha(state.alpha)
        )
    }
    
    @Composable
    fun OtherComposable() {
        val animatableTextState = remember { AnimatableTextState() }
        val scope = rememberCoroutineScope()
        
        Column {
            AnimatableText(animatableTextState)
            Button(onClick = { scope.launch { animatableTextState.play() } }) {
                Text("Animate")
            }
        }
    }
    

    With this, your text will start with alpha = 1F and it will only animate when play() is called, not after composable is created. If you want to start with different alpha value, you can simple do that remember { AnimatableTextState(0.5F) }.