I'm trying to implement an animation as DrawModifi...
# compose
m
I'm trying to implement an animation as DrawModifier, how would I accomplish this? the Draw function seems to be only called once per recomposition
j
Don't you need recomposition to be able to update your animation state? Once per recomposition sounds ideal
m
The problem is that it's implemented in a fairly complex way and doesn't depend on a "progress" variable if that makes sense, so infiniteRepeatable wouldn't be a good fit here for an infinite animation every frame.
It's probably closer to a particle-like effect if that helps clarify the use-case
j
Sounds like your particles have a state, which should trigger recomposition on update 🤔
1
1
s
Copy code
LaunchedEffect {
  while (true) {
    withFrameNanos {
       // update animation
    }
  }
}
 
Modifier.drawBehind {
  // read animation state 
}
1
e
Don’t you need recomposition to be able to update your animation state? Once per recomposition sounds ideal
No. If you set it up as infinite repeatable then it already animates & updates its state.
Sounds like your particles have a state, which should trigger recomposition on update
There should be a state but it does not need to trigger recomposition, it could trigger only relayout and/or redraw.
j
Yeah, a change in animation state would trigger recomposition, which would re-layout and/or re-draw as needed Whether that recomposition is skipped is up to the runtime right?
e
No, I am saying that in this case, iiuc, they only need to invalidate draw only, not composition nor layout … by reading the state within draw phase
j
Right, and I'm saying recomposition would trigger that redraw, and skip the layout phase My understanding is composition isn't a magic third step, instead it encompasses both layout and draw, deciding which one to run (if any)
A state change triggers recomposition. Composition decides whats changed, and whether that change requires layout or draw
e
A state change triggers recomposition if that state is read during composition. More correctly, a state change will trigger a rerun of the scope in which it is read. If you do not read the state in composition, it will not recompose on that state change. In this case, its advisable to read the state within a draw scope, as you gain nothing from triggering recomposition (like I mentioned, infinite repeatable already animates its value internally, you only need to observe its state)
My understanding is composition isn’t a magic third step, instead it encompasses both layout and draw, deciding which one to run (if any)
Ah, I get it, I think we just have different meaning for the terms, but officially compose ui has (generally) three *distinct* phases: composition, layout & draw. Thats the definition I am referencing
👀 1
j
Thanks for the resource, I didn't realise state reads had scopes beyond `@Composable`s 🤔
I guess what I've been referring to as "recomposition" is actually a "frame", and composition is its own phase
☝🏻 1
z
Lots of things are restartable based on snapshot state.
💯 1
m
Solved by adding the following snippet to my modifier node:
Copy code
override val shouldAutoInvalidate = false
override fun onAttach() {
    coroutineScope.launch {
        while (isActive) {
            withFrameNanos {
                invalidateDraw()
            }
        }
    }
}
s
I mean that would work, but it will redraw even if animation is not running
2
m
It's meant to draw something new every frame, it's not using the compose animation system since using that would require a transition from 0 to infinity to ensure it always runs and invalidates each draw but that would likely also unnecessarily trigger recompositions.
s
It doesn't cause recompositions really? Unless you read the animated value in composition Btw the more optimized way would be to just call invalidateDraw at the end of draw, I think that should also loop indefinitely
1