Hello, what is the correct way to animate some sta...
# compose
l
Hello, what is the correct way to animate some state (e.g.
animateFloatAsState(…)
) but only at a certain rate? My intent is to not cause re-renders at 120fps during the animation, but only 60, 30, 20, or even 15fps, for elements that move only a little, and don't need a high refresh rate animation to feel smooth.
z
I think your only option right now is to do a loop with a delay
l
I was thinking using Choreographer + count and
%
,wouldn't that work best than delay?
@ephemient That'd affect all animations. I want to do it only for select ones, and keep at the maximum device desired framerate otherwise.
j
You can still grab the frame clock for that
z
Yea if you want to use Choreographer directly you could probably make something work
j
Don't bother with Choreographer directly
z
our frame clock doesn’t have support for schedule-with-delay, just fyi
l
BTW, didn't mention, I'm not in Compose UI, though I'm still drawing pixels with Compose runtime, on the Android platform
That means I can control the frame clock, but I don't want to delay all the animations
j
You can use withFrameNanos to get vsync and then counts + deltas to maintain a framerate
👍 2
The clock is in the coroutine context
l
It is, I'm putting it myself :)
I took some inspiration from Molecule to bootstrap my project
BTW, why did you say "Don't bother with Choreographer directly", @jw ? Wouldn't it be equivalent to using
withFrameNanos
?
j
Because the abstraction facilitates things like testing and debugging
👍 1
Mosaic and Redwood are probably better examples since they have real node trees
e
totally untested (I haven't even entered it into a compiler), but I imagine that if you replace
Copy code
val float by animateFloatAsState(value)
by
Copy code
class SkippingMonotonicFrameClock(private val divisor: Int, private val delegate: MonotonicFrameClock) : MonotonicFrameClock {
    override fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R) {
        repeat(divisor - 1) { delegate.withFrameNanos { } }
        return delegate.withFrameNanos(onFrame)
    }
}
val animatedFloat = remember { Animatable(value) }
LaunchedEffect(value) {
    withContext(SkippingMonotonicFrameClock(divisor = 4, delegate = currentCoroutineContext[MonotonicFrameClock]!!)) {
        animatedFloat.animateTo(value)
    }
}
val float by animatedFloat
that it would only animate on every 4th frame
👀 1
l
For sure. Molecule was lacking some things I needed, but it was simple to gradually get familiar with deeper Compose things. Now that I'm more familiar with how nodes and applier work, and SnapshotStateObserver, I'll probably look into Redwood implementation
Thank you @ephemient, this snippet is nice in that it lets me keep using the Animatable APIs and everything that's built upon it 🙏🏼
Thank you all for your answers 🙏🏼
r
The problem with that snippet is that you are making assumptions about the screen's refresh rate.
You should at least know what it is to figure out your divisor
Ans even better would be to have a version of Android that supports variable refresh rate 😇
1
e
it would probably be better to skip the frame time callbacks until a specific time has passed if you're targeting a specific frame rate, but it's basically the same idea
VRR would be neat 🙂
1
l
I was thinking about that, Romain, and thinking that I can indeed look into how much frameTimeNanos grew since last call where I called the delegate, to work the same on 120fps, 90fps, 60fps, and ??fps screens
BTW, is Choreographer always going to call the doFrame callback for the max refresh rate available to the application? That is, at least 60fps on a typical LCD/OLED/Amoled screen? Or are there situations where it would not call doFrame ASAP? Besides the device's being sleeping of course.