Reputation: 383
I have this code to get an infinite rotation animation:
@Composable
fun RotatingObject(rpm: Int) {
val infiniteTransition = rememberInfiniteTransition()
val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
Surface(
Modifier
.size(100.dp)
.graphicsLayer { rotationZ = rotation},
color = Color.Gray
) {}
}
I want the rpm
parameter to define the number of revolutions per minute the object should make while spinning. I have tried setting durationMillis
to 60000 / rpm
but the speed stays the same after rpm
changes.
How can I get the speed to change after the initial composition?
Requirements
The rotation angle should not jump or jitter - it should always continually change based on the current rpm.
The solution should be friendly to animated rpm
values to allow for smoothly changing speed over time.
I would prefer a solution that doesn't cause avoidable recompositions.
Edit:
After trying countless different things, each of which had something wrong with it, I'm going to share the least wrong outcome.
@Composable
fun RotatingObject(rpm: Int) {
var rotation by remember { mutableStateOf(0f) }
val infiniteTransition = rememberInfiniteTransition()
val ticker by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
LaunchedEffect(ticker) {
rotation += 0.1f * rpm
}
Surface(
Modifier
.size(100.dp)
.graphicsLayer { rotationZ = rotation},
color = Color.Gray
) {}
}
I track the rotation value as state and manually change it inside a LaunchedEffect
. The relaunching of the effect is controlled by the infinite transition. This makes the rotation update at the expected frame rate. 0.1f * rpm
is the conversion of rpm to 'degrees per frame' at 60Hz.
So, this behaves as I want it to, however, in order to get here I made an assumption that I feel is not safe to make. This code requires that the effect is relaunched at a constant frequency of 60Hz. Since I didn't see this defined anywhere, this might just be the case on my system.
Also, if I do more operations inside the effect, it sometimes decides not to update the rotation at all, meaning that this is not a reliable way to do this. Therefore I feel that this is not a correct solution and I'm afraid it may not behave the same on different systems or at different times even. I currently have no choice but to use this, so if you have any ideas or suggestions, please share.
Upvotes: 5
Views: 2061
Reputation: 895
While the question is old, it may be helpful for others as I needed the same behavior and found a solution.
In Compose AnimationSpec
you can explicitly define the values for each frame. It is no different for infiniteRepeatable
animation, as it has animationSpec
argument.
An example from AnimationSpec
which is very telling is as follows:
keyframes {
0f at 0 //ms
0.4f at 75 // ms
0.4f at 225 // ms
0f at 375 // ms
durationMillis = 375
}
In my case, I wanted a simple solution for breathing animation, but the exhalation is longer than the inhalation. Thus, for my specific case, I implemented the infinite animation like this:
val infiniteTransition = rememberInfiniteTransition(label = "infiniteScaleBackground")
val liveScaleBackground by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 5000
1.2f at 2000 using LinearEasing // Takes 2 seconds to reach 1.2f
1f at 5000 using LinearEasing // Takes 3 seconds to return to 1f
},
repeatMode = RepeatMode.Restart
),
label = "liveScaleBackground"
)
Note, that I have the repeatMode
on RepeatMode.restart
. That's because, within the keyFrames
, I return to the starting point within the single cycle.
Upvotes: 0