From 83719e5af2576aa7f404adbccd5c7530e95cbe2b Mon Sep 17 00:00:00 2001 From: Nishan Date: Sun, 23 Jul 2023 15:09:28 +0530 Subject: [PATCH 1/7] feat: android transform origin --- .../View/ReactNativeStyleAttributes.js | 1 + .../NativeComponent/BaseViewConfig.android.js | 1 + .../react/uimanager/BaseViewManager.java | 52 +++++++++++++-- .../uimanager/BaseViewManagerDelegate.java | 3 + .../uimanager/BaseViewManagerInterface.java | 2 + .../react/uimanager/TransformHelper.java | 63 ++++++++++++++++--- .../facebook/react/uimanager/ViewProps.java | 2 + .../main/res/views/uimanager/values/ids.xml | 6 ++ .../js/examples/Transform/TransformExample.js | 51 ++++++++++++++- 9 files changed, 169 insertions(+), 12 deletions(-) diff --git a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js index 13d8db56a5fe67..a170e0b7ea3f65 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -111,6 +111,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { * Transform */ transform: {process: processTransform}, + transformOrigin: true, /** * View diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index f0dfd8d6628105..998635439075b3 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 a7970346275fd7..ec4f3e00756616 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 @@ * 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,9 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie view.setElevation(0); view.setAnimationMatrix(null); + view.setTag(R.id.transform, null); + view.setTag(R.id.transform_origin, null); + view.removeOnLayoutChangeListener(this); // setShadowColor if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { view.setOutlineAmbientShadowColor(Color.BLACK); @@ -129,6 +132,33 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie return view; } + @Override + public void onLayoutChange(View v, + int left, + int top, + int right, + int bottom, + int oldLeft, + int oldTop, + int oldRight, + int oldBottom) { + // Old width and height + int oldWidth = oldRight - oldLeft; + int oldHeight = oldBottom - oldTop; + + // Current width and height + int currentWidth = right - left; + int currentHeight = bottom - top; + + if ((currentHeight != oldHeight || currentWidth != oldWidth)) { + String transformOrigin = (String) v.getTag(R.id.transform_origin); + ReadableArray transformMatrix = (ReadableArray) v.getTag(R.id.transform); + if (transformMatrix != null && transformOrigin != null) { + setTransformProperty((T) v, transformMatrix, transformOrigin); + } + } + } + @Override @ReactProp( name = ViewProps.BACKGROUND_COLOR, @@ -141,11 +171,24 @@ public void setBackgroundColor(@NonNull T view, int backgroundColor) { @Override @ReactProp(name = ViewProps.TRANSFORM) public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) { + view.setTag(R.id.transform, matrix); if (matrix == null) { resetTransformProperty(view); } else { - setTransformProperty(view, matrix); + String transformOrigin = (String) view.getTag(R.id.transform_origin); + setTransformProperty(view, matrix, transformOrigin); + } + } + + @Override + @ReactProp(name = ViewProps.TRANSFORM_ORIGIN) + public void setTransformOrigin(@NonNull T view, @Nullable String transformOrigin) { + view.setTag(R.id.transform_origin, transformOrigin); + ReadableArray transformMatrix = (ReadableArray) view.getTag(R.id.transform); + if (transformMatrix != null) { + setTransformProperty(view, transformMatrix, transformOrigin); } + view.addOnLayoutChangeListener(this); } @Override @@ -439,9 +482,10 @@ public void setAccessibilityLiveRegion(@NonNull T view, @Nullable String liveReg } } - private static void setTransformProperty(@NonNull View view, ReadableArray transforms) { + private static void setTransformProperty(@NonNull View view, ReadableArray transforms, String transformOrigin) { sMatrixDecompositionContext.reset(); - TransformHelper.processTransform(transforms, sTransformDecompositionArray); + TransformHelper.processTransform(transforms, sTransformDecompositionArray, + PixelUtil.toDIPFromPixel(view.getWidth()), PixelUtil.toDIPFromPixel(view.getHeight()), transformOrigin); MatrixMathHelper.decomposeMatrix(sTransformDecompositionArray, sMatrixDecompositionContext); view.setTranslationX( PixelUtil.toPixelFromDIP( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java index e59a1c4da3b80f..341142b560cef2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java @@ -121,6 +121,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case ViewProps.TRANSFORM: mViewManager.setTransform(view, (ReadableArray) value); break; + case ViewProps.TRANSFORM_ORIGIN: + mViewManager.setTransformOrigin(view, (String) value); + break; case ViewProps.TRANSLATE_X: mViewManager.setTranslateX(view, value == null ? 0.0f : ((Double) value).floatValue()); break; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java index 5887ff5ba31535..b609d285a8844b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java @@ -72,6 +72,8 @@ public interface BaseViewManagerInterface { void setTransform(T view, @Nullable ReadableArray matrix); + void setTransformOrigin(T view, @Nullable String 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 49d6af12396a01..e0cbb7dfb6f8f4 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 @@ -20,12 +20,12 @@ public class TransformHelper { private static ThreadLocal sHelperMatrix = - new ThreadLocal() { - @Override - protected double[] initialValue() { - return new double[16]; - } - }; + new ThreadLocal() { + @Override + protected double[] initialValue() { + return new double[16]; + } + }; private static double convertToRadians(ReadableMap transformMap, String key) { double value; @@ -45,7 +45,7 @@ private static double convertToRadians(ReadableMap transformMap, String key) { return inRadians ? value : MatrixMathHelper.degreesToRadians(value); } - public static void processTransform(ReadableArray transforms, double[] result) { + public static void processTransform(ReadableArray transforms, double[] result, float viewWidth, float viewHeight, String transformOrigin) { double[] helperMatrix = sHelperMatrix.get(); MatrixMathHelper.resetIdentityMatrix(result); @@ -60,6 +60,13 @@ public static void processTransform(ReadableArray transforms, double[] result) { return; } + 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); + } + for (int transformIdx = 0, size = transforms.size(); transformIdx < size; transformIdx++) { ReadableMap transform = transforms.getMap(transformIdx); String transformType = transform.keySetIterator().nextKey(); @@ -106,5 +113,47 @@ public static void processTransform(ReadableArray transforms, double[] result) { MatrixMathHelper.multiplyInto(result, result, helperMatrix); } + if (offsets != null) { + MatrixMathHelper.resetIdentityMatrix(helperMatrix); + MatrixMathHelper.applyTranslate3D(helperMatrix, -offsets[0], -offsets[1], -offsets[2]); + MatrixMathHelper.multiplyInto(result, result, helperMatrix); + } + } + + public static float[] getTranslateForTransformOrigin(float viewWidth, float viewHeight, String transformOrigin) { + if (transformOrigin == null || (viewHeight == 0 && viewWidth == 0)) { + return null; + } + float viewCenterX = viewWidth / 2; + float viewCenterY = viewHeight / 2; + + float[] origin = {viewCenterX, viewCenterY, 0.0f}; + + String[] parts = transformOrigin.split(" "); + for (int i = 0; i < parts.length && i < 3; i++) { + String part = parts[i]; + if (part.endsWith("%")) { + float val = Float.parseFloat(part.substring(0, part.length() - 1)); + origin[i] = (i == 0 ? viewWidth : viewHeight) * val / 100.0f; + } else if (part.equals("top")) { + origin[1] = 0.0f; + } else if (part.equals("bottom")) { + origin[1] = viewHeight; + } else if (part.equals("left")) { + origin[0] = 0.0f; + } else if (part.equals("right")) { + origin[0] = viewWidth; + } else if (part.equals("center")) { + continue; + } else { + origin[i] = Float.parseFloat(part); + } + } + + 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 cc9f7178e65919..fa6eae38a49771 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 d2928f810dfb90..90c887f805800e 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,12 @@ + + + + + + diff --git a/packages/rn-tester/js/examples/Transform/TransformExample.js b/packages/rn-tester/js/examples/Transform/TransformExample.js index 1358005c94c314..3da631db93a641 100644 --- a/packages/rn-tester/js/examples/Transform/TransformExample.js +++ b/packages/rn-tester/js/examples/Transform/TransformExample.js @@ -9,7 +9,7 @@ */ import React, {useEffect, useState} from 'react'; -import {Animated, StyleSheet, Text, View} from 'react-native'; +import {Animated, StyleSheet, Text, View, Easing} from 'react-native'; import type {Node, Element} from 'react'; @@ -50,6 +50,39 @@ function AnimateTransformSingleProp() { ); } +function TransformOriginExample() { + const rotateAnim = React.useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.loop( + Animated.timing(rotateAnim, { + toValue: 1, + duration: 5000, + easing: Easing.linear, + useNativeDriver: true, + }), + ).start(); + }, [rotateAnim]); + + const spin = rotateAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); + + return ( + + + + ); +} + function Flip() { const [theta] = useState(new Animated.Value(45)); const animate = () => { @@ -234,6 +267,15 @@ const styles = StyleSheet.create({ color: 'white', fontWeight: 'bold', }, + transformOriginWrapper: { + alignItems: 'center', + }, + transformOriginView: { + backgroundColor: 'pink', + width: 100, + height: 100, + transformOrigin: 'top left', + }, }); exports.title = 'Transforms'; @@ -346,4 +388,11 @@ exports.examples = [ ); }, }, + { + title: 'Transform origin', + description: "transformOrigin: 'top'", + render(): Node { + return ; + }, + }, ]; From 3ecc8ec9b324e3641a34862a4e7f1313052b1a27 Mon Sep 17 00:00:00 2001 From: Nishan Date: Wed, 26 Jul 2023 22:46:01 +0530 Subject: [PATCH 2/7] move process transform origin on to js --- .../View/ReactNativeStyleAttributes.js | 3 +- .../Libraries/StyleSheet/StyleSheetTypes.d.ts | 1 + .../processTransformOrigin-test.js.snap | 5 + .../__tests__/processTransformOrigin-test.js | 134 +++++++++++++++++ .../StyleSheet/private/_TransformStyle.js | 12 ++ .../StyleSheet/processTransformOrigin.js | 136 ++++++++++++++++++ .../Libraries/StyleSheet/splitLayoutProps.js | 1 + .../react/uimanager/BaseViewManager.java | 8 +- .../uimanager/BaseViewManagerDelegate.java | 2 +- .../uimanager/BaseViewManagerInterface.java | 2 +- .../react/uimanager/TransformHelper.java | 35 ++--- 11 files changed, 312 insertions(+), 27 deletions(-) create mode 100644 packages/react-native/Libraries/StyleSheet/__tests__/__snapshots__/processTransformOrigin-test.js.snap create mode 100644 packages/react-native/Libraries/StyleSheet/__tests__/processTransformOrigin-test.js create mode 100644 packages/react-native/Libraries/StyleSheet/processTransformOrigin.js diff --git a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js index a170e0b7ea3f65..79dbeddb741e39 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -14,6 +14,7 @@ import processAspectRatio from '../../StyleSheet/processAspectRatio'; import processColor from '../../StyleSheet/processColor'; import processFontVariant from '../../StyleSheet/processFontVariant'; import processTransform from '../../StyleSheet/processTransform'; +import processTransformOrigin from '../../StyleSheet/processTransformOrigin'; import sizesDiffer from '../../Utilities/differ/sizesDiffer'; const colorAttributes = {process: processColor}; @@ -111,7 +112,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { * Transform */ transform: {process: processTransform}, - transformOrigin: true, + transformOrigin: {process: processTransformOrigin}, /** * View diff --git a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts index 7956766b34367a..ef9c0f90c56ee1 100644 --- a/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts +++ b/packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts @@ -196,6 +196,7 @@ export interface TransformsStyle { >[] | string | undefined; + transformOrigin?: Array | string | undefined; /** * @deprecated Use matrix in transform prop instead. */ diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/__snapshots__/processTransformOrigin-test.js.snap b/packages/react-native/Libraries/StyleSheet/__tests__/__snapshots__/processTransformOrigin-test.js.snap new file mode 100644 index 00000000000000..8d6455d0b55cbc --- /dev/null +++ b/packages/react-native/Libraries/StyleSheet/__tests__/__snapshots__/processTransformOrigin-test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`processTransformOrigin validation only accepts three values 1`] = `"Transform origin must have exactly 3 values."`; + +exports[`processTransformOrigin validation only accepts three values 2`] = `"Transform origin must have exactly 3 values."`; \ No newline at end of file diff --git a/packages/react-native/Libraries/StyleSheet/__tests__/processTransformOrigin-test.js b/packages/react-native/Libraries/StyleSheet/__tests__/processTransformOrigin-test.js new file mode 100644 index 00000000000000..5a1b06d289cc5a --- /dev/null +++ b/packages/react-native/Libraries/StyleSheet/__tests__/processTransformOrigin-test.js @@ -0,0 +1,134 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @oncall react_native + */ + +import processTransformOrigin from '../processTransformOrigin'; + +describe('processTransformOrigin', () => { + describe('validation', () => { + it('only accepts three values', () => { + expect(() => { + processTransformOrigin([]); + }).toThrowErrorMatchingSnapshot(); + expect(() => { + processTransformOrigin(['50%', '50%']); + }).toThrowErrorMatchingSnapshot(); + }); + + it('should transform a string', () => { + expect(processTransformOrigin('50% 50% 5px')).toEqual(['50%', '50%', 5]); + }); + + it('should handle one value', () => { + expect(processTransformOrigin('top')).toEqual(['50%', 0, 0]); + expect(processTransformOrigin('right')).toEqual(['100%', '50%', 0]); + expect(processTransformOrigin('bottom')).toEqual(['50%', '100%', 0]); + expect(processTransformOrigin('left')).toEqual([0, '50%', 0]); + }); + + it('should handle two values', () => { + expect(processTransformOrigin('30% top')).toEqual(['30%', 0, 0]); + expect(processTransformOrigin('right 30%')).toEqual(['100%', '30%', 0]); + expect(processTransformOrigin('30% bottom')).toEqual(['30%', '100%', 0]); + expect(processTransformOrigin('left 30%')).toEqual([0, '30%', 0]); + }); + + it('should handle two keywords in either order', () => { + expect(processTransformOrigin('right bottom')).toEqual([ + '100%', + '100%', + 0, + ]); + expect(processTransformOrigin('bottom right')).toEqual([ + '100%', + '100%', + 0, + ]); + expect(processTransformOrigin('right bottom 5px')).toEqual([ + '100%', + '100%', + 5, + ]); + expect(processTransformOrigin('bottom right 5px')).toEqual([ + '100%', + '100%', + 5, + ]); + }); + + it('should not allow specifying same position twice', () => { + expect(() => { + processTransformOrigin('top top'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: top top"`, + ); + expect(() => { + processTransformOrigin('right right'); + }).toThrowErrorMatchingInlineSnapshot( + `"Transform-origin right can only be used for x-position"`, + ); + expect(() => { + processTransformOrigin('bottom bottom'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: bottom bottom"`, + ); + expect(() => { + processTransformOrigin('left left'); + }).toThrowErrorMatchingInlineSnapshot( + `"Transform-origin left can only be used for x-position"`, + ); + expect(() => { + processTransformOrigin('top bottom'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: top bottom"`, + ); + expect(() => { + processTransformOrigin('left right'); + }).toThrowErrorMatchingInlineSnapshot( + `"Transform-origin right can only be used for x-position"`, + ); + }); + + it('should handle three values', () => { + expect(processTransformOrigin('30% top 10px')).toEqual(['30%', 0, 10]); + expect(processTransformOrigin('right 30% 10px')).toEqual([ + '100%', + '30%', + 10, + ]); + expect(processTransformOrigin('30% bottom 10px')).toEqual([ + '30%', + '100%', + 10, + ]); + expect(processTransformOrigin('left 30% 10px')).toEqual([0, '30%', 10]); + }); + + it('should enforce two value ordering', () => { + expect(() => { + processTransformOrigin('top 30%'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: top 30%"`, + ); + }); + + it('should not allow percents for z-position', () => { + expect(() => { + processTransformOrigin('top 30% 30%'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: top 30% 30%"`, + ); + expect(() => { + processTransformOrigin('top 30% center'); + }).toThrowErrorMatchingInlineSnapshot( + `"Could not parse transform-origin: top 30% center"`, + ); + }); + }); +}); diff --git a/packages/react-native/Libraries/StyleSheet/private/_TransformStyle.js b/packages/react-native/Libraries/StyleSheet/private/_TransformStyle.js index 8fa2b271d1cf4e..8e08b0335738f4 100644 --- a/packages/react-native/Libraries/StyleSheet/private/_TransformStyle.js +++ b/packages/react-native/Libraries/StyleSheet/private/_TransformStyle.js @@ -52,4 +52,16 @@ export type ____TransformStyle_Internal = $ReadOnly<{| |}, > | string, + /** + * `transformOrigin` accepts an array with 3 elements - each element either being + * a number, or a string of a number ending with `%`. The last element cannot be + * a percentage, so must be a number. + * + * E.g. transformOrigin: ['30%', '80%', 15] + * + * Alternatively accepts a string of the CSS syntax. You must use `%` or `px`. + * + * E.g. transformOrigin: '30% 80% 15px' + */ + transformOrigin?: Array | string, |}>; diff --git a/packages/react-native/Libraries/StyleSheet/processTransformOrigin.js b/packages/react-native/Libraries/StyleSheet/processTransformOrigin.js new file mode 100644 index 00000000000000..5cad4f6ef57b5a --- /dev/null +++ b/packages/react-native/Libraries/StyleSheet/processTransformOrigin.js @@ -0,0 +1,136 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +import invariant from 'invariant'; + +const INDEX_X = 0; +const INDEX_Y = 1; +const INDEX_Z = 2; + +/* eslint-disable no-labels */ +export default function processTransformOrigin( + transformOrigin: Array | string, +): Array { + if (typeof transformOrigin === 'string') { + const transformOriginString = transformOrigin; + const regex = /(top|bottom|left|right|center|\d+(?:%|px)|0)/gi; + const transformOriginArray: Array = ['50%', '50%', 0]; + + let index = INDEX_X; + let matches; + outer: while ((matches = regex.exec(transformOriginString))) { + let nextIndex = index + 1; + + const value = matches[0]; + const valueLower = value.toLowerCase(); + + switch (valueLower) { + case 'left': + case 'right': { + invariant( + index === INDEX_X, + 'Transform-origin %s can only be used for x-position', + value, + ); + transformOriginArray[INDEX_X] = valueLower === 'left' ? 0 : '100%'; + break; + } + case 'top': + case 'bottom': { + invariant( + index !== INDEX_Z, + 'Transform-origin %s can only be used for y-position', + value, + ); + transformOriginArray[INDEX_Y] = valueLower === 'top' ? 0 : '100%'; + + // Handle [[ center | left | right ] && [ center | top | bottom ]] ? + if (index === INDEX_X) { + const horizontal = regex.exec(transformOriginString); + if (horizontal == null) { + break outer; + } + + switch (horizontal[0].toLowerCase()) { + case 'left': + transformOriginArray[INDEX_X] = 0; + break; + case 'right': + transformOriginArray[INDEX_X] = '100%'; + break; + case 'center': + transformOriginArray[INDEX_X] = '50%'; + break; + default: + invariant( + false, + 'Could not parse transform-origin: %s', + transformOriginString, + ); + } + nextIndex = INDEX_Z; + } + + break; + } + case 'center': { + invariant( + index !== INDEX_Z, + 'Transform-origin value %s cannot be used for z-position', + value, + ); + transformOriginArray[index] = '50%'; + break; + } + default: { + if (value.endsWith('%')) { + transformOriginArray[index] = value; + } else { + transformOriginArray[index] = parseFloat(value); // Remove `px` + } + break; + } + } + + index = nextIndex; + } + + transformOrigin = transformOriginArray; + } + + if (__DEV__) { + _validateTransformOrigin(transformOrigin); + } + + return transformOrigin; +} + +function _validateTransformOrigin(transformOrigin: Array) { + invariant( + transformOrigin.length === 3, + 'Transform origin must have exactly 3 values.', + ); + const [x, y, z] = transformOrigin; + invariant( + typeof x === 'number' || (typeof x === 'string' && x.endsWith('%')), + 'Transform origin x-position must be a number. Passed value: %s.', + x, + ); + invariant( + typeof y === 'number' || (typeof y === 'string' && y.endsWith('%')), + 'Transform origin y-position must be a number. Passed value: %s.', + y, + ); + invariant( + typeof z === 'number', + 'Transform origin z-position must be a number. Passed value: %s.', + z, + ); +} diff --git a/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js b/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js index b92f86d3ad2559..2a1215a0ffd9ca 100644 --- a/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js +++ b/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js @@ -49,6 +49,7 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): { case 'bottom': case 'top': case 'transform': + case 'transformOrigin': case 'rowGap': case 'columnGap': case 'gap': 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 ec4f3e00756616..689b31a8684a70 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 @@ -151,7 +151,7 @@ public void onLayoutChange(View v, int currentHeight = bottom - top; if ((currentHeight != oldHeight || currentWidth != oldWidth)) { - String transformOrigin = (String) v.getTag(R.id.transform_origin); + ReadableArray transformOrigin = (ReadableArray) v.getTag(R.id.transform_origin); ReadableArray transformMatrix = (ReadableArray) v.getTag(R.id.transform); if (transformMatrix != null && transformOrigin != null) { setTransformProperty((T) v, transformMatrix, transformOrigin); @@ -175,14 +175,14 @@ public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) { if (matrix == null) { resetTransformProperty(view); } else { - String transformOrigin = (String) view.getTag(R.id.transform_origin); + ReadableArray transformOrigin = (ReadableArray) view.getTag(R.id.transform_origin); setTransformProperty(view, matrix, transformOrigin); } } @Override @ReactProp(name = ViewProps.TRANSFORM_ORIGIN) - public void setTransformOrigin(@NonNull T view, @Nullable String transformOrigin) { + public void setTransformOrigin(@NonNull T view, @Nullable ReadableArray transformOrigin) { view.setTag(R.id.transform_origin, transformOrigin); ReadableArray transformMatrix = (ReadableArray) view.getTag(R.id.transform); if (transformMatrix != null) { @@ -482,7 +482,7 @@ public void setAccessibilityLiveRegion(@NonNull T view, @Nullable String liveReg } } - private static void setTransformProperty(@NonNull View view, ReadableArray transforms, String transformOrigin) { + private static void setTransformProperty(@NonNull View view, ReadableArray transforms, ReadableArray transformOrigin) { sMatrixDecompositionContext.reset(); TransformHelper.processTransform(transforms, sTransformDecompositionArray, PixelUtil.toDIPFromPixel(view.getWidth()), PixelUtil.toDIPFromPixel(view.getHeight()), transformOrigin); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java index 341142b560cef2..2a845e1903c59d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java @@ -122,7 +122,7 @@ public void setProperty(T view, String propName, @Nullable Object value) { mViewManager.setTransform(view, (ReadableArray) value); break; case ViewProps.TRANSFORM_ORIGIN: - mViewManager.setTransformOrigin(view, (String) value); + mViewManager.setTransformOrigin(view, (ReadableArray) value); break; case ViewProps.TRANSLATE_X: mViewManager.setTranslateX(view, value == null ? 0.0f : ((Double) value).floatValue()); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java index b609d285a8844b..6ef232813dbc3f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java @@ -72,7 +72,7 @@ public interface BaseViewManagerInterface { void setTransform(T view, @Nullable ReadableArray matrix); - void setTransformOrigin(T view, @Nullable String transformOrigin); + void setTransformOrigin(T view, @Nullable ReadableArray transformOrigin); void setTranslateX(T view, float translateX); 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 e0cbb7dfb6f8f4..c2e2c662cef012 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 @@ -45,7 +45,7 @@ private static double convertToRadians(ReadableMap transformMap, String key) { return inRadians ? value : MatrixMathHelper.degreesToRadians(value); } - public static void processTransform(ReadableArray transforms, double[] result, float viewWidth, float viewHeight, String transformOrigin) { + public static void processTransform(ReadableArray transforms, double[] result, float viewWidth, float viewHeight, ReadableArray transformOrigin) { double[] helperMatrix = sHelperMatrix.get(); MatrixMathHelper.resetIdentityMatrix(result); @@ -120,7 +120,7 @@ public static void processTransform(ReadableArray transforms, double[] result, f } } - public static float[] getTranslateForTransformOrigin(float viewWidth, float viewHeight, String transformOrigin) { + public static float[] getTranslateForTransformOrigin(float viewWidth, float viewHeight, ReadableArray transformOrigin) { if (transformOrigin == null || (viewHeight == 0 && viewWidth == 0)) { return null; } @@ -129,24 +129,19 @@ public static float[] getTranslateForTransformOrigin(float viewWidth, float view float[] origin = {viewCenterX, viewCenterY, 0.0f}; - String[] parts = transformOrigin.split(" "); - for (int i = 0; i < parts.length && i < 3; i++) { - String part = parts[i]; - if (part.endsWith("%")) { - float val = Float.parseFloat(part.substring(0, part.length() - 1)); - origin[i] = (i == 0 ? viewWidth : viewHeight) * val / 100.0f; - } else if (part.equals("top")) { - origin[1] = 0.0f; - } else if (part.equals("bottom")) { - origin[1] = viewHeight; - } else if (part.equals("left")) { - origin[0] = 0.0f; - } else if (part.equals("right")) { - origin[0] = viewWidth; - } else if (part.equals("center")) { - continue; - } else { - origin[i] = Float.parseFloat(part); + 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; + } } } From 1f4abeae8517815638bad22bd10fdfbed1910a0f Mon Sep 17 00:00:00 2001 From: Nishan Date: Wed, 23 Aug 2023 10:52:36 +0530 Subject: [PATCH 3/7] fix: pr feedback --- .../java/com/facebook/react/uimanager/BaseViewManager.java | 3 ++- .../java/com/facebook/react/uimanager/TransformHelper.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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 689b31a8684a70..83373e231093fa 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 @@ -132,6 +132,7 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie return view; } + // Currently. onLayout listener is only attached when transform origin prop is being used. @Override public void onLayoutChange(View v, int left, @@ -482,7 +483,7 @@ public void setAccessibilityLiveRegion(@NonNull T view, @Nullable String liveReg } } - private static void setTransformProperty(@NonNull View view, ReadableArray transforms, ReadableArray transformOrigin) { + private static void setTransformProperty(@NonNull View view, ReadableArray transforms, @Nullable ReadableArray transformOrigin) { sMatrixDecompositionContext.reset(); TransformHelper.processTransform(transforms, sTransformDecompositionArray, PixelUtil.toDIPFromPixel(view.getWidth()), PixelUtil.toDIPFromPixel(view.getHeight()), transformOrigin); 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 c2e2c662cef012..70383527d713e4 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 @@ -45,6 +45,10 @@ private static double convertToRadians(ReadableMap transformMap, String key) { return inRadians ? value : MatrixMathHelper.degreesToRadians(value); } + 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); From 71a7be54f436f1664aaba6ba0a9893b455bc19c9 Mon Sep 17 00:00:00 2001 From: Nishan Date: Wed, 23 Aug 2023 10:57:02 +0530 Subject: [PATCH 4/7] fix: pr feedback --- packages/react-native/Libraries/StyleSheet/splitLayoutProps.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js b/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js index 2a1215a0ffd9ca..b92f86d3ad2559 100644 --- a/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js +++ b/packages/react-native/Libraries/StyleSheet/splitLayoutProps.js @@ -49,7 +49,6 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): { case 'bottom': case 'top': case 'transform': - case 'transformOrigin': case 'rowGap': case 'columnGap': case 'gap': From c494b029cac8437ab3c9a5907e8aecf32039255a Mon Sep 17 00:00:00 2001 From: Nishan Date: Wed, 23 Aug 2023 12:25:50 +0530 Subject: [PATCH 5/7] fix: handle translate for matrix transform and remove listener if transform origin is not present --- .../react/uimanager/BaseViewManager.java | 6 +- .../react/uimanager/TransformHelper.java | 107 +++++++++--------- 2 files changed, 59 insertions(+), 54 deletions(-) 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 83373e231093fa..40b4030b298988 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 @@ -189,7 +189,11 @@ public void setTransformOrigin(@NonNull T view, @Nullable ReadableArray transfor if (transformMatrix != null) { setTransformProperty(view, transformMatrix, transformOrigin); } - view.addOnLayoutChangeListener(this); + if (transformOrigin != null) { + view.addOnLayoutChangeListener(this); + } else { + view.removeOnLayoutChangeListener(this); + } } @Override 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 70383527d713e4..e9a19f6ba291c8 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 @@ -52,71 +52,72 @@ public static void processTransform(ReadableArray transforms, double[] result) { 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); } - return; - } - - 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); - } - - 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 { + 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); } - } 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); + MatrixMathHelper.multiplyInto(result, result, helperMatrix); + } } + if (offsets != null) { MatrixMathHelper.resetIdentityMatrix(helperMatrix); MatrixMathHelper.applyTranslate3D(helperMatrix, -offsets[0], -offsets[1], -offsets[2]); From 070787f31c9c113eae925d038878aeb5ea4d3247 Mon Sep 17 00:00:00 2001 From: Nishan Date: Wed, 23 Aug 2023 21:09:45 +0530 Subject: [PATCH 6/7] fix: optimise transform --- .../react/uimanager/BaseViewManager.java | 19 +++++++++---------- .../react/uimanager/TransformHelper.java | 2 +- .../main/res/views/uimanager/values/ids.xml | 3 +++ 3 files changed, 13 insertions(+), 11 deletions(-) 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 40b4030b298988..3238d12aecd8d2 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 @@ -173,22 +173,14 @@ public void setBackgroundColor(@NonNull T view, int backgroundColor) { @ReactProp(name = ViewProps.TRANSFORM) public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) { view.setTag(R.id.transform, matrix); - if (matrix == null) { - resetTransformProperty(view); - } else { - ReadableArray transformOrigin = (ReadableArray) view.getTag(R.id.transform_origin); - setTransformProperty(view, matrix, transformOrigin); - } + view.setTag(R.id.invalidate_transform, true); } @Override @ReactProp(name = ViewProps.TRANSFORM_ORIGIN) public void setTransformOrigin(@NonNull T view, @Nullable ReadableArray transformOrigin) { view.setTag(R.id.transform_origin, transformOrigin); - ReadableArray transformMatrix = (ReadableArray) view.getTag(R.id.transform); - if (transformMatrix != null) { - setTransformProperty(view, transformMatrix, transformOrigin); - } + view.setTag(R.id.invalidate_transform, true); if (transformOrigin != null) { view.addOnLayoutChangeListener(this); } else { @@ -575,6 +567,13 @@ private void updateViewAccessibility(@NonNull T view) { protected void onAfterUpdateTransaction(@NonNull T view) { super.onAfterUpdateTransaction(view); updateViewAccessibility(view); + Boolean invalidateTransform = (Boolean) view.getTag(R.id.invalidate_transform); + if (invalidateTransform != null && invalidateTransform) { + ReadableArray transformOrigin = (ReadableArray) view.getTag(R.id.transform_origin); + ReadableArray transformMatrix = (ReadableArray) view.getTag(R.id.transform); + setTransformProperty(view, transformMatrix, transformOrigin); + view.setTag(R.id.invalidate_transform, false); + } } @Override 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 e9a19f6ba291c8..b8a79d143de650 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 @@ -125,7 +125,7 @@ public static void processTransform(ReadableArray transforms, double[] result, f } } - public static float[] getTranslateForTransformOrigin(float viewWidth, float viewHeight, ReadableArray transformOrigin) { + private static float[] getTranslateForTransformOrigin(float viewWidth, float viewHeight, ReadableArray transformOrigin) { if (transformOrigin == null || (viewHeight == 0 && viewWidth == 0)) { return null; } 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 90c887f805800e..84ebee0466979e 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 @@ -53,4 +53,7 @@ + + + From bf53d65f3eb08af8220a8eec2f08a5da52bd9236 Mon Sep 17 00:00:00 2001 From: Nishan Date: Thu, 24 Aug 2023 00:55:24 +0530 Subject: [PATCH 7/7] reset invalidate_transform view tag --- .../main/java/com/facebook/react/uimanager/BaseViewManager.java | 1 + 1 file changed, 1 insertion(+) 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 3238d12aecd8d2..757c6d34875578 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 @@ -92,6 +92,7 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie view.setTag(R.id.transform, null); view.setTag(R.id.transform_origin, null); + view.setTag(R.id.invalidate_transform, null); view.removeOnLayoutChangeListener(this); // setShadowColor if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {