Skip to content

feat: Add the Android animator for CSS platform transitions - #10090

Merged
MatiPl01 merged 9 commits into
mainfrom
@matipl01/android-css-transition-player
Aug 12, 2026
Merged

MatiPl01 merged 9 commits into
mainfrom
@matipl01/android-css-transition-player

Conversation

@MatiPl01

@MatiPl01 MatiPl01 commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Plays an already resolved CSS transition timeline on a native View with ObjectAnimator. Nothing constructs it yet; the C++ resolver and the JNI bridge follow separately.

Animates the same View property React Native writes rather than a side channel. transitionAlpha composites with the real alpha, so it could only ever carry opacity, could not express a fade to fully transparent, and is contested by android.transition, which captures and restores it around its own animations. Writing React Native's own property removes all three limits and the API 29 floor with them. The cost is that a commit can overwrite a running animation, which the next PR reconciles.

Easings are rebuilt rather than sampled, so steps() keeps its discontinuities at any duration. An interruption resumes from the value on screen rather than the committed style value, which would snap the view back to where the cancelled transition began.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fe7a5151-545f-4218-b0c8-74414a636a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 7c49248 and 0fba311.

📒 Files selected for processing (3)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added Android support for CSS-style transition animations.
    • Added easing support for cubic-bezier, steps, and linear-stop transitions.
    • Added opacity transitions for animated views.
  • Bug Fixes
    • Transitions now respect Android animation duration settings, including disabled animations.
    • Interrupted transitions resume smoothly from their current state.
    • Improved transition cancellation and handling for views that are not yet mounted.

Walkthrough

Android CSS transition support adds opacity property access, duration-scale handling, CSS easing conversion, and a manager that resolves views and controls synchronized ObjectAnimator instances.

Changes

Android CSS transition playback

Layer / File(s) Summary
Transition channels and duration scaling
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPropertyWriter.kt, packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt
Opacity maps to View.alpha. Effective animation scaling uses Android-version-specific APIs and settings.
CSS easing conversion
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
Cubic-bezier, step, and linear-stop descriptors produce Android interpolators. Unknown types use linear interpolation.
Transition animation orchestration
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
Transitions resolve Fabric views, cache interpolators, retry until mounting, resume interrupted animations, align timelines, and cancel active transitions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CSSPlatformTransitionsManager
  participant DurationScale
  participant FabricUIManager
  participant CSSEasing
  participant ObjectAnimator
  CSSPlatformTransitionsManager->>DurationScale: effectiveScale(reactContext)
  CSSPlatformTransitionsManager->>FabricUIManager: resolveView(viewTag)
  CSSPlatformTransitionsManager->>CSSEasing: create interpolator(easing type and points)
  CSSPlatformTransitionsManager->>ObjectAnimator: configure and start transition
  ObjectAnimator-->>CSSPlatformTransitionsManager: complete and clear transition
Loading

Possibly related PRs

Suggested reviewers: pawicao

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding the Android animator for CSS platform transitions.
Description check ✅ Passed The description directly explains the animator behavior, supported properties, easing handling, interruption behavior, and implementation scope.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch @matipl01/android-css-transition-player

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 63-68: Update the transition method surrounding viewForTag and its
UiThreadUtil callback so it does not return true when the native view cannot be
resolved. Make ownership conditional on successfully obtaining the view, or
provide the required deferred/failure handoff so callers retain control when
viewForTag returns null; preserve the existing start flow for resolved views.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1491d62e-3f51-42d2-809e-09e620cdf018

📥 Commits

Reviewing files that changed from the base of the PR and between 01b2f47 and 03c7d6f.

📒 Files selected for processing (4)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSViewChannel.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt

@MatiPl01 MatiPl01 self-assigned this Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt`:
- Line 7: Update the documentation comment near the easing implementations to
scope the “reproduced rather than sampled” statement only to the custom
interpolators, specifically the steps() and linear-stop implementations. Remove
any implication that PathInterpolator or all easing curves avoid internal
sampling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 45dcd9d4-0bd8-4079-9186-2c0ec84cd80e

📥 Commits

Reviewing files that changed from the base of the PR and between 03c7d6f and 26d6bf2.

📒 Files selected for processing (4)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSViewChannel.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt (1)

51-56: ⚠️ Potential issue | 🟠 Major

Do not report success when the native view is unavailable.

viewForTag(viewTag) can return null, but animateTransition still returns true; line 52 then silently drops the transition. Make ownership conditional on resolving the view, or add a deferred failure handoff so the caller retains its fallback timeline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`
around lines 51 - 56, Update animateTransition so it does not return success
when viewForTag(viewTag) resolves to null; ensure ownership remains with the
caller by returning false or using the established deferred failure handoff
before the UI-thread callback drops the transition. Preserve the existing start
flow when a native view is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 51-56: Update animateTransition so it does not return success when
viewForTag(viewTag) resolves to null; ensure ownership remains with the caller
by returning false or using the established deferred failure handoff before the
UI-thread callback drops the transition. Preserve the existing start flow when a
native view is available.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 067c92be-f9f9-4f54-8b58-f4df0e4896bf

📥 Commits

Reviewing files that changed from the base of the PR and between 26d6bf2 and 067569f.

📒 Files selected for processing (3)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPropertyWriter.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt (1)

61-63: 🎯 Functional Correctness | 🟠 Major

Do not claim ownership after an unsuccessful retry.

If both begin() attempts fail, no animator is created, but animateTransition() still returns true. Preserve a failure handoff, or explicitly define true as “accepted for asynchronous retry” rather than successful native playback. This repeats the unresolved ownership issue from the previous review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`
around lines 61 - 63, Update animateTransition() and the retry path around
begin() so a failed immediate attempt followed by a failed Choreographer retry
does not return true or claim animation ownership without creating an animator.
Either propagate the failure handoff after the retry or explicitly make true
represent acceptance for asynchronous retry, consistently preserving ownership
semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Line 61: Update the retry flow around begin() in CSSPlatformTransitionsManager
to retain a pending frame callback generation/token per transition key. Clear or
invalidate that token in removeTransition() and when a newer request supersedes
the same key, and validate it before the posted callback invokes begin() so
stale retries cannot start obsolete transitions.

---

Duplicate comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 61-63: Update animateTransition() and the retry path around
begin() so a failed immediate attempt followed by a failed Choreographer retry
does not return true or claim animation ownership without creating an animator.
Either propagate the failure handoff after the retry or explicitly make true
represent acceptance for asynchronous retry, consistently preserving ownership
semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 900286a5-e8e2-4bbc-8e4e-ea52bab0a529

📥 Commits

Reviewing files that changed from the base of the PR and between 067569f and 0603dc7.

📒 Files selected for processing (2)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 80-85: Update the Choreographer callback in the transition-start
flow so it does not permanently remove the start token when the view is still
unmounted. Retry on subsequent frames or use mount notifications until the view
exists, while enforcing a bounded timeout or otherwise failing explicitly; only
clear startTokens and call begin() once the transition can start.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d878d51-ce92-455d-84bd-81020d72db16

📥 Commits

Reviewing files that changed from the base of the PR and between 0603dc7 and 7c1e083.

📒 Files selected for processing (1)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt (1)

166-167: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the interpolator cache.

Line 166 creates a distinct cache key for every easing point list. Line 167 retains the key and interpolator with no eviction. Dynamic transition timing values can grow this map for the manager lifetime and cause memory pressure.

Use a bounded cache with eviction, or avoid caching non-preset easing definitions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`
around lines 166 - 167, Bound the cache used by the interpolator lookup around
InterpolatorKey and interpolators so dynamically generated easing point lists
cannot accumulate for the manager lifetime. Prefer the existing cache
implementation if it supports a fixed capacity and eviction; otherwise bypass
caching for non-preset easing definitions while preserving cached preset
interpolators.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 166-167: Bound the cache used by the interpolator lookup around
InterpolatorKey and interpolators so dynamically generated easing point lists
cannot accumulate for the manager lifetime. Prefer the existing cache
implementation if it supports a fixed capacity and eviction; otherwise bypass
caching for non-preset easing definitions while preserving cached preset
interpolators.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e099afbb-d0e0-474d-aa31-2fd1d6954ab9

📥 Commits

Reviewing files that changed from the base of the PR and between 0603dc7 and bf506b5.

📒 Files selected for processing (1)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt (2)

92-103: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check expiry before begin().

Line 99 invokes begin() before it checks endTimestampMs. If the View mounts after this timestamp, begin() starts an expired animator.

Check the timestamp first. Remove the token and return without calling begin() when the transition has ended.

Proposed fix
-        if (begin() || animationTimestamp() >= endTimestampMs) {
+        if (animationTimestamp() >= endTimestampMs || begin()) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`
around lines 92 - 103, Update beginWhenMounted to check animationTimestamp()
against endTimestampMs before invoking begin(). When the transition has expired,
remove startTokens[key] and return without calling begin(); otherwise preserve
the existing begin() success handling and frame-callback retry flow.

26-26: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Bound the interpolator cache.

Each distinct timing function retains boxed point lists and an interpolator for the manager lifetime. Dynamic CSS timing functions can grow this map without a limit.

Use a bounded cache. Alternatively, cache only PathInterpolator instances.

Also applies to: 159-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`
at line 26, Bound the interpolator cache used by
CSSPlatformTransitionsManager.interpolators so dynamically generated timing
functions cannot grow it indefinitely. Replace the unbounded HashMap with the
project’s established bounded-cache mechanism and preserve lookup/reuse
behavior; alternatively, restrict caching to PathInterpolator instances while
leaving other interpolators uncached.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt`:
- Around line 92-103: Update beginWhenMounted to check animationTimestamp()
against endTimestampMs before invoking begin(). When the transition has expired,
remove startTokens[key] and return without calling begin(); otherwise preserve
the existing begin() success handling and frame-callback retry flow.
- Line 26: Bound the interpolator cache used by
CSSPlatformTransitionsManager.interpolators so dynamically generated timing
functions cannot grow it indefinitely. Replace the unbounded HashMap with the
project’s established bounded-cache mechanism and preserve lookup/reuse
behavior; alternatively, restrict caching to PathInterpolator instances while
leaving other interpolators uncached.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: da709d87-20cb-4b04-93bc-3fb9de32a7df

📥 Commits

Reviewing files that changed from the base of the PR and between bf506b5 and 55089ed.

📒 Files selected for processing (4)
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSEasing.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPlatformTransitionsManager.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/CSSPropertyWriter.kt
  • packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/css/DurationScale.kt

Plays an already resolved CSS transition timeline on a native View with
ObjectAnimator. Nothing constructs it yet; the C++ resolver and the JNI bridge
follow separately.

A property rides a View channel that React Native never writes itself, so a
Fabric commit re-applying the prop cannot clobber a running animation. opacity
uses setTransitionAlpha, a second multiplicative alpha factor: to render f the
animator writes f / alpha and pins alpha to the target React is about to commit.
Anything times zero is zero, so a fade to fully transparent is inexpressible and
the channel declines it.

Easings are rebuilt rather than sampled, so steps() keeps its discontinuities at
any duration, and the point search mirrors firstSmallerOrEqual so a run of equal
x resolves to its last entry.

An interruption resumes from the value on screen instead of the committed style
value, which would snap the view back to where the cancelled transition began.
The caller resolves the timeline on the JS thread, but the animator only starts
after the hop to the UI thread and the wait for the next frame. Passing an
elapsed offset baked in at resolve time shifts the whole timeline late by that
gap; taking the start timestamp and subtracting here absorbs it, which is what
CALayer.beginTime does on Apple.

Both sides read the same clock, so slow animations cannot desync them.

Also drop the comments that only restated the code.
Drops the orthogonal-channel indirection. transitionAlpha composites with the
real alpha, so it can only carry opacity, cannot express a fade to fully
transparent, and is contested by android.transition, which captures and restores
it around its own animations.

Writing the property React Native writes removes all three limits and the API 29
floor with them, at the cost of a commit being able to overwrite a running
animation. Reconciling that is the next commit's job; nothing routes here yet.

Interruption now resumes straight from the property, and no teardown reset is
needed since React Native already clears its own fields on view recycling.
A tag can be registered before its View exists, with the mount still in flight,
and dropping the transition there snaps the property instead of animating it. The
start timestamp is absolute, so waiting a frame costs nothing: the elapsed offset
absorbs it.

Also narrows the easing comment, which implied every curve is evaluated directly.
PathInterpolator flattens cubic-bezier internally; only steps() and linear() stops
are computed from their points.
The posted frame callback was unguarded, so a retry could start an obsolete
transition after removeTransition ran or after a newer request took the same key.
Each start claims a monotonic token, which the callback checks before running;
removeTransition drops it. The token never repeats, so an entry cleared on a
successful start cannot be matched by a stale retry.

Also states what the return value means: the property is accepted for native
playback, but playback can still fail on the UI thread if the View never mounts,
and there is no path back to demote it - matching the Apple backend.
One retry assumed the mount always lands within a frame, which nothing
guarantees: the tag is registered independently of its View. Starting late is
harmless because the start timestamp is absolute, so a late start seeks rather
than drifting, which means the retry needs no arbitrary timeout - once the
transition would have ended there is nothing left to play.
ObjectAnimator writes nothing until its first frame, and transition-delay defers
that frame. React Native has already committed the target by then, so the view
showed the end state for the whole delay and jumped back when the animation
finally started.
ValueAnimator.start() reads mSeekFraction when deciding whether to begin
immediately, so seeking a late start afterwards raced the first frame.

Also drops comments that restate the code, shortens those whose second
sentence the first already implied, imports FloatProperty rather than naming
it in full, and renames the binary search to firstSmallerOrEqual so it and
its C++ counterpart turn up in the same grep.
@MatiPl01
MatiPl01 force-pushed the @matipl01/android-css-transition-player branch from 7c49248 to 9334e78 Compare August 11, 2026 23:56
@MatiPl01
MatiPl01 requested review from pawicao and tjzel August 12, 2026 07:25

@tjzel tjzel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, but please de-slop the comments a bit

@MatiPl01
MatiPl01 merged commit 2e3fd9e into main Aug 12, 2026
11 checks passed
@MatiPl01
MatiPl01 deleted the @matipl01/android-css-transition-player branch August 12, 2026 07:59
MatiPl01 added a commit that referenced this pull request Aug 12, 2026
…ative writes (#10092)

A platform transition animates the same `View` property React Native
writes, so any commit touching that view overwrites the running
animation. On Android Fabric re-applies the whole props blob, so it is
enough for any prop on the view to change.

This PR adds a pre-draw listener that re-applies styles from running
animators to ensure that the animation takes effect and is not
overridden by RN.

Stacked on #10090.
MatiPl01 added a commit that referenced this pull request Aug 12, 2026
…er JNI (#10060)

Owns the per-property state CSS reversing needs: it detects a reversal
against the in-flight transition's start value, reverse-shortens the
duration through the easing curve, and hands the resolved timeline to
the Kotlin animator from #10090.

Elapsed time crosses the boundary instead of an absolute start.
Reverse-shortening backdates the start and `transition-delay` pushes it
forward, and computing it here keeps Kotlin off a second clock that
would drift from the loop's timestamp.

Easings cross as the same normalized point arrays the loop uses, so
nothing reimplements the easing spec on the other side of the boundary.

Nothing routes to this yet; it is inert until the next PR flips the
gate.

Stacked on #10090.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants