diff --git a/ReactCommon/fabric/animations/LayoutAnimationDriver.h b/ReactCommon/fabric/animations/LayoutAnimationDriver.h index 2a221577529..3aa04f99163 100644 --- a/ReactCommon/fabric/animations/LayoutAnimationDriver.h +++ b/ReactCommon/fabric/animations/LayoutAnimationDriver.h @@ -23,6 +23,9 @@ namespace react { class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager { public: + LayoutAnimationDriver(LayoutAnimationStatusDelegate *delegate) + : LayoutAnimationKeyFrameManager(delegate) {} + virtual ~LayoutAnimationDriver() {} protected: diff --git a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp index 034d871388b..12478e15569 100644 --- a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp @@ -212,6 +212,12 @@ void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation( } } +void LayoutAnimationKeyFrameManager::setLayoutAnimationStatusDelegate( + LayoutAnimationStatusDelegate *delegate) const { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + layoutAnimationStatusDelegate_ = delegate; +} + bool LayoutAnimationKeyFrameManager::shouldOverridePullTransaction() const { return shouldAnimateFrame(); } @@ -341,6 +347,8 @@ LayoutAnimationKeyFrameManager::pullTransaction( std::chrono::high_resolution_clock::now().time_since_epoch()) .count(); + bool inflightAnimationsExistInitially = !inflightAnimations_.empty(); + if (!mutations.empty()) { #ifdef RN_SHADOW_TREE_INTROSPECTION { @@ -817,6 +825,21 @@ LayoutAnimationKeyFrameManager::pullTransaction( mutationsForAnimation.begin(), mutationsForAnimation.end()); + // Signal to delegate if all animations are complete, or if we were not + // animating anything and now some animation exists. + if (inflightAnimationsExistInitially && inflightAnimations_.empty()) { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + if (layoutAnimationStatusDelegate_ != nullptr) { + layoutAnimationStatusDelegate_->onAllAnimationsComplete(); + } + } else if ( + !inflightAnimationsExistInitially && !inflightAnimations_.empty()) { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + if (layoutAnimationStatusDelegate_ != nullptr) { + layoutAnimationStatusDelegate_->onAnimationStarted(); + } + } + // TODO: fill in telemetry return MountingTransaction{ surfaceId, transactionNumber, std::move(mutations), {}}; diff --git a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h index f286bd27a38..4f2a766113a 100644 --- a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace facebook { @@ -37,7 +38,7 @@ enum class AnimationProperty { }; enum class AnimationConfigurationType { Noop, // for animation placeholders that are not animated, and should be - // executed once other animations have completed + // executed once other animations have completed Create, Update, Delete @@ -97,6 +98,12 @@ struct LayoutAnimation { class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, public MountingOverrideDelegate { public: + LayoutAnimationKeyFrameManager(LayoutAnimationStatusDelegate *delegate) + : layoutAnimationStatusDelegate_(delegate) { + // This is the ONLY place where we set or access + // layoutAnimationStatusDelegate_ without a mutex. + } + void uiManagerDidConfigureNextLayoutAnimation( RawValue const &config, std::shared_ptr successCallback, @@ -118,7 +125,19 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, MountingTelemetry const &telemetry, ShadowViewMutationList mutations) const override; + // LayoutAnimationStatusDelegate - this is for the platform to get + // signal when animations start and complete. Setting and resetting this + // delegate is protected by a mutex; ALL method calls into this delegate are + // also protected by the mutex! The only way to set this without a mutex is + // via a constructor. + public: + void setLayoutAnimationStatusDelegate( + LayoutAnimationStatusDelegate *delegate) const; + private: + mutable std::mutex layoutAnimationStatusDelegateMutex_; + mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{}; + void adjustDelayedMutationIndicesForMutation( SurfaceId surfaceId, ShadowViewMutation const &mutation) const; diff --git a/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h b/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h new file mode 100644 index 00000000000..b87b96f68f7 --- /dev/null +++ b/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +class LayoutAnimationStatusDelegate { + public: + /** + * Called when the LayoutAnimation engine state changes from animation nothing + * to animating something. This will only be called when you go from 0 to N>0 + * active animations, N to N+1 animations will not result in this being + * called. + */ + virtual void onAnimationStarted() = 0; + + /** + * Called when the LayoutAnimation engine completes all pending animations. + */ + virtual void onAllAnimationsComplete() = 0; +}; + +} // namespace react +} // namespace facebook