https://kotlinlang.org logo
#compose
Title
# compose
s

svenjacobs

10/27/2022, 7:29 AM
Hello, what is the best “Compose way” to repeat an animation just a specific number of times? For instance I want to highlight a specific item on my screen by shortly flashing the background. Would the following (pseudo) code be okay or are there any better solutions?
Copy code
enum class BackgroundState { Normal, Highlighted }

@Composable
fun Item() {
    var backgroundState by remember { mutableStateOf(BackgroundState.Normal) }
    val backgroundColor by animateColorAsState(
        targetValue = when (backgroundState) {
            BackgroundState.Normal -> Color.Transparent
            BackgroundState.Highlighted -> Color.Yellow
        },
        animationSpec = tween(),
    )

    LaunchedEffect(Unit) {
        delay(250)
        backgroundState = BackgroundState.Highlighted
        delay(250)
        backgroundState = BackgroundState.Normal
        delay(250)
        backgroundState = BackgroundState.Highlighted
        delay(250)
        backgroundState = BackgroundState.Normal
    }

    Box(modifier = Modifier.background(backgroundColor)) {
        Text("Hello World")
    }
}
Thanks for your help!
y

Yves Kalume

10/27/2022, 7:38 AM
You can use
repeatable
as the animation spec
s

svenjacobs

10/27/2022, 7:46 AM
I still don't understand how to switch between two colours with
repeatable
. Could you please show a code example?
y

Yves Kalume

10/27/2022, 8:04 AM
Copy code
var startAnimation by remember {
        mutableStateOf(false)
    }

    val bgColor by animateColorAsState(
        targetValue = if (startAnimation) Color.Blue else Color.Black,
        animationSpec = repeatable(
            iterations = 5,
            animation = tween(),
            repeatMode = RepeatMode.Reverse
        )
    )

    LaunchedEffect(Unit) {
        startAnimation = true
    }
With repeatable, you can specify iterations, but if you can an infinite animation you can use
infiniteRepeatable
f

Filip Wiesner

10/27/2022, 8:56 AM
Maybe this thread could be somewhat relatable. TLDR: I think you are looking for
Animatable
s

svenjacobs

10/27/2022, 9:00 AM
Thanks Yves and Filip, I will have a look at your solutions.
s

Stylianos Gakis

10/27/2022, 9:25 AM
Animatable for sure. With the functions being suspending, you can simply call one after the other and it all just works™. It even waits until the animation is over, so you have full control over it.
Copy code
@Composable
fun Item() {
    val backgroundColor = remember { Animatable(Color.Transparent) }

    LaunchedEffect(Unit) {
        repeat(2) {
            backgroundColor.animateTo(Color.Yellow, tween(1_000))
            backgroundColor.animateTo(Color.Transparent, tween(1_000))
        }
    }

    Box(
        modifier = Modifier.background(backgroundColor.value),
    ) {
        Text("Hello World")
    }
}
One thing I noticed running this, the vector conversion from
Transparent
to
Yellow
goes through a really dark color in-between, so might have to play with the vector conversion somehow. Not a color expert though, not quite sure how you’d do that 😅
Right, because half-way though, the rgba values are (0xa0, 0xa0, 0x00, 0xa0) that shows a quite black color, as that’s what Color.Transparent gives, 0x00000000 which is pure black, only entirely transparent. Going linearly from that to yellow means that inbetween it’s gonna be half black, which is half visible. What you probably want to go for, is going from Color.Yellow with 0% opacity, to Color.Yellow with 100% opacity instead. So:
Copy code
@Composable
fun Item() {
    val backgroundColor = remember {
        Animatable(Color.Yellow.copy(alpha = 0f))
    }

    SideEffect { println(backgroundColor.value) }

    LaunchedEffect(Unit) {
        repeat(2) {
            backgroundColor.animateTo(Color.Yellow, tween())
            backgroundColor.animateTo(Color.Yellow.copy(alpha = 0f), tween())
        }
    }

    Column {
        Box(
            modifier = Modifier.background(backgroundColor.value),
        ) {
            Text("Hello World")
        }
    }
}
z

Zoltan Demant

10/27/2022, 10:05 AM
Fwiw, I recently saw that someone reported the issue with color animations not taking alpha values into account. I cant find it now, but its out there somewhere 🛸 and theres a workaround that works on the animationSpec level of things!
s

Stylianos Gakis

10/27/2022, 10:10 AM
Hmmm but I wonder how that’d work. If you’re going from Transparent to Yellow, you still need to go from (for notation 0xRR GG BB AA) 0x00 00 00 00 to 0xFF FF 00 FF So else would this be interpolated if not have the alpha go from 00 to FF? I don’t know if I could call this an issue, it’s just that if you want to only animate the alpha, and not the color, maybe you need to do just that. Maybe even change the code to this tbh to better show the intention:
Copy code
@Composable
fun Item() {
    val backgroundAlpha = remember { Animatable(0f) }

    LaunchedEffect(Unit) {
        repeat(2) {
            backgroundAlpha.animateTo(1f, tween())
            backgroundAlpha.animateTo(0f, tween())
        }
    }

    Column {
        Box(
            modifier = Modifier.background(Color.Yellow.copy(alpha = backgroundAlpha.value)),
        ) {
            Text("Hello World")
        }
    }
}
z

Zoltan Demant

10/27/2022, 10:17 AM
s

Stylianos Gakis

10/27/2022, 10:58 AM
Ooh, “alpha premultiplication”, a fancy new word to know about 😅 Yes this sounds interesting, I never would’ve come up with smth like that myself. It sounds like a good general solution for a lot of cases then.
z

Zoltan Demant

10/27/2022, 11:14 AM
It makes a pretty huge difference for some of my use-cases; I think in the same manner as what youre doing in the code snippets above 🙂
554 Views