Skip to content

Conversation

geoff-powell
Copy link
Collaborator

@geoff-powell geoff-powell commented Oct 18, 2024

So animations with offsetMillis didn't work properly in paparazzi. This was due to the Transition apis tracking the startTimeNanos when a transition starts. Since this is set ahead of the transition, the delta to render the frame was always 0. For gif() methods that used start = 0 they render correctly because the starting frame is at 0 nanos.

The best way I could combat this was for cases in Compose where the caller was manually setting the startNanos for Paparazzi.takeSnapshots() I call multiple render calls with time 0 to ensure Transition apis have startTimeNanos = 0

Fixes #627 #678

@geoff-powell geoff-powell force-pushed the gpowell/compose/animation-timing branch 3 times, most recently from b3babf7 to 3079a30 Compare October 18, 2024 20:03
@geoff-powell geoff-powell requested a review from nak5ive October 18, 2024 20:07
@geoff-powell geoff-powell changed the title Proposed fix to better time Compose UI animations Proposed fix to correct Compose UI animations Oct 18, 2024
@jrodbx jrodbx added this to the 1.4 milestone Oct 22, 2024
Copy link
Collaborator

@BrianGardnerAtl BrianGardnerAtl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good. Having to render twice with a 0 start time is a little quirky but the comment explains it well

@geoff-powell geoff-powell force-pushed the gpowell/compose/animation-timing branch 2 times, most recently from d4adf47 to d9ebe08 Compare July 22, 2025 14:34
renderSession.render(false)
}
}

for (frame in 0 until frameCount) {
val nowNanos = (startNanos + (frame * 1_000_000_000.0 / fps)).toLong()

// If we have pendingTasks run recomposer to ensure we get the correct frame.
var hasPendingWork = false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @geoff-powell, spotted a subtle bug here. hasPendingWork is a computed property backed by synchronized state in Recomposer, so storing its value in a local var like this might introduce an issue if the underlying state changes between reads (which is likely during animation rendering).

Would it make sense to unwrap recomposer once, then query recomposerInstance.hasPendingWork at the moment it's needed, instead of snapshotting into a local var?

That way the logic stays aligned with Recomposer's live state, like:

if (frame == 0 && recomposerInstance.hasPendingWork) {
    withTime(nowNanos) {
        renderForResult()
    }
    if (recomposerInstance.hasPendingWork) {
        logger.warning("...")
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

offsetMillis in Compose
4 participants