I tried to make a "pulse" animation but the result...
# compose
b
I tried to make a "pulse" animation but the result is really bad, the icon inside the button keep "shaking" due to some rounding errors in the size and padding calculation.
Copy code
Box(
    modifier = Modifier.fillMaxSize()
) {
    val infiniteTransition: InfiniteTransition = rememberInfiniteTransition()
    val initialSize: Float = 72f
    val pulsingSize: Float = 88f
    val size: Float by infiniteTransition.animateFloat(
        initialValue = initialSize,
        targetValue = pulsingSize,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 1000,
                easing = LinearEasing
            ),
            repeatMode = RepeatMode.Reverse
        )
    )
    val paddingReduction: Float = (size - initialSize) / 2
    FloatingActionButton(
        modifier = Modifier
            .align(Alignment.BottomCenter)
            .padding((16f - paddingReduction).dp)
            .size(size.dp),
        onClick = {}
    ) {
        Icon(
            imageVector = Icons.Default.ArrowUpward,
            contentDescription = "To the top!"
        )
    }
}
f
Can you just use DP as the state you animate? So animateDp or something like that in the API instead of animateFloat? Also - try not to use linear easing but something else, because that's usually the least smooth easing 😄
b
Like that? It seems even worst 😟
Copy code
val infiniteTransition: InfiniteTransition = rememberInfiniteTransition()
val initialSize: Dp = 72.dp
val pulsingSize: Dp = 88.dp
val size: Dp by infiniteTransition.animateValue(
    initialValue = initialSize,
    targetValue = pulsingSize,
    typeConverter = Dp.VectorConverter,
    animationSpec = infiniteRepeatable(
        animation = tween(
            durationMillis = 1000,
            easing = FastOutSlowInEasing
        ),
        repeatMode = RepeatMode.Reverse
    )
)
FloatingActionButton(
    modifier = Modifier
        .align(Alignment.BottomCenter)
        .padding(16.dp - (size - initialSize) / 2)
        .size(size),
    backgroundColor = backgroundColor,
    contentColor = contentColor,
    onClick = {}
) {
    Icon(
        imageVector = Icons.Default.ArrowUpward,
        contentDescription = "To the top!"
    )
}
f
Can you debug to check the padding values? maybe it's got to do with diving by (2) rather than (2f) so the rounding could go wrong there
b
I/System.out: Size: 72.0.dp | Padding: 16.0.dp I/System.out: Size: 87.12841.dp | Padding: 8.435795.dp I/System.out: Size: 87.72229.dp | Padding: 8.138855.dp I/System.out: Size: 78.69273.dp | Padding: 12.653633.dp I/System.out: Size: 86.10176.dp | Padding: 8.94912.dp I/System.out: Size: 87.75508.dp | Padding: 8.122459.dp I/System.out: Size: 87.99959.dp | Padding: 8.000206.dp I/System.out: Size: 87.81478.dp | Padding: 8.092609.dp I/System.out: Size: 87.195595.dp | Padding: 8.402203.dp I did the maths, the values are right so far, so the "noise" come from the final dp -> pixel conversion by Compose framework
Not sure if I can get around this problem using different composition stack, like make the FloatingActionButton centered in another Box that is 120dp and aligned bottom
f
Yeah I remember that the approach you're taking worked before the transition API changes: https://www.raywenderlich.com/13282144-jetpack-compose-animations-tutorial-getting-started it could be just an issue of adding padding and the size. Maybe the order of the params matters, so having padding after size could help - to make sure the padding is applied after the size is already drawn, but I'm not a 100% sure 😢
b
The problem in my case is that, unlike in this tutorial, the icon inside my button stay the same. It's not supposed to move but due to the animation, the icon "shake" a lot. The border of the button is animated smoothly but not its icon.
👍 1
Ok I found a workaround, using
Modifier.scale()
instead of
Modifier.size()
on the Button. The icon inside the button grow too, so it's not exactly the animation I was trying to achieve, but at least it's smooth.
👍 1
f
Woo!
a
If you need a pulse animation, this is my implementation from my work project:
Copy code
@Composable
fun Pulse(
    modifier: Modifier = Modifier,
    tint: Color = LocalContentColor.current,
    active: Boolean = true,
    bounded: Boolean = true,
    count: Int = 3,
    duration: Int = 1000,
) {
    if (!active) return

    val infiniteTransition = rememberInfiniteTransition()
    val pulses = buildList {
        for (i in 0 until count) {
            add(infiniteTransition.animateFloat(
                initialValue = 0F,
                targetValue = 1F,
                animationSpec = infiniteRepeatable(
                    animation = tween(duration * count, easing = FastOutSlowInEasing),
                    initialStartOffset = StartOffset(i * duration),
                )
            ))
        }
    }

    Canvas(modifier.then(if (bounded) Modifier.clipToBounds() else Modifier)) {
        val (a, b) = size
        val r = sqrt(a.pow(2) + b.pow(2)) / 2

        pulses.forEach {
            val progress = it.value

            scale(progress) {
                drawCircle(tint.copy(alpha = (1F - progress) * 0.12F), r)
            }
        }
    }
}
Seems like you're using this in a fab, could probably throw this in a box and use it
Copy code
FloatingActionButton(
                {},
                Modifier.align(Alignment.BottomEnd).navigationBarsPadding().padding(16.dp),
                backgroundColor = Theme.colors.primary,
            ) {
                Icon(Icons.Household.ic_fan_mode_on)
                Pulse(Modifier.size(56.dp))
            }
b
@andrew Thank you for the code sample, will help a lot for the next steps!
a
That is the effect you want to achieve, right? @Benjamin Deroche?
b
@andrew This animation is a bit "light" for my use case, I need something more flashy, I was thinking more about something like that:

https://www.youtube.com/watch?v=SDJUbYU3Pzc&t=409s

But the code you shared is still a good step forward and will help me a lot. Thank you.
a
You can modify the colors, etc easily
You would just put both inside a box, set the bounds of pulse, and put the fab on top 🙂
And it’s probably gonna be more performant, no layout happening on each pulse expansion, all transforms, canvas, etc, so no jumping around
You could also modify the pulse to allow children, I might do that too for my own uses
b
Yes thank this was really helpful to get me started
I don't know if you've seen it but
drawCircle
accept an optional parameter
alpha
so you don't have to use
tint.copy
You can also simplify the function you used to build the list:
Copy code
val pulses = List(count) { i ->
    infiniteTransition.animateFloat(
        initialValue = 0F,
        targetValue = 1F,
        animationSpec = infiniteRepeatable(
            animation = tween(duration * count, easing = FastOutSlowInEasing),
            initialStartOffset = StartOffset(i * duration),
        )
    )
}
a
I am used to copying the color, but that works too
I also interchangeably use buildlist 😛