Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <reanimated/LayoutAnimations/LayoutAnimationsManager.h>
#include <reanimated/LayoutAnimations/NativeLayoutAnimation.h>
#include <reanimated/LayoutAnimations/NativeLayoutAnimationPresetFactory.h>
#include <reanimated/LayoutAnimations/NativeLayoutAnimationDescriptor.h>

#include <memory>
#include <unordered_map>
Expand Down Expand Up @@ -90,44 +89,76 @@ void LayoutAnimationsManager::startLayoutAnimation(
rt, jsi::Value(tag), jsi::Value(static_cast<int>(type)), values, configPair.first->toJSValue(rt));
}

#if __APPLE__
static NativeLayoutAnimationDescriptor parseNativeDescriptor(jsi::Runtime &rt, const jsi::Object &descriptorObj) {
NativeLayoutAnimationDescriptor descriptor;
descriptor.durationMs = descriptorObj.getProperty(rt, "durationMs").asNumber();

jsi::Array properties = descriptorObj.getProperty(rt, "properties").asObject(rt).asArray(rt);
const size_t propertyCount = properties.size(rt);
descriptor.properties.reserve(propertyCount);

for (size_t i = 0; i < propertyCount; i++) {
jsi::Object property = properties.getValueAtIndex(rt, i).asObject(rt);

NativeLayoutAnimationProperty parsedProperty;
parsedProperty.keyPath = property.getProperty(rt, "keyPath").asString(rt).utf8(rt);

jsi::Array offsets = property.getProperty(rt, "offsets").asObject(rt).asArray(rt);
jsi::Array values = property.getProperty(rt, "values").asObject(rt).asArray(rt);
const size_t keyframeCount = offsets.size(rt);
parsedProperty.offsets.reserve(keyframeCount);
parsedProperty.values.reserve(keyframeCount);
for (size_t j = 0; j < keyframeCount; j++) {
parsedProperty.offsets.push_back(offsets.getValueAtIndex(rt, j).asNumber());
parsedProperty.values.push_back(values.getValueAtIndex(rt, j).asNumber());
}

descriptor.properties.push_back(std::move(parsedProperty));
}

return descriptor;
}

void LayoutAnimationsManager::startNativeLayoutAnimation(
jsi::Runtime &rt,
const int tag,
const LayoutAnimationType type,
const facebook::react::Rect &startFrame,
const facebook::react::Rect &endFrame,
const jsi::Object &values,
const bool usePresentationLayer,
std::function<void(bool)> &&onAnimationEnd) {
if (!runNativeLayoutAnimation_) {
onAnimationEnd(true);
return;
}

LayoutAnimationConfigEntry configPair;
{
auto lock = std::unique_lock<std::recursive_mutex>(animationsMutex_);
if (!getConfigsForType(type).contains(tag)) {
onAnimationEnd(true);
return;
}
configPair = getConfigsForType(type)[tag];
}

if (!configPair.second || !configPair.second->presetName) {
if (!configPair.first) {
onAnimationEnd(true);
return;
}

std::vector<NativeLayoutAnimation> animations = NativeLayoutAnimationPresetFactory::instance()
.create(type, *configPair.second->presetName)
->calculate(startFrame, endFrame);

auto callback = std::make_shared<std::function<void(bool)>>(std::move(onAnimationEnd));
if (type == LayoutAnimationType::ENTERING) {
facebook::react::Rect initialFrame = startFrame;
dispatch_async(dispatch_get_main_queue(), ^{
runCoreAnimationForView_(tag, initialFrame, animations, *configPair.second, false, [callback](bool finished) {
(*callback)(finished);
});
});
} else {
runCoreAnimationForView_(
tag, startFrame, animations, *configPair.second, true, [callback](bool finished) { (*callback)(finished); });
}
// Ask JS to run the preset builder for these runtime values and sample it into
// a generic keyframe descriptor (the same animation objects the legacy path
// would have driven frame-by-frame).
jsi::Object layoutAnimationsManager =
rt.global().getPropertyAsObject(rt, "global").getPropertyAsObject(rt, "LayoutAnimationsManager");
jsi::Function computeNativeDescriptor = layoutAnimationsManager.getPropertyAsFunction(rt, "computeNativeDescriptor");
jsi::Value descriptorValue = computeNativeDescriptor.call(
rt, jsi::Value(tag), jsi::Value(static_cast<int>(type)), values, configPair.first->toJSValue(rt));

NativeLayoutAnimationDescriptor descriptor = parseNativeDescriptor(rt, descriptorValue.asObject(rt));

runNativeLayoutAnimation_(tag, descriptor, usePresentationLayer, std::move(onAnimationEnd));
}
#endif

void LayoutAnimationsManager::cancelLayoutAnimation(jsi::Runtime &rt, const int tag) const {
jsi::Value layoutAnimationRepositoryAsValue =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,29 @@ class LayoutAnimationsManager {
public:
LayoutAnimationsManager() : sharedTransitionManager_(std::make_shared<SharedTransitionManager>()) {}

#if __APPLE__
explicit LayoutAnimationsManager(RunCoreAnimationForView runCoreAnimationForView) : LayoutAnimationsManager() {
runCoreAnimationForView_ = std::move(runCoreAnimationForView);
explicit LayoutAnimationsManager(RunNativeLayoutAnimation runNativeLayoutAnimation) : LayoutAnimationsManager() {
runNativeLayoutAnimation_ = std::move(runNativeLayoutAnimation);
}
#endif

void configureAnimationBatch(const std::vector<LayoutAnimationConfig> &layoutAnimationsBatch);
void setShouldAnimateExiting(const int tag, const bool value);
bool shouldAnimateExiting(const int tag, const bool shouldAnimate);
bool hasLayoutAnimation(const int tag, const LayoutAnimationType type);
void startLayoutAnimation(jsi::Runtime &rt, const int tag, const LayoutAnimationType type, const jsi::Object &values);
#if __APPLE__
// Computes a generic keyframe descriptor in JS (by sampling the preset's
// animation objects for the given runtime `values`) and hands it to the
// platform's native animation player. Used instead of `startLayoutAnimation`
// when the native layout-animations feature flag is enabled.
void startNativeLayoutAnimation(
jsi::Runtime &rt,
const int tag,
const LayoutAnimationType type,
const facebook::react::Rect &startFrame,
const facebook::react::Rect &endFrame,
const jsi::Object &values,
const bool usePresentationLayer,
std::function<void(bool)> &&onAnimationEnd);
#endif
bool hasNativeLayoutAnimationPlayer() const {
return runNativeLayoutAnimation_ != nullptr;
}
void clearLayoutAnimationConfig(const int tag);
void cancelLayoutAnimation(jsi::Runtime &rt, const int tag) const;
void transferConfigFromNativeID(const int nativeId, const int tag);
Expand All @@ -91,9 +95,7 @@ class LayoutAnimationsManager {
// `sharedTransitionsForNativeID_`, `sharedTransitions_`, `enteringAnimations_`, `exitingAnimations_`,
// `layoutAnimations_` and `shouldAnimateExitingForTag_`.

#if __APPLE__
RunCoreAnimationForView runCoreAnimationForView_;
#endif
RunNativeLayoutAnimation runNativeLayoutAnimation_;
};

} // namespace reanimated
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@

namespace reanimated {

namespace {
// Whether layout animations should be played by the platform's native animation
// engine (Core Animation / `android.animation`) instead of the legacy
// JS-driven React mutation path. Resolved at compile time from the static
// feature flags so the unused branch is dropped entirely.
constexpr bool useNativeLayoutAnimations() {
#if __APPLE__
return StaticFeatureFlags::getFlag("IOS_USE_NATIVE_LAYOUT_ANIMATIONS");
#elif defined(ANDROID)
return StaticFeatureFlags::getFlag("ANDROID_USE_NATIVE_LAYOUT_ANIMATIONS");
#else
return false;
#endif
}
} // namespace

// We never modify the Shadow Tree, we just send some additional
// mutations to the mounting layer.
// When animations finish, the Host Tree will represent the most recent Shadow
Expand Down Expand Up @@ -653,24 +669,6 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
.finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
}
auto oldView = mutation.oldChildShadowView;
auto newView = mutation.newChildShadowView;

#if __APPLE__
if constexpr (StaticFeatureFlags::getFlag("IOS_USE_NATIVE_LAYOUT_ANIMATIONS")) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
tag,
LayoutAnimationType::ENTERING,
// old view does not really exist here with proper data so we just
// pass new view
newView.layoutMetrics.frame,
newView.layoutMetrics.frame,
[strongThis, tag](bool finished) { strongThis->endLayoutAnimation(tag, false); });

return;
}
#endif

Snapshot values(mutation.newChildShadowView, window);
auto &uiRuntime = strongThis->uiRuntime_;
jsi::Object yogaValues(uiRuntime);
Expand All @@ -682,6 +680,18 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);

if constexpr (useNativeLayoutAnimations()) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
uiRuntime,
tag,
LayoutAnimationType::ENTERING,
yogaValues,
/* usePresentationLayer */ false,
[strongThis, tag](bool /* finished */) { strongThis->endLayoutAnimation(tag, false); });
return;
}

strongThis->layoutAnimationsManager_->startLayoutAnimation(
uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
});
Expand All @@ -708,16 +718,6 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
window = strongThis->surfaceManager.getWindow(surfaceId);
}

#if __APPLE__
if constexpr (StaticFeatureFlags::getFlag("IOS_USE_NATIVE_LAYOUT_ANIMATIONS")) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
tag,
LayoutAnimationType::EXITING,
oldView.layoutMetrics.frame,
oldView.layoutMetrics.frame,
[strongThis, tag](bool finished) { strongThis->endLayoutAnimation(tag, finished); });
} else
#endif
{
Snapshot values(oldView, window);

Expand All @@ -731,8 +731,19 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
strongThis->layoutAnimationsManager_->startLayoutAnimation(
uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);

if constexpr (useNativeLayoutAnimations()) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
uiRuntime,
tag,
LayoutAnimationType::EXITING,
yogaValues,
/* usePresentationLayer */ true,
[strongThis, tag](bool finished) { strongThis->endLayoutAnimation(tag, finished); });
} else {
strongThis->layoutAnimationsManager_->startLayoutAnimation(
uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
}
}
strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
});
Expand All @@ -751,7 +762,6 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
}

auto oldView = mutation.oldChildShadowView;
auto newView = mutation.newChildShadowView;
Rect window{};
{
auto &mutex = strongThis->mutex;
Expand All @@ -760,19 +770,6 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
window = strongThis->surfaceManager.getWindow(surfaceId);
}

#if __APPLE__
if constexpr (StaticFeatureFlags::getFlag("IOS_USE_NATIVE_LAYOUT_ANIMATIONS")) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
tag,
LayoutAnimationType::LAYOUT,
oldView.layoutMetrics.frame,
newView.layoutMetrics.frame,
[strongThis, tag](bool finished) { strongThis->endLayoutAnimation(tag, false); });

return;
}
#endif

Snapshot currentValues(oldView, window);
Snapshot targetValues(mutation.newChildShadowView, window);

Expand All @@ -792,6 +789,18 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);

if constexpr (useNativeLayoutAnimations()) {
strongThis->layoutAnimationsManager_->startNativeLayoutAnimation(
uiRuntime,
tag,
LayoutAnimationType::LAYOUT,
yogaValues,
/* usePresentationLayer */ true,
[strongThis, tag](bool /* finished */) { strongThis->endLayoutAnimation(tag, false); });
return;
}

strongThis->layoutAnimationsManager_->startLayoutAnimation(uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
});
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <string>
#include <vector>

namespace reanimated {

// A single animated channel of a native layout animation. Mirrors
// `NativeLayoutAnimationProperty` on the JS side. `offsets` are normalized to
// [0, 1] and `values` holds the matching keyframe values (same length).
//
// `keyPath` is a canonical, platform-agnostic channel name, one of: `opacity`,
// `originX`, `originY`, `width`, `height`, `translateX`, `translateY`,
// `scaleX`, `scaleY`, `rotation`, `rotationX`, `rotationY`, `skewX`,
// `perspective`. Angles are expressed in radians. The platform player maps
// these onto Core Animation key paths (iOS) or View properties (Android).
struct NativeLayoutAnimationProperty {
std::string keyPath;
std::vector<double> offsets;
std::vector<double> values;
};

// Generic, pre-sampled description of a layout animation. Produced in JS by
// sampling the regular Reanimated animation objects, and played natively by the
// platform animation engine. Mirrors `NativeLayoutAnimationDescriptor` on the
// JS side.
struct NativeLayoutAnimationDescriptor {
double durationMs = 0;
std::vector<NativeLayoutAnimationProperty> properties;
};

} // namespace reanimated

This file was deleted.

Loading