Skip to content

Generic native layout-animation backend (Core Animation + Android animators)#9647

Draft
piaskowyk wants to merge 1 commit into
@pawicao/core-animation-layout-animationsfrom
claude/infallible-williamson-0386d9
Draft

Generic native layout-animation backend (Core Animation + Android animators)#9647
piaskowyk wants to merge 1 commit into
@pawicao/core-animation-layout-animationsfrom
claude/infallible-williamson-0386d9

Conversation

@piaskowyk

@piaskowyk piaskowyk commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

This PR builds on the Core Animation layout-animations PoC (#8442) and turns it into a complete, cross-platform native backend for entering / exiting / layout animations. When enabled, layout animations bypass the per-frame React/JS mutation path entirely and are played by the platform's native animation engine — Core Animation on iOS and android.animation on Android — for better performance.

It targets @pawicao/core-animation-layout-animations and is intended to be merged into that branch.

Approach — one generic, JS-sampled keyframe descriptor

The PoC re-implemented each preset's math in C++ (NativeLayoutAnimationPreset*) and only animated position.x/y. That doesn't scale to the ~90 presets (sequences, easings, springs, opacity/scale/rotation/skew/perspective…).

Instead, this PR samples the existing Reanimated animation objects — the very same withTiming / withSequence / withSpring / easing the legacy path drives — across virtual time into one generic, platform-agnostic keyframe descriptor:

{ durationMs, properties: [ { keyPath, offsets[0..1], values } ] }

Because we tick the real animation objects (on the UI runtime, where defineAnimation resolves to concrete objects), every preset is supported automatically, with easing and spring physics baked into the sampled curve. Channels are canonical: opacity, originX/Y, width/height, translateX/Y, scaleX/Y, rotation, rotationX/Y, skewX, perspective.

Flow: native proxy (flag on) → C++ LayoutAnimationsManager::startNativeLayoutAnimation calls JS LayoutAnimationsManager.computeNativeDescriptor(type, values, config) → parses the descriptor → hands it to the platform player via PlatformDepMethodsHolder.runNativeLayoutAnimation. Completion runs through the existing endLayoutAnimation path (so exiting views are still removed correctly).

What's new

Shared (TS):

  • layoutReanimation/nativeAnimationDescriptor.ts — the generic sampler + descriptor types.
  • animationsManager.tscomputeNativeDescriptor on the UI-runtime manager.
  • Feature flags: kept IOS_USE_NATIVE_LAYOUT_ANIMATIONS, added ANDROID_USE_NATIVE_LAYOUT_ANIMATIONS.

Shared (C++):

  • NativeLayoutAnimationDescriptor.h — the descriptor struct.
  • LayoutAnimationsManager / LayoutAnimationsProxy_Legacy / ReanimatedModuleProxy rewired to compute the descriptor in JS and call a cross-platform RunNativeLayoutAnimation player.
  • Removed the now-dead C++ preset library (NativeLayoutAnimation*, NativeLayoutAnimationPreset*Factory/Impl).

iOS:

  • REANodesManager runNativeLayoutAnimationForView: — generic Core Animation player: CAKeyframeAnimations for opacity / composed CATransform3D / position / bounds.size, committing final values to the model layer under setDisableActions:YES (no snap-back), and using the presentation layer for seamless interruption.

Android (new native backend):

  • LayoutAnimationCallback (C++ HybridClass + Kotlin) bridges completion back to C++.
  • NativeProxy::runNativeLayoutAnimation flattens the descriptor over JNI.
  • NativeLayoutAnimator.kt replays it on the View via ValueAnimator (alpha / translation / scale / rotation / cameraDistance), driven linearly since easing/springs are pre-baked.

Known limitations / follow-ups

  • Transform channels are canonicalized (composed in a fixed order), so a few pivot-sensitive presets (Rotate*, some Flip*) may differ slightly from the JS path; can be refined per-preset.
  • Android has no View skew, so LightSpeed* loses its shear; width/height are approximated as a top-left scale (no child relayout mid-animation).
  • The PoC's rawConfig/applyRawConfig plumbing is now vestigial (the descriptor is derived from the serialized builder) and can be removed in a follow-up.

Test plan

  • iOS and Android both build the fabric-example app.
  • Enable the relevant *_USE_NATIVE_LAYOUT_ANIMATIONS flag and exercise the layout-animation examples (entering/exiting/linear-transition, interrupted animations)

Replace the C++ per-preset computation with a generic JS-sampled keyframe
descriptor played by the platform animation engine (Core Animation on iOS,
android.animation on Android). The sampler ticks the existing Reanimated
animation objects, so all presets, sequences, easings and springs are
supported automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant