diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp index 7c086e4ad13..7d0e0ee17fa 100644 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp @@ -28,6 +28,20 @@ void LayoutAnimationsManager::configureAnimation( } } +void LayoutAnimationsManager::setShouldAnimateExiting(int tag, bool value) { + auto lock = std::unique_lock(animationsMutex_); + shouldAnimateExitingForTag_[tag] = value; +} + +bool LayoutAnimationsManager::shouldAnimateExiting( + int tag, + bool shouldAnimate) { + auto lock = std::unique_lock(animationsMutex_); + return collection::contains(shouldAnimateExitingForTag_, tag) + ? shouldAnimateExitingForTag_[tag] + : shouldAnimate; +} + bool LayoutAnimationsManager::hasLayoutAnimation( int tag, LayoutAnimationType type) { @@ -44,6 +58,7 @@ void LayoutAnimationsManager::clearLayoutAnimationConfig(int tag) { enteringAnimations_.erase(tag); exitingAnimations_.erase(tag); layoutAnimations_.erase(tag); + shouldAnimateExitingForTag_.erase(tag); #ifdef DEBUG const auto &pair = viewsScreenSharedTagMap_[tag]; screenSharedTagSet_.erase(pair); diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h index 05829105cb8..b13440a7702 100644 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h @@ -32,6 +32,8 @@ class LayoutAnimationsManager { LayoutAnimationType type, const std::string &sharedTransitionTag, std::shared_ptr config); + void setShouldAnimateExiting(int tag, bool value); + bool shouldAnimateExiting(int tag, bool shouldAnimate); bool hasLayoutAnimation(int tag, LayoutAnimationType type); void startLayoutAnimation( jsi::Runtime &rt, @@ -69,9 +71,10 @@ class LayoutAnimationsManager { std::unordered_set ignoreProgressAnimationForTag_; std::unordered_map> sharedTransitionGroups_; std::unordered_map viewTagToSharedTag_; + std::unordered_map shouldAnimateExitingForTag_; mutable std::mutex animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`, - // `layoutAnimations_` and `viewSharedValues_`. + // `layoutAnimations_`, `viewSharedValues_` and `shouldAnimateExitingForTag_`. }; } // namespace reanimated diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index b830639df40..b05f6862ac2 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -321,6 +321,14 @@ jsi::Value NativeReanimatedModule::configureLayoutAnimation( return jsi::Value::undefined(); } +void NativeReanimatedModule::setShouldAnimateExiting( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &shouldAnimate) { + layoutAnimationsManager_.setShouldAnimateExiting( + viewTag.asNumber(), shouldAnimate.asBool()); +} + bool NativeReanimatedModule::isAnyHandlerWaitingForEvent( const std::string &eventName, const int emitterReactTag) { diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index 0c49f1b707e..67ef933df84 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -94,6 +94,10 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec { const jsi::Value &type, const jsi::Value &sharedTransitionTag, const jsi::Value &config) override; + void setShouldAnimateExiting( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &shouldAnimate) override; void onRender(double timestampMs); diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp index 03353ba8c7d..fc32dce39fd 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp @@ -176,6 +176,16 @@ static jsi::Value SPEC_PREFIX(configureLayoutAnimation)( std::move(args[3])); } +static jsi::Value SPEC_PREFIX(setShouldAnimateExiting)( + jsi::Runtime &rt, + TurboModule &turboModule, + const jsi::Value *args, + size_t) { + static_cast(&turboModule) + ->setShouldAnimateExiting(rt, std::move(args[0]), std::move(args[1])); + return jsi::Value::undefined(); +} + NativeReanimatedModuleSpec::NativeReanimatedModuleSpec( std::shared_ptr jsInvoker) : TurboModule("NativeReanimated", jsInvoker) { @@ -213,5 +223,7 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec( methodMap_["configureLayoutAnimation"] = MethodMetadata{4, SPEC_PREFIX(configureLayoutAnimation)}; + methodMap_["setShouldAnimateExitingForTag"] = + MethodMetadata{2, SPEC_PREFIX(setShouldAnimateExiting)}; } } // namespace reanimated diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h index 5eaf02dd362..f777be7ab1e 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h @@ -102,6 +102,11 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { const jsi::Value &type, const jsi::Value &sharedTransitionTag, const jsi::Value &config) = 0; + + virtual void setShouldAnimateExiting( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &shouldAnimate) = 0; }; } // namespace reanimated diff --git a/android/src/fabric/java/com/swmansion/reanimated/NativeProxy.java b/android/src/fabric/java/com/swmansion/reanimated/NativeProxy.java index e987b88efb6..ae7ac8df87b 100644 --- a/android/src/fabric/java/com/swmansion/reanimated/NativeProxy.java +++ b/android/src/fabric/java/com/swmansion/reanimated/NativeProxy.java @@ -80,6 +80,11 @@ public int findPrecedingViewTagForTransition(int tag) { return -1; } + @Override + public boolean shouldAnimateExiting(int tag, boolean shouldAnimate) { + return false; + } + @Override public boolean hasAnimation(int tag, int type) { return false; diff --git a/android/src/main/cpp/LayoutAnimations.cpp b/android/src/main/cpp/LayoutAnimations.cpp index 3497d66c24d..02e6bb76009 100644 --- a/android/src/main/cpp/LayoutAnimations.cpp +++ b/android/src/main/cpp/LayoutAnimations.cpp @@ -47,6 +47,11 @@ void LayoutAnimations::setHasAnimationBlock( this->hasAnimationBlock_ = hasAnimationBlock; } +void LayoutAnimations::setShouldAnimateExitingBlock( + ShouldAnimateExitingBlock shouldAnimateExitingBlock) { + this->shouldAnimateExitingBlock_ = shouldAnimateExitingBlock; +} + #ifdef DEBUG void LayoutAnimations::setCheckDuplicateSharedTag( CheckDuplicateSharedTag checkDuplicateSharedTag) { @@ -62,6 +67,10 @@ bool LayoutAnimations::hasAnimationForTag(int tag, int type) { return hasAnimationBlock_(tag, type); } +bool LayoutAnimations::shouldAnimateExiting(int tag, bool shouldAnimate) { + return shouldAnimateExitingBlock_(tag, shouldAnimate); +} + void LayoutAnimations::setClearAnimationConfigBlock( ClearAnimationConfigBlock clearAnimationConfigBlock) { this->clearAnimationConfigBlock_ = clearAnimationConfigBlock; @@ -102,6 +111,8 @@ void LayoutAnimations::registerNatives() { "startAnimationForTag", LayoutAnimations::startAnimationForTag), makeNativeMethod( "hasAnimationForTag", LayoutAnimations::hasAnimationForTag), + makeNativeMethod( + "shouldAnimateExiting", LayoutAnimations::shouldAnimateExiting), makeNativeMethod( "clearAnimationConfigForTag", LayoutAnimations::clearAnimationConfigForTag), diff --git a/android/src/main/cpp/LayoutAnimations.h b/android/src/main/cpp/LayoutAnimations.h index df85d802d9f..692c3172ad8 100644 --- a/android/src/main/cpp/LayoutAnimations.h +++ b/android/src/main/cpp/LayoutAnimations.h @@ -15,6 +15,7 @@ class LayoutAnimations : public jni::HybridClass { using AnimationStartingBlock = std::function>)>; using HasAnimationBlock = std::function; + using ShouldAnimateExitingBlock = std::function; #ifdef DEBUG using CheckDuplicateSharedTag = std::function; #endif @@ -34,10 +35,13 @@ class LayoutAnimations : public jni::HybridClass { int type, alias_ref> values); bool hasAnimationForTag(int tag, int type); + bool shouldAnimateExiting(int tag, bool shouldAnimate); bool isLayoutAnimationEnabled(); void setAnimationStartingBlock(AnimationStartingBlock animationStartingBlock); void setHasAnimationBlock(HasAnimationBlock hasAnimationBlock); + void setShouldAnimateExitingBlock( + ShouldAnimateExitingBlock shouldAnimateExitingBlock); #ifdef DEBUG void setCheckDuplicateSharedTag( CheckDuplicateSharedTag checkDuplicateSharedTag); @@ -64,6 +68,7 @@ class LayoutAnimations : public jni::HybridClass { jni::global_ref javaPart_; AnimationStartingBlock animationStartingBlock_; HasAnimationBlock hasAnimationBlock_; + ShouldAnimateExitingBlock shouldAnimateExitingBlock_; ClearAnimationConfigBlock clearAnimationConfigBlock_; CancelAnimationBlock cancelAnimationBlock_; FindPrecedingViewTagForTransitionBlock diff --git a/android/src/main/cpp/NativeProxy.cpp b/android/src/main/cpp/NativeProxy.cpp index 83469601e27..85b610db0bf 100644 --- a/android/src/main/cpp/NativeProxy.cpp +++ b/android/src/main/cpp/NativeProxy.cpp @@ -498,6 +498,15 @@ void NativeProxy::setupLayoutAnimations() { return false; }); + layoutAnimations_->cthis()->setShouldAnimateExitingBlock( + [weakNativeReanimatedModule](int tag, bool shouldAnimate) { + if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { + return nativeReanimatedModule->layoutAnimationsManager() + .shouldAnimateExiting(tag, shouldAnimate); + } + return false; + }); + #ifdef DEBUG layoutAnimations_->cthis()->setCheckDuplicateSharedTag( [weakNativeReanimatedModule](int viewTag, int screenTag) { diff --git a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java index 28c7eacdf42..008a4c71c35 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java @@ -86,7 +86,7 @@ public void onViewRemoval(View view, ViewGroup parent, Runnable callback) { Integer tag = view.getId(); mCallbacks.put(tag, callback); - if (!removeOrAnimateExitRecursive(view, true)) { + if (!removeOrAnimateExitRecursive(view, true, true)) { removeView(view, parent); } } @@ -489,6 +489,10 @@ public void updateLayout( } } + public boolean shouldAnimateExiting(int tag, boolean shouldAnimate) { + return mNativeMethodsHolder.shouldAnimateExiting(tag, shouldAnimate); + } + public boolean hasAnimationForTag(int tag, int type) { return mNativeMethodsHolder.hasAnimation(tag, type); } @@ -497,7 +501,8 @@ public boolean isLayoutAnimationEnabled() { return mNativeMethodsHolder != null && mNativeMethodsHolder.isLayoutAnimationEnabled(); } - private boolean removeOrAnimateExitRecursive(View view, boolean shouldRemove) { + private boolean removeOrAnimateExitRecursive( + View view, boolean shouldRemove, boolean shouldAnimate) { int tag = view.getId(); ViewManager viewManager = resolveViewManager(tag); @@ -512,8 +517,12 @@ private boolean removeOrAnimateExitRecursive(View view, boolean shouldRemove) { } } + shouldAnimate = shouldAnimateExiting(tag, shouldAnimate); + boolean hasExitAnimation = - hasAnimationForTag(tag, LayoutAnimations.Types.EXITING) || mExitingViews.containsKey(tag); + shouldAnimate + && (hasAnimationForTag(tag, LayoutAnimations.Types.EXITING) + || mExitingViews.containsKey(tag)); boolean hasAnimatedChildren = false; shouldRemove = shouldRemove && !hasExitAnimation; @@ -531,7 +540,7 @@ private boolean removeOrAnimateExitRecursive(View view, boolean shouldRemove) { ViewGroup viewGroup = (ViewGroup) view; for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { View child = viewGroup.getChildAt(i); - if (removeOrAnimateExitRecursive(child, shouldRemove)) { + if (removeOrAnimateExitRecursive(child, shouldRemove, shouldAnimate)) { hasAnimatedChildren = true; } else if (shouldRemove && child.getId() != -1) { toBeRemoved.add(child); diff --git a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/LayoutAnimations.java b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/LayoutAnimations.java index c1e1439217a..c9126fe4216 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/LayoutAnimations.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/LayoutAnimations.java @@ -40,6 +40,8 @@ public LayoutAnimations(ReactApplicationContext context) { public native boolean hasAnimationForTag(int tag, int type); + public native boolean shouldAnimateExiting(int tag, boolean shouldAnimate); + public native void checkDuplicateSharedTag(int viewTag, int screenTag); public native void clearAnimationConfigForTag(int tag); diff --git a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/NativeMethodsHolder.java b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/NativeMethodsHolder.java index 50074ca5951..803cb5295d6 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/NativeMethodsHolder.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/NativeMethodsHolder.java @@ -5,6 +5,8 @@ public interface NativeMethodsHolder { void startAnimation(int tag, int type, HashMap values); + boolean shouldAnimateExiting(int tag, boolean shouldAnimate); + boolean hasAnimation(int tag, int type); void clearAnimationConfig(int tag); diff --git a/android/src/paper/java/com/swmansion/reanimated/NativeProxy.java b/android/src/paper/java/com/swmansion/reanimated/NativeProxy.java index d63b679619f..648ab4ab1aa 100644 --- a/android/src/paper/java/com/swmansion/reanimated/NativeProxy.java +++ b/android/src/paper/java/com/swmansion/reanimated/NativeProxy.java @@ -72,6 +72,15 @@ public void startAnimation(int tag, int type, HashMap values) { } } + @Override + public boolean shouldAnimateExiting(int tag, boolean shouldAnimate) { + LayoutAnimations layoutAnimations = weakLayoutAnimations.get(); + if (layoutAnimations != null) { + return layoutAnimations.shouldAnimateExiting(tag, shouldAnimate); + } + return false; + } + @Override public boolean isLayoutAnimationEnabled() { LayoutAnimations layoutAnimations = weakLayoutAnimations.get(); diff --git a/app/src/examples/LayoutAnimations/FlatListSkipEnteringExiting.tsx b/app/src/examples/LayoutAnimations/FlatListSkipEnteringExiting.tsx new file mode 100644 index 00000000000..4b2a4bb49e3 --- /dev/null +++ b/app/src/examples/LayoutAnimations/FlatListSkipEnteringExiting.tsx @@ -0,0 +1,121 @@ +'use strict'; +import React, { useState } from 'react'; +import { Button, StyleSheet } from 'react-native'; +import Animated, { + FadeInUp, + LayoutAnimationConfig, + PinwheelIn, + PinwheelOut, + SlideInRight, + SlideOutLeft, +} from 'react-native-reanimated'; + +const digits = [0, 1, 2]; + +export default function FlatListSkipEnteringExiting() { + return ; +} + +function List() { + const [show, setShow] = useState(true); + const [data, setData] = useState(digits); + + return ( + <> +