One of the ways to do is using Animatables that animate scale and alpha after each other.
create a class that contains 2 Animatables
class AnimatedCountdownTimer(
private val coroutineScope: CoroutineScope
) {
private val animatableScale = Animatable(1f)
private val animatableAlpha = Animatable(1f)
val scale: Float
get() = animatableScale.value
val alpha: Float
get() = animatableAlpha.value
fun start(initialValue: Int, endValue: Int, onChange: (Int) -> Unit) {
var value = initialValue
coroutineScope.launch {
while (value > endValue - 1) {
onChange(value)
animatableScale.snapTo(1f)
animatableAlpha.snapTo(1f)
animatableScale.animateTo(2f, animationSpec = tween(750))
animatableAlpha.animateTo(0f, animationSpec = tween(250))
value--
}
}
}
}
And use it as
@Preview
@Composable
fun TimerTest() {
var timer by remember {
mutableStateOf(5)
}
val coroutineScope = rememberCoroutineScope()
val animatedCountdownTimer = remember {
AnimatedCountdownTimer(coroutineScope)
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier.graphicsLayer {
scaleX = animatedCountdownTimer.scale
scaleY = animatedCountdownTimer.scale
alpha = animatedCountdownTimer.alpha
},
text = "$timer",
fontSize = 120.sp,
fontWeight = FontWeight.Bold,
color = Color.Gray
)
Spacer(Modifier.height(20.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
animatedCountdownTimer.start(5, 0) {
timer = it
}
}
) {
Text("Start")
}
}
}
If you don't want to scale to 1f after animation end just ad if block so it can stay at scale. Any param can be customized easily time durations or max scale.
If you wish to keep last number scaled you can use it like this
coroutineScope.launch {
while (value > endValue - 1) {
onChange(value)
animatableScale.snapTo(1f)
animatableAlpha.snapTo(1f)
animatableScale.animateTo(2f, animationSpec = tween(750))
if (value > endValue) {
animatableAlpha.animateTo(0f, animationSpec = tween(250))
}
value--
}
}