diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index 0880a7f372f..a1bfe008c78 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -316,6 +316,11 @@ bool LayoutAnimationKeyFrameManager::shouldOverridePullTransaction() const { return shouldAnimateFrame(); } +void LayoutAnimationKeyFrameManager::stopSurface(SurfaceId surfaceId) { + std::lock_guard lock(surfaceIdsToStopMutex_); + surfaceIdsToStop_.push_back(surfaceId); +} + bool LayoutAnimationKeyFrameManager::shouldAnimateFrame() const { // There is potentially a race here between getting and setting // `currentMutation_`. We don't want to lock around this because then we're @@ -780,6 +785,35 @@ LayoutAnimationKeyFrameManager::pullTransaction( bool inflightAnimationsExistInitially = !inflightAnimations_.empty(); + // Execute stopSurface on any ongoing animations + if (inflightAnimationsExistInitially) { + std::vector surfaceIdsToStop{}; + { + std::lock_guard lock(surfaceIdsToStopMutex_); + surfaceIdsToStop = surfaceIdsToStop_; + surfaceIdsToStop_ = {}; + } + + for (auto it = inflightAnimations_.begin(); + it != inflightAnimations_.end();) { + const auto &animation = *it; + + if (std::find( + surfaceIdsToStop.begin(), + surfaceIdsToStop.end(), + animation.surfaceId) != surfaceIdsToStop.end()) { +#ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING + LOG(ERROR) + << "LayoutAnimations: stopping animation due to stopSurface on " + << surfaceId; +#endif + it = inflightAnimations_.erase(it); + } else { + it++; + } + } + } + if (!mutations.empty()) { #ifdef RN_SHADOW_TREE_INTROSPECTION { diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h index 0ffd0530427..1a67e5230a9 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h @@ -184,6 +184,8 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, bool shouldOverridePullTransaction() const override; + void stopSurface(SurfaceId surfaceId) override; + // This is used to "hijack" the diffing process to figure out which mutations // should be animated. The mutations returned by this function will be // executed immediately. @@ -227,6 +229,9 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, ShadowViewMutationList &mutations, bool deletesOnly = false) const; + mutable std::mutex surfaceIdsToStopMutex_; + mutable std::vector surfaceIdsToStop_{}; + protected: bool mutatedViewIsVirtual(ShadowViewMutation const &mutation) const; diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 58128870bd0..4cefd596a70 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -246,6 +246,9 @@ void Scheduler::renderTemplateToSurface( void Scheduler::stopSurface(SurfaceId surfaceId) const { SystraceSection s("Scheduler::stopSurface"); + // Stop any ongoing animations. + uiManager_->stopSurfaceForAnimationDelegate(surfaceId); + // Note, we have to do in inside `visit` function while the Shadow Tree // is still being registered. uiManager_->getShadowTreeRegistry().visit( diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp index cb0cccb1676..701e9e83ee4 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -336,6 +336,12 @@ void UIManager::setAnimationDelegate(UIManagerAnimationDelegate *delegate) { animationDelegate_ = delegate; } +void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) { + if (animationDelegate_ != nullptr) { + animationDelegate_->stopSurface(surfaceId); + } +} + void UIManager::animationTick() { if (animationDelegate_ != nullptr && animationDelegate_->shouldAnimateFrame()) { diff --git a/ReactCommon/react/renderer/uimanager/UIManager.h b/ReactCommon/react/renderer/uimanager/UIManager.h index 4c9688537be..1b3f5d73aa5 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/ReactCommon/react/renderer/uimanager/UIManager.h @@ -50,6 +50,11 @@ class UIManager final : public ShadowTreeDelegate { */ void setAnimationDelegate(UIManagerAnimationDelegate *delegate); + /** + * Execute stopSurface on any UIMAnagerAnimationDelegate. + */ + void stopSurfaceForAnimationDelegate(SurfaceId surfaceId); + void animationTick(); /* diff --git a/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h b/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h index 3dcb549f14d..cfbb0c00e55 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h +++ b/ReactCommon/react/renderer/uimanager/UIManagerAnimationDelegate.h @@ -41,6 +41,11 @@ class UIManagerAnimationDelegate { * Only needed on Android to drive animations. */ virtual bool shouldAnimateFrame() const = 0; + + /** + * Drop any animations for a given surface. + */ + virtual void stopSurface(SurfaceId surfaceId) = 0; }; } // namespace react