diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 249e81199a5..0326ec9afd6 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -165,6 +165,7 @@ const validAttributesForNonEventProps = { // @ReactProps from BaseViewManager backgroundColor: {process: require('../StyleSheet/processColor').default}, transform: true, + transformOrigin: true, opacity: true, elevation: true, shadowColor: {process: require('../StyleSheet/processColor').default}, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index a7970346275..20c3fd2d7fc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -40,7 +40,7 @@ import java.util.Map; * provides support for base view properties such as backgroundColor, opacity, etc. */ public abstract class BaseViewManager - extends ViewManager implements BaseViewManagerInterface { + extends ViewManager implements BaseViewManagerInterface, View.OnLayoutChangeListener { private static final int PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX = 2; private static final float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = (float) Math.sqrt(5); @@ -90,6 +90,10 @@ public abstract class BaseViewManager= Build.VERSION_CODES.P) { view.setOutlineAmbientShadowColor(Color.BLACK); @@ -129,6 +133,35 @@ public abstract class BaseViewManager { void setTransform(T view, @Nullable ReadableArray matrix); + void setTransformOrigin(T view, @Nullable ReadableArray transformOrigin); + void setTranslateX(T view, float translateX); void setTranslateY(T view, float translateY); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java index 49d6af12396..5a5a9ddaa92 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java @@ -46,65 +46,121 @@ public class TransformHelper { } public static void processTransform(ReadableArray transforms, double[] result) { + processTransform(transforms, result, 0, 0, null); + } + + public static void processTransform( + ReadableArray transforms, + double[] result, + float viewWidth, + float viewHeight, + ReadableArray transformOrigin) { double[] helperMatrix = sHelperMatrix.get(); MatrixMathHelper.resetIdentityMatrix(result); + float[] offsets = getTranslateForTransformOrigin(viewWidth, viewHeight, transformOrigin); + if (offsets != null) { + MatrixMathHelper.resetIdentityMatrix(helperMatrix); + MatrixMathHelper.applyTranslate3D(helperMatrix, offsets[0], offsets[1], offsets[2]); + MatrixMathHelper.multiplyInto(result, result, helperMatrix); + } // 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.size() == 16 && transforms.getType(0) == ReadableType.Number) { + MatrixMathHelper.resetIdentityMatrix(helperMatrix); for (int i = 0; i < transforms.size(); i++) { - result[i] = transforms.getDouble(i); + helperMatrix[i] = transforms.getDouble(i); + } + MatrixMathHelper.multiplyInto(result, result, helperMatrix); + } else { + for (int transformIdx = 0, size = transforms.size(); transformIdx < size; transformIdx++) { + ReadableMap transform = transforms.getMap(transformIdx); + String transformType = transform.keySetIterator().nextKey(); + + MatrixMathHelper.resetIdentityMatrix(helperMatrix); + if ("matrix".equals(transformType)) { + ReadableArray matrix = transform.getArray(transformType); + for (int i = 0; i < 16; i++) { + helperMatrix[i] = matrix.getDouble(i); + } + } else if ("perspective".equals(transformType)) { + MatrixMathHelper.applyPerspective(helperMatrix, transform.getDouble(transformType)); + } else if ("rotateX".equals(transformType)) { + MatrixMathHelper.applyRotateX(helperMatrix, convertToRadians(transform, transformType)); + } else if ("rotateY".equals(transformType)) { + MatrixMathHelper.applyRotateY(helperMatrix, convertToRadians(transform, transformType)); + } else if ("rotate".equals(transformType) || "rotateZ".equals(transformType)) { + MatrixMathHelper.applyRotateZ(helperMatrix, convertToRadians(transform, transformType)); + } else if ("scale".equals(transformType)) { + double scale = transform.getDouble(transformType); + MatrixMathHelper.applyScaleX(helperMatrix, scale); + MatrixMathHelper.applyScaleY(helperMatrix, scale); + } else if ("scaleX".equals(transformType)) { + MatrixMathHelper.applyScaleX(helperMatrix, transform.getDouble(transformType)); + } else if ("scaleY".equals(transformType)) { + MatrixMathHelper.applyScaleY(helperMatrix, transform.getDouble(transformType)); + } else if ("translate".equals(transformType)) { + ReadableArray value = transform.getArray(transformType); + double x = value.getDouble(0); + double y = value.getDouble(1); + double z = value.size() > 2 ? value.getDouble(2) : 0d; + MatrixMathHelper.applyTranslate3D(helperMatrix, x, y, z); + } else if ("translateX".equals(transformType)) { + MatrixMathHelper.applyTranslate2D(helperMatrix, transform.getDouble(transformType), 0d); + } else if ("translateY".equals(transformType)) { + MatrixMathHelper.applyTranslate2D(helperMatrix, 0d, transform.getDouble(transformType)); + } else if ("skewX".equals(transformType)) { + MatrixMathHelper.applySkewX(helperMatrix, convertToRadians(transform, transformType)); + } else if ("skewY".equals(transformType)) { + MatrixMathHelper.applySkewY(helperMatrix, convertToRadians(transform, transformType)); + } else { + FLog.w(ReactConstants.TAG, "Unsupported transform type: " + transformType); + } + + MatrixMathHelper.multiplyInto(result, result, helperMatrix); } - return; } - for (int transformIdx = 0, size = transforms.size(); transformIdx < size; transformIdx++) { - ReadableMap transform = transforms.getMap(transformIdx); - String transformType = transform.keySetIterator().nextKey(); - + if (offsets != null) { MatrixMathHelper.resetIdentityMatrix(helperMatrix); - if ("matrix".equals(transformType)) { - ReadableArray matrix = transform.getArray(transformType); - for (int i = 0; i < 16; i++) { - helperMatrix[i] = matrix.getDouble(i); - } - } else if ("perspective".equals(transformType)) { - MatrixMathHelper.applyPerspective(helperMatrix, transform.getDouble(transformType)); - } else if ("rotateX".equals(transformType)) { - MatrixMathHelper.applyRotateX(helperMatrix, convertToRadians(transform, transformType)); - } else if ("rotateY".equals(transformType)) { - MatrixMathHelper.applyRotateY(helperMatrix, convertToRadians(transform, transformType)); - } else if ("rotate".equals(transformType) || "rotateZ".equals(transformType)) { - MatrixMathHelper.applyRotateZ(helperMatrix, convertToRadians(transform, transformType)); - } else if ("scale".equals(transformType)) { - double scale = transform.getDouble(transformType); - MatrixMathHelper.applyScaleX(helperMatrix, scale); - MatrixMathHelper.applyScaleY(helperMatrix, scale); - } else if ("scaleX".equals(transformType)) { - MatrixMathHelper.applyScaleX(helperMatrix, transform.getDouble(transformType)); - } else if ("scaleY".equals(transformType)) { - MatrixMathHelper.applyScaleY(helperMatrix, transform.getDouble(transformType)); - } else if ("translate".equals(transformType)) { - ReadableArray value = transform.getArray(transformType); - double x = value.getDouble(0); - double y = value.getDouble(1); - double z = value.size() > 2 ? value.getDouble(2) : 0d; - MatrixMathHelper.applyTranslate3D(helperMatrix, x, y, z); - } else if ("translateX".equals(transformType)) { - MatrixMathHelper.applyTranslate2D(helperMatrix, transform.getDouble(transformType), 0d); - } else if ("translateY".equals(transformType)) { - MatrixMathHelper.applyTranslate2D(helperMatrix, 0d, transform.getDouble(transformType)); - } else if ("skewX".equals(transformType)) { - MatrixMathHelper.applySkewX(helperMatrix, convertToRadians(transform, transformType)); - } else if ("skewY".equals(transformType)) { - MatrixMathHelper.applySkewY(helperMatrix, convertToRadians(transform, transformType)); - } else { - FLog.w(ReactConstants.TAG, "Unsupported transform type: " + transformType); - } - + MatrixMathHelper.applyTranslate3D(helperMatrix, -offsets[0], -offsets[1], -offsets[2]); MatrixMathHelper.multiplyInto(result, result, helperMatrix); } } + + private static float[] getTranslateForTransformOrigin( + float viewWidth, float viewHeight, ReadableArray transformOrigin) { + if (transformOrigin == null || (viewHeight == 0 && viewWidth == 0)) { + return null; + } + float viewCenterX = viewWidth / 2; + float viewCenterY = viewHeight / 2; + + float[] origin = {viewCenterX, viewCenterY, 0.0f}; + + for (int i = 0; i < transformOrigin.size() && i < 3; i++) { + switch (transformOrigin.getType(i)) { + case Number: + origin[i] = (float) transformOrigin.getDouble(i); + break; + case String: + { + String part = transformOrigin.getString(i); + if (part.endsWith("%")) { + float val = Float.parseFloat(part.substring(0, part.length() - 1)); + origin[i] = (i == 0 ? viewWidth : viewHeight) * val / 100.0f; + } + break; + } + } + } + + float newTranslateX = -viewCenterX + origin[0]; + float newTranslateY = -viewCenterY + origin[1]; + float newTranslateZ = origin[2]; + + return new float[] {newTranslateX, newTranslateY, newTranslateZ}; + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index cc9f7178e65..fa6eae38a49 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -151,6 +151,8 @@ public class ViewProps { public static final String ON_LAYOUT = "onLayout"; public static final String TRANSFORM = "transform"; + + public static final String TRANSFORM_ORIGIN = "transformOrigin"; public static final String ELEVATION = "elevation"; public static final String SHADOW_COLOR = "shadowColor"; public static final String Z_INDEX = "zIndex"; diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml index d2928f810df..84ebee04669 100644 --- a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -45,6 +45,15 @@ + + + + + + + + +