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

Benjamin Deroche

03/22/2022, 9:53 AM
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

f.babic

03/22/2022, 9:54 AM
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

Benjamin Deroche

03/22/2022, 10:02 AM
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

f.babic

03/22/2022, 10:05 AM
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

Benjamin Deroche

03/22/2022, 10:19 AM
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

f.babic

03/22/2022, 10:26 AM
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

Benjamin Deroche

03/22/2022, 10:46 AM
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

f.babic

03/22/2022, 10:59 AM
Woo!
a

andrew

03/22/2022, 2:20 PM
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

Benjamin Deroche

03/22/2022, 4:34 PM
@andrew Thank you for the code sample, will help a lot for the next steps!
a

andrew

03/22/2022, 4:35 PM
That is the effect you want to achieve, right? @Benjamin Deroche?
b

Benjamin Deroche

03/22/2022, 4:41 PM
@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

andrew

03/22/2022, 4:41 PM
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

Benjamin Deroche

03/23/2022, 9:35 AM
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

andrew

03/23/2022, 2:38 PM
I am used to copying the color, but that works too
I also interchangeably use buildlist 😛
88 Views