diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp index 5714d60ae6f7..7bec48e950f6 100644 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp @@ -8,48 +8,50 @@ namespace reanimated { -void LayoutAnimationsManager::configureAnimation( - const int tag, - const LayoutAnimationType type, - const std::string &sharedTransitionTag, - const std::shared_ptr &config) { - auto lock = std::unique_lock(animationsMutex_); - if (type == SHARED_ELEMENT_TRANSITION || - type == SHARED_ELEMENT_TRANSITION_PROGRESS) { +void LayoutAnimationsManager::configureAnimationBatch( + const std::vector &layoutAnimationsBatch) { + auto lock = std::unique_lock(animationsMutex_); + std::vector sharedTransitionConfigs; + for (auto layoutAnimationConfig : layoutAnimationsBatch) { + const auto &[tag, type, config, sharedTransitionTag] = + layoutAnimationConfig; + if (type == SHARED_ELEMENT_TRANSITION || + type == SHARED_ELEMENT_TRANSITION_PROGRESS) { + clearSharedTransitionConfig(tag); + sharedTransitionConfigs.push_back(std::move(layoutAnimationConfig)); + } else { + if (config == nullptr) { + getConfigsForType(type).erase(tag); + } else { + getConfigsForType(type)[tag] = config; + } + } + } + for (const auto &[tag, type, config, sharedTransitionTag] : + sharedTransitionConfigs) { + if (config == nullptr) { + continue; + } sharedTransitionGroups_[sharedTransitionTag].push_back(tag); viewTagToSharedTag_[tag] = sharedTransitionTag; getConfigsForType(SHARED_ELEMENT_TRANSITION)[tag] = config; if (type == SHARED_ELEMENT_TRANSITION) { ignoreProgressAnimationForTag_.insert(tag); } - } else { - getConfigsForType(type)[tag] = config; - } -} - -void LayoutAnimationsManager::configureAnimationBatch( - const std::vector &layoutAnimationsBatch) { - auto lock = std::unique_lock(animationsMutex_); - for (auto [tag, type, config] : layoutAnimationsBatch) { - if (config == nullptr) { - getConfigsForType(type).erase(tag); - } else { - getConfigsForType(type)[tag] = config; - } } } void LayoutAnimationsManager::setShouldAnimateExiting( const int tag, const bool value) { - auto lock = std::unique_lock(animationsMutex_); + auto lock = std::unique_lock(animationsMutex_); shouldAnimateExitingForTag_[tag] = value; } bool LayoutAnimationsManager::shouldAnimateExiting( const int tag, const bool shouldAnimate) { - auto lock = std::unique_lock(animationsMutex_); + auto lock = std::unique_lock(animationsMutex_); return collection::contains(shouldAnimateExitingForTag_, tag) ? shouldAnimateExitingForTag_[tag] : shouldAnimate; @@ -58,7 +60,7 @@ bool LayoutAnimationsManager::shouldAnimateExiting( bool LayoutAnimationsManager::hasLayoutAnimation( const int tag, const LayoutAnimationType type) { - auto lock = std::unique_lock(animationsMutex_); + auto lock = std::unique_lock(animationsMutex_); if (type == SHARED_ELEMENT_TRANSITION_PROGRESS) { auto end = ignoreProgressAnimationForTag_.end(); return ignoreProgressAnimationForTag_.find(tag) == end; @@ -66,12 +68,8 @@ bool LayoutAnimationsManager::hasLayoutAnimation( return collection::contains(getConfigsForType(type), tag); } -void LayoutAnimationsManager::clearLayoutAnimationConfig(const int tag) { - auto lock = std::unique_lock(animationsMutex_); - enteringAnimations_.erase(tag); - exitingAnimations_.erase(tag); - layoutAnimations_.erase(tag); - shouldAnimateExitingForTag_.erase(tag); +void LayoutAnimationsManager::clearSharedTransitionConfig(const int tag) { + auto lock = std::unique_lock(animationsMutex_); #ifndef NDEBUG const auto &pair = viewsScreenSharedTagMap_[tag]; screenSharedTagSet_.erase(pair); @@ -81,6 +79,7 @@ void LayoutAnimationsManager::clearLayoutAnimationConfig(const int tag) { sharedTransitionAnimations_.erase(tag); auto const &groupName = viewTagToSharedTag_[tag]; if (groupName.empty()) { + viewTagToSharedTag_.erase(tag); return; } auto &group = sharedTransitionGroups_[groupName]; @@ -95,6 +94,15 @@ void LayoutAnimationsManager::clearLayoutAnimationConfig(const int tag) { ignoreProgressAnimationForTag_.erase(tag); } +void LayoutAnimationsManager::clearLayoutAnimationConfig(const int tag) { + auto lock = std::unique_lock(animationsMutex_); + enteringAnimations_.erase(tag); + exitingAnimations_.erase(tag); + layoutAnimations_.erase(tag); + shouldAnimateExitingForTag_.erase(tag); + clearSharedTransitionConfig(tag); +} + void LayoutAnimationsManager::startLayoutAnimation( jsi::Runtime &rt, const int tag, @@ -102,7 +110,7 @@ void LayoutAnimationsManager::startLayoutAnimation( const jsi::Object &values) { std::shared_ptr config, viewShareable; { - auto lock = std::unique_lock(animationsMutex_); + auto lock = std::unique_lock(animationsMutex_); config = getConfigsForType(type)[tag]; } // TODO: cache the following!! diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h index db3de2399887..c5e3e8fd5287 100644 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h @@ -22,17 +22,13 @@ struct LayoutAnimationConfig { int tag; LayoutAnimationType type; std::shared_ptr config; + std::string sharedTransitionTag; }; class LayoutAnimationsManager { public: explicit LayoutAnimationsManager(const std::shared_ptr &jsLogger) : jsLogger_(jsLogger) {} - void configureAnimation( - const int tag, - const LayoutAnimationType type, - const std::string &sharedTransitionTag, - const std::shared_ptr &config); void configureAnimationBatch( const std::vector &layoutAnimationsBatch); void setShouldAnimateExiting(const int tag, const bool value); @@ -44,6 +40,7 @@ class LayoutAnimationsManager { const LayoutAnimationType type, const jsi::Object &values); void clearLayoutAnimationConfig(const int tag); + void clearSharedTransitionConfig(const int tag); void cancelLayoutAnimation(jsi::Runtime &rt, const int tag) const; int findPrecedingViewTagForTransition(const int tag); #ifndef NDEBUG @@ -75,7 +72,7 @@ class LayoutAnimationsManager { std::unordered_map> sharedTransitionGroups_; std::unordered_map viewTagToSharedTag_; std::unordered_map shouldAnimateExitingForTag_; - mutable std::mutex + mutable std::recursive_mutex animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`, // `layoutAnimations_`, `viewSharedValues_` and `shouldAnimateExitingForTag_`. }; diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 9a8b1a921664..d4a2a2a5efdf 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -377,23 +377,6 @@ jsi::Value NativeReanimatedModule::configureProps( return jsi::Value::undefined(); } -jsi::Value NativeReanimatedModule::configureLayoutAnimation( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &type, - const jsi::Value &sharedTransitionTag, - const jsi::Value &config) { - layoutAnimationsManager_.configureAnimation( - viewTag.asNumber(), - static_cast(type.asNumber()), - sharedTransitionTag.asString(rt).utf8(rt), - extractShareableOrThrow( - rt, - config, - "[Reanimated] Layout animation config must be an object.")); - return jsi::Value::undefined(); -} - jsi::Value NativeReanimatedModule::configureLayoutAnimationBatch( jsi::Runtime &rt, const jsi::Value &layoutAnimationsBatch) { @@ -415,6 +398,16 @@ jsi::Value NativeReanimatedModule::configureLayoutAnimationBatch( config, "[Reanimated] Layout animation config must be an object."); } + if (batch[i].type != SHARED_ELEMENT_TRANSITION && + batch[i].type != SHARED_ELEMENT_TRANSITION_PROGRESS) { + continue; + } + auto sharedTransitionTag = item.getProperty(rt, "sharedTransitionTag"); + if (sharedTransitionTag.isUndefined()) { + batch[i].config = nullptr; + } else { + batch[i].sharedTransitionTag = sharedTransitionTag.asString(rt).utf8(rt); + } } layoutAnimationsManager_.configureAnimationBatch(batch); return jsi::Value::undefined(); diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index 9fac5f50d94a..5cd7d03c137f 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -84,12 +84,6 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec { jsi::Runtime &rt, const jsi::Value &uiProps, const jsi::Value &nativeProps) override; - jsi::Value configureLayoutAnimation( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &type, - const jsi::Value &sharedTransitionTag, - const jsi::Value &config) override; jsi::Value configureLayoutAnimationBatch( jsi::Runtime &rt, const jsi::Value &layoutAnimationsBatch) override; diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp index fd88a073c544..91e3efc77d03 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp @@ -150,20 +150,6 @@ static jsi::Value SPEC_PREFIX(unsubscribeFromKeyboardEvents)( return jsi::Value::undefined(); } -static jsi::Value SPEC_PREFIX(configureLayoutAnimation)( - jsi::Runtime &rt, - TurboModule &turboModule, - const jsi::Value *args, - size_t) { - return static_cast(&turboModule) - ->configureLayoutAnimation( - rt, - std::move(args[0]), - std::move(args[1]), - std::move(args[2]), - std::move(args[3])); -} - static jsi::Value SPEC_PREFIX(configureLayoutAnimationBatch)( jsi::Runtime &rt, TurboModule &turboModule, @@ -214,8 +200,6 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec( methodMap_["unsubscribeFromKeyboardEvents"] = MethodMetadata{1, SPEC_PREFIX(unsubscribeFromKeyboardEvents)}; - methodMap_["configureLayoutAnimation"] = - MethodMetadata{4, SPEC_PREFIX(configureLayoutAnimation)}; methodMap_["configureLayoutAnimationBatch"] = MethodMetadata{1, SPEC_PREFIX(configureLayoutAnimationBatch)}; methodMap_["setShouldAnimateExitingForTag"] = diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h index e6180d808bdd..c0f91af2f5fb 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h @@ -96,13 +96,6 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { const jsi::Value &nativeProps) = 0; // layout animations - virtual jsi::Value configureLayoutAnimation( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &type, - const jsi::Value &sharedTransitionTag, - const jsi::Value &config) = 0; - virtual jsi::Value configureLayoutAnimationBatch( jsi::Runtime &rt, const jsi::Value &layoutAnimationsBatch) = 0; 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 172e6e90bbda..5f36c94e2b18 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/AnimationsManager.java @@ -714,8 +714,8 @@ private static Point convertScreenLocationToViewCoordinates(Point fromPoint, Vie return new Point(fromPoint.x - toPoint[0], fromPoint.y - toPoint[1]); } - public void screenDidLayout() { - mSharedTransitionManager.screenDidLayout(); + public void screenDidLayout(View view) { + mSharedTransitionManager.screenDidLayout(view); } public void viewDidLayout(View view) { @@ -726,6 +726,10 @@ public void notifyAboutViewsRemoval(int[] tagsToDelete) { mSharedTransitionManager.onViewsRemoval(tagsToDelete); } + public void notifyAboutScreenWillDisappear() { + mSharedTransitionManager.onScreenWillDisappear(); + } + public void makeSnapshotOfTopScreenViews(ViewGroup stack) { mSharedTransitionManager.doSnapshotForTopScreenViews(stack); } diff --git a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/ReanimatedNativeHierarchyManager.java b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/ReanimatedNativeHierarchyManager.java index e73034e3fb2a..e98c7706ca1b 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/ReanimatedNativeHierarchyManager.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/ReanimatedNativeHierarchyManager.java @@ -6,13 +6,16 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.NativeViewHierarchyManager; +import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.ViewAtIndex; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManagerRegistry; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationListener; import com.swmansion.reanimated.ReanimatedModule; @@ -164,6 +167,20 @@ public void deleteView(final View view, final LayoutAnimationListener listener) if (parentName.equals("RNSScreenStack")) { mAnimationsManager.cancelAnimationsInSubviews(view); super.deleteView(view, listener); + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag( + (ReactContext) view.getContext(), view.getId()); + if (eventDispatcher != null) { + eventDispatcher.addListener( + event -> { + // we schedule the start of transition for the ScreenWilDisappear event, so that the + // layout of the target screen is already calculated + // this allows us to make snapshots on the go, so that they are always up-to-date + if (event.getEventName().equals("topWillDisappear")) { + getAnimationsManager().notifyAboutScreenWillDisappear(); + } + }); + } return; } } @@ -264,7 +281,7 @@ public synchronized void updateLayout( if (container != null && viewManagerName.equals("RNSScreen") && mReaLayoutAnimator != null) { boolean hasHeader = checkIfTopScreenHasHeader((ViewGroup) container); if (!hasHeader || !container.isLayoutRequested()) { - mReaLayoutAnimator.getAnimationsManager().screenDidLayout(); + mReaLayoutAnimator.getAnimationsManager().screenDidLayout(container); } } View view = resolveView(tag); diff --git a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/SharedTransitionManager.java b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/SharedTransitionManager.java index 45b4d7429748..00c4524c1d99 100644 --- a/android/src/main/java/com/swmansion/reanimated/layoutReanimation/SharedTransitionManager.java +++ b/android/src/main/java/com/swmansion/reanimated/layoutReanimation/SharedTransitionManager.java @@ -8,8 +8,12 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.EventDispatcherListener; import com.facebook.react.views.view.ReactViewGroup; import com.swmansion.reanimated.Utils; import java.util.ArrayList; @@ -19,6 +23,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import javax.annotation.Nullable; public class SharedTransitionManager { @@ -29,6 +35,7 @@ public class SharedTransitionManager { private final Map mSharedTransitionInParentIndex = new HashMap<>(); private final Map mSnapshotRegistry = new HashMap<>(); private final Map mCurrentSharedTransitionViews = new HashMap<>(); + private final Map> mSharedViewChildrenIndices = new HashMap<>(); private View mTransitionContainer; private final List mRemovedSharedViews = new ArrayList<>(); private final Set mViewTagsToHide = new HashSet<>(); @@ -38,6 +45,25 @@ public class SharedTransitionManager { private final List mSharedElementsWithProgress = new ArrayList<>(); private final List mSharedElementsWithAnimation = new ArrayList<>(); private final Set mReattachedViews = new HashSet<>(); + private boolean mIsTransitionPrepared = false; + private final Set mTagsToCleanup = new HashSet<>(); + + class TopWillAppearListener implements EventDispatcherListener { + private final EventDispatcher mEventDispatcher; + + public TopWillAppearListener(EventDispatcher eventDispatcher) { + mEventDispatcher = eventDispatcher; + } + + @Override + public void onEventDispatch(Event event) { + if (event.getEventName().equals("topWillAppear")) { + tryStartSharedTransitionForViews(mAddedSharedViews, true); + mAddedSharedViews.clear(); + mEventDispatcher.removeListener(this); + } + } + } public SharedTransitionManager(AnimationsManager animationsManager) { mAnimationsManager = animationsManager; @@ -56,13 +82,22 @@ protected View getTransitioningView(int tag) { return mCurrentSharedTransitionViews.get(tag); } - protected void screenDidLayout() { - tryStartSharedTransitionForViews(mAddedSharedViews, true); - mAddedSharedViews.clear(); + protected void screenDidLayout(View view) { + if (mAddedSharedViews.isEmpty()) { + return; + } + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag( + (ReactContext) view.getContext(), view.getId()); + if (eventDispatcher != null) { + eventDispatcher.addListener(new TopWillAppearListener(eventDispatcher)); + } } protected void viewDidLayout(View view) { - maybeRestartAnimationWithNewLayout(view); + // this was causing problems when I moved the start of transition to the willAppear event + // handler + // maybeRestartAnimationWithNewLayout(view); } protected void onViewsRemoval(int[] tagsToDelete) { @@ -72,11 +107,11 @@ protected void onViewsRemoval(int[] tagsToDelete) { visitTreeForTags(tagsToDelete, new SnapshotTreeVisitor()); if (mRemovedSharedViews.size() > 0) { // this happens when navigation goes back - boolean animationStarted = tryStartSharedTransitionForViews(mRemovedSharedViews, false); - if (!animationStarted) { + mIsTransitionPrepared = prepareSharedTransition(mRemovedSharedViews, false); + if (!mIsTransitionPrepared) { mRemovedSharedViews.clear(); } - visitTreeForTags(tagsToDelete, new ConfigCleanerTreeVisitor()); + visitTreeForTags(tagsToDelete, new PrepareConfigCleanupTreeVisitor()); } } @@ -141,8 +176,7 @@ private void maybeRestartAnimationWithNewLayout(View view) { sharedElementsToRestart, LayoutAnimations.Types.SHARED_ELEMENT_TRANSITION); } - private boolean tryStartSharedTransitionForViews( - List sharedViews, boolean withNewElements) { + protected boolean prepareSharedTransition(List sharedViews, boolean withNewElements) { if (sharedViews.isEmpty()) { return false; } @@ -155,11 +189,43 @@ private boolean tryStartSharedTransitionForViews( setupTransitionContainer(); reparentSharedViewsForCurrentTransition(sharedElements); orderByAnimationTypes(sharedElements); + return true; + } + + protected void onScreenWillDisappear() { + if (!mIsTransitionPrepared) { + return; + } + mIsTransitionPrepared = false; + for (SharedElement sharedElement : mSharedElementsWithAnimation) { + sharedElement.targetViewSnapshot = new Snapshot(sharedElement.targetView); + } + for (SharedElement sharedElement : mSharedElementsWithProgress) { + sharedElement.targetViewSnapshot = new Snapshot(sharedElement.targetView); + } + + startPreparedTransitions(); + + for (Integer tag : mTagsToCleanup) { + mNativeMethodsHolder.clearAnimationConfig(tag); + } + mTagsToCleanup.clear(); + } + + private boolean tryStartSharedTransitionForViews( + List sharedViews, boolean withNewElements) { + if (!prepareSharedTransition(sharedViews, withNewElements)) { + return false; + } + startPreparedTransitions(); + return true; + } + + private void startPreparedTransitions() { startSharedTransition( mSharedElementsWithAnimation, LayoutAnimations.Types.SHARED_ELEMENT_TRANSITION); startSharedTransition( mSharedElementsWithProgress, LayoutAnimations.Types.SHARED_ELEMENT_TRANSITION_PROGRESS); - return true; } private void sortViewsByTags(List views) { @@ -334,14 +400,27 @@ private void reparentSharedViewsForCurrentTransition(List sharedE for (SharedElement sharedElement : sharedElements) { View viewSource = sharedElement.sourceView; if (!mSharedTransitionParent.containsKey(viewSource.getId())) { + var parent = (ViewGroup) viewSource.getParent(); + int parentTag = parent.getId(); + int childIndex = parent.indexOfChild(viewSource); mSharedTransitionParent.put(viewSource.getId(), (View) viewSource.getParent()); - mSharedTransitionInParentIndex.put( - viewSource.getId(), ((ViewGroup) viewSource.getParent()).indexOfChild(viewSource)); - ((ViewGroup) viewSource.getParent()).removeView(viewSource); - ((ViewGroup) mTransitionContainer).addView(viewSource); - mReattachedViews.add(viewSource); + mSharedTransitionInParentIndex.put(viewSource.getId(), childIndex); + var childrenIndicesSet = mSharedViewChildrenIndices.get(parentTag); + if (childrenIndicesSet == null) { + mSharedViewChildrenIndices.put( + parentTag, new TreeSet<>(Collections.singleton(childIndex))); + } else { + childrenIndicesSet.add(childIndex); + } } } + + for (SharedElement sharedElement : sharedElements) { + View viewSource = sharedElement.sourceView; + ((ViewGroup) viewSource.getParent()).removeView(viewSource); + ((ViewGroup) mTransitionContainer).addView(viewSource); + mReattachedViews.add(viewSource); + } } private void startSharedTransition(List sharedElements, int type) { @@ -388,6 +467,15 @@ protected void finishSharedAnimation(int tag) { View parentView = mSharedTransitionParent.get(viewTag); int childIndex = mSharedTransitionInParentIndex.get(viewTag); ViewGroup parentViewGroup = ((ViewGroup) parentView); + int parentTag = parentViewGroup.getId(); + var childIndicesSet = mSharedViewChildrenIndices.get(parentTag); + // here we calculate how many children with smaller indices have not been reinserted yet + int childIndexOffset = childIndicesSet.headSet(childIndex).size(); + childIndicesSet.remove(childIndex); + if (childIndicesSet.isEmpty()) { + mSharedViewChildrenIndices.remove(parentTag); + } + childIndex -= childIndexOffset; if (childIndex <= parentViewGroup.getChildCount()) { parentViewGroup.addView(view, childIndex); } else { @@ -498,9 +586,9 @@ public void run(View view) { } } - class ConfigCleanerTreeVisitor implements TreeVisitor { + class PrepareConfigCleanupTreeVisitor implements TreeVisitor { public void run(View view) { - mNativeMethodsHolder.clearAnimationConfig(view.getId()); + mTagsToCleanup.add(view.getId()); } } diff --git a/app/src/examples/SharedElementTransitions/ChangeTheme.tsx b/app/src/examples/SharedElementTransitions/ChangeTheme.tsx new file mode 100644 index 000000000000..438994bd6f13 --- /dev/null +++ b/app/src/examples/SharedElementTransitions/ChangeTheme.tsx @@ -0,0 +1,358 @@ +'use strict'; +import React, { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; + +import Animated, { + Layout, + SharedTransition, + SharedTransitionType, + withSpring, +} from 'react-native-reanimated'; +import { Button, StyleSheet, View, Text } from 'react-native'; +import { + NativeStackScreenProps, + createNativeStackNavigator, +} from '@react-navigation/native-stack'; + +import { ParamListBase } from '@react-navigation/native'; + +const Stack = createNativeStackNavigator(); +const Context = createContext({ + theme: true, + disabled: false, + modals: false, + toggleTheme: () => {}, + toggleDisabled: () => {}, + toggleModals: () => {}, +}); + +function getTheme(theme: boolean, disabled: boolean) { + const style = theme + ? { backgroundColor: 'purple' } + : { backgroundColor: 'pink' }; + const config = { duration: 1300 }; + const customTransition = SharedTransition.custom((values) => { + 'worklet'; + return { + width: withSpring(values.targetWidth, config), + height: withSpring(values.targetHeight, config), + originX: withSpring(values.targetOriginX, config), + originY: withSpring(values.targetOriginY, config), + borderRadius: withSpring(values.targetBorderRadius, config), + }; + }) + .progressAnimation((values, progress) => { + 'worklet'; + const getValue = ( + progress: number, + target: number, + current: number + ): number => { + return ( + (2 * progress * progress - progress) * (target - current) + current + ); + }; + return { + width: getValue(progress, values.targetWidth, values.currentWidth), + height: getValue(progress, values.targetHeight, values.currentHeight), + originX: getValue( + progress, + values.targetOriginX, + values.currentOriginX + ), + originY: getValue( + progress, + values.targetOriginY, + values.currentOriginY + ), + borderRadius: getValue( + progress, + values.targetBorderRadius, + values.currentBorderRadius + ), + }; + }) + .defaultTransitionType(SharedTransitionType.ANIMATION); + const transition = disabled + ? undefined + : theme + ? customTransition + : new SharedTransition(); + + return { style, transition }; +} + +function Screen1({ navigation }: NativeStackScreenProps) { + const { theme, disabled, modals, toggleTheme, toggleDisabled, toggleModals } = + useContext(Context); + const { style, transition } = useMemo( + () => getTheme(theme, disabled), + [theme, disabled] + ); + return ( + + Current theme: {theme ? 1 : 2} +