I have Row with .horizontalScroll() and I need bounce animation effect. That is, when the list appears, it scrolls forward a little, and then returns back. At the same time, I need a spring effect when returning the scroll back. Here is my implementation:
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val bounceDistance = 150
LaunchedEffect(Unit) {
val animatable = Animatable(scrollState.value.toFloat())
coroutineScope.launch {
animatable.animateTo(
targetValue = bounceDistance.toFloat(),
animationSpec = tween(
durationMillis = 200,
easing = LinearOutSlowInEasing
)
)
scrollState.scrollTo(animatable.value.toInt())
animatable.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = 0.4f,
stiffness = Spring.StiffnessLow
)
)
scrollState.scrollTo(animatable.value.toInt())
animatable.animateTo(
targetValue = bounceDistance * 0.3f,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
scrollState.scrollTo(animatable.value.toInt())
animatable.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = 0.6f,
stiffness = Spring.StiffnessLow
)
)
scrollState.scrollTo(animatable.value.toInt())
}
}
However, it all looks very unsmooth and slow. What am I doing wrong?
P.S. This is needed to hint to the user that the component has horizontal scrolling.
Your code doesn't work properly because you manually call scrollState.scrollTo
4 times after each animation. You should call scrollState.scrollTo
when animatable.value
updates, for example:
val scrollState = rememberScrollState()
val bounceDistance = 150
val animatable = remember { Animatable(0f) }
LaunchedEffect(Unit) {
animatable.animateTo(
targetValue = bounceDistance.toFloat(),
animationSpec = tween(
durationMillis = 200,
easing = LinearOutSlowInEasing
)
)
animatable.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = 0.4f,
stiffness = Spring.StiffnessLow
)
)
animatable.animateTo(
targetValue = bounceDistance * 0.3f,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
animatable.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = 0.6f,
stiffness = Spring.StiffnessLow
)
)
}
LaunchedEffect(animatable.value) {
scrollState.scrollTo(animatable.value.toInt())
}
Instead of making bounce animation manually you can use tween
animation spec with EaseOutBounce easing:
val animatable = remember { Animatable(0, Int.VectorConverter) }
LaunchedEffect(Unit) {
animatable.animateTo(
targetValue = bounceDistance,
animationSpec = tween(
durationMillis = 200,
easing = LinearOutSlowInEasing
)
)
animatable.animateTo(
targetValue = 0,
animationSpec = tween(
durationMillis = 1000,
easing = EaseOutBounce
)
)
}
LaunchedEffect(animatable.value) {
scrollState.scrollTo(animatable.value)
}