From eb12cb5bbc56f8c66d36933c42d96dd2e868df8c Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Thu, 14 May 2020 12:35:46 -0700 Subject: [PATCH] Support interpolating `transform` View property Summary: For Fabric LayoutAnimations, we need to support interpolating the Transform property (which really ends up just being interpolation of ScaleX, ScaleY, or ScaleXY transforms - not arbitrary matrices). To support that, we need to be able to convert Transform back to folly::dynamic, and on the Java side we need to support accepting arbitrary matrices instead of transform maps of properties. Changelog: [Internal] Fabric-only changes Reviewed By: sammy-SC Differential Revision: D21564590 fbshipit-source-id: b137f659b27e4b8fae83921a28ccf46035e18651 --- .../react/uimanager/TransformHelper.java | 11 +++++ .../components/view/ViewComponentDescriptor.h | 14 ++++-- ReactCommon/fabric/graphics/Transform.cpp | 12 +++++ ReactCommon/fabric/graphics/Transform.h | 45 +++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java index 16545dd1eac..df87f5b98f1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java @@ -48,6 +48,17 @@ public class TransformHelper { double[] helperMatrix = sHelperMatrix.get(); MatrixMathHelper.resetIdentityMatrix(result); + // If the transforms array is actually just the matrix itself, + // copy that directly. This is for Fabric LayoutAnimations support. + // All of the stuff this Java helper does is already done in C++ in Fabric, so we + // can just use that matrix directly. + if (transforms.getType(0) == ReadableType.Number && transforms.size() == 16) { + for (int i = 0; i < transforms.size(); i++) { + result[i] = transforms.getDouble(i); + } + return; + } + for (int transformIdx = 0, size = transforms.size(); transformIdx < size; transformIdx++) { ReadableMap transform = transforms.getMap(transformIdx); String transformType = transform.keySetIterator().nextKey(); diff --git a/ReactCommon/fabric/components/view/ViewComponentDescriptor.h b/ReactCommon/fabric/components/view/ViewComponentDescriptor.h index 3dd91fd4ea2..9eaacaaa77c 100644 --- a/ReactCommon/fabric/components/view/ViewComponentDescriptor.h +++ b/ReactCommon/fabric/components/view/ViewComponentDescriptor.h @@ -28,13 +28,16 @@ class ViewComponentDescriptor dynamic_cast(props.get()); ViewProps const *newViewProps = dynamic_cast(newProps.get()); - float opacity = oldViewProps->opacity + - (newViewProps->opacity - oldViewProps->opacity) * animationProgress; SharedProps interpolatedPropsShared = cloneProps(newProps, {}); ViewProps *interpolatedProps = const_cast( dynamic_cast(interpolatedPropsShared.get())); - interpolatedProps->opacity = opacity; + + interpolatedProps->opacity = oldViewProps->opacity + + (newViewProps->opacity - oldViewProps->opacity) * animationProgress; + + interpolatedProps->transform = Transform::Interpolate( + animationProgress, oldViewProps->transform, newViewProps->transform); // Android uses RawProps, not props, to update props on the platform... // Since interpolated props don't interpolate at all using RawProps, we need @@ -44,7 +47,10 @@ class ViewComponentDescriptor // mounting layer. Once we can remove this, we should change `rawProps` to // be const again. #ifdef ANDROID - interpolatedProps->rawProps["opacity"] = opacity; + interpolatedProps->rawProps["opacity"] = interpolatedProps->opacity; + + interpolatedProps->rawProps["transform"] = + (folly::dynamic)interpolatedProps->transform; #endif return interpolatedPropsShared; diff --git a/ReactCommon/fabric/graphics/Transform.cpp b/ReactCommon/fabric/graphics/Transform.cpp index 7293e03312b..46575fec31c 100644 --- a/ReactCommon/fabric/graphics/Transform.cpp +++ b/ReactCommon/fabric/graphics/Transform.cpp @@ -86,6 +86,18 @@ Transform Transform::Rotate(Float x, Float y, Float z) { return transform; } +Transform Transform::Interpolate( + float animationProgress, + Transform const &lhs, + Transform const &rhs) { + auto result = Transform{}; + for (size_t i = 0; i < 16; i++) { + result.matrix[i] = + lhs.matrix[i] + (rhs.matrix[i] - lhs.matrix[i]) * animationProgress; + } + return result; +} + bool Transform::operator==(Transform const &rhs) const { for (auto i = 0; i < 16; i++) { if (matrix[i] != rhs.matrix[i]) { diff --git a/ReactCommon/fabric/graphics/Transform.h b/ReactCommon/fabric/graphics/Transform.h index 49d8c095fd3..18e568f5471 100644 --- a/ReactCommon/fabric/graphics/Transform.h +++ b/ReactCommon/fabric/graphics/Transform.h @@ -13,6 +13,10 @@ #include #include +#ifdef ANDROID +#include +#endif + namespace facebook { namespace react { @@ -56,6 +60,22 @@ struct Transform { static Transform RotateZ(Float angle); static Transform Rotate(Float angleX, Float angleY, Float angleZ); + /** + * Perform a simple interpolation between lhs and rhs, given "progress" + * between the two assuming that we are "moving" from lhs to rhs. This is a + * simple linear interpolation between each matrix index and will only work + * for simple scaling or translation; this will not work for rotation. + * + * @param progress + * @param lhs + * @param rhs + * @return + */ + static Transform Interpolate( + float animationProgress, + Transform const &lhs, + Transform const &rhs); + /* * Equality operators. */ @@ -72,6 +92,31 @@ struct Transform { * Concatenates (multiplies) transform matrices. */ Transform operator*(Transform const &rhs) const; + + /** + * Convert to folly::dynamic. + */ +#ifdef ANDROID + operator folly::dynamic() const { + return folly::dynamic::array( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + matrix[6], + matrix[7], + matrix[8], + matrix[9], + matrix[10], + matrix[11], + matrix[12], + matrix[13], + matrix[14], + matrix[15]); + } +#endif }; /*