mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat: android transform origin (#38558)
Summary: This PR adds transform-origin support for android to make it easier to review. https://github.com/facebook/react-native/pull/37606#pullrequestreview-1513082850 by javache. I'll answer feedback from javache below. ## Changelog: [Android] [ADDED] - Transform origin <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: https://github.com/facebook/react-native/pull/38558 Test Plan: Run iOS RNTester app in old architecture and test transform-origin example in `TransformExample.js`. Reviewed By: NickGerleman Differential Revision: D48528339 Pulled By: javache fbshipit-source-id: e255a7f364e57396dada60b2c69c724cec406f51
This commit is contained in:
committed by
Facebook GitHub Bot
parent
177ef21ea2
commit
9e68599daf
@@ -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},
|
||||
|
||||
+65
-6
@@ -40,7 +40,7 @@ import java.util.Map;
|
||||
* provides support for base view properties such as backgroundColor, opacity, etc.
|
||||
*/
|
||||
public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode>
|
||||
extends ViewManager<T, C> implements BaseViewManagerInterface<T> {
|
||||
extends ViewManager<T, C> implements BaseViewManagerInterface<T>, 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<T extends View, C extends LayoutShadowNode
|
||||
view.setElevation(0);
|
||||
view.setAnimationMatrix(null);
|
||||
|
||||
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) {
|
||||
view.setOutlineAmbientShadowColor(Color.BLACK);
|
||||
@@ -129,6 +133,35 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
return view;
|
||||
}
|
||||
|
||||
// Currently. onLayout listener is only attached when transform origin prop is being used.
|
||||
@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)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(
|
||||
name = ViewProps.BACKGROUND_COLOR,
|
||||
@@ -141,10 +174,19 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
@Override
|
||||
@ReactProp(name = ViewProps.TRANSFORM)
|
||||
public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) {
|
||||
if (matrix == null) {
|
||||
resetTransformProperty(view);
|
||||
view.setTag(R.id.transform, matrix);
|
||||
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);
|
||||
view.setTag(R.id.invalidate_transform, true);
|
||||
if (transformOrigin != null) {
|
||||
view.addOnLayoutChangeListener(this);
|
||||
} else {
|
||||
setTransformProperty(view, matrix);
|
||||
view.removeOnLayoutChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,9 +481,15 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
}
|
||||
}
|
||||
|
||||
private static void setTransformProperty(@NonNull View view, ReadableArray transforms) {
|
||||
private static void setTransformProperty(
|
||||
@NonNull View view, ReadableArray transforms, @Nullable ReadableArray 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(
|
||||
@@ -526,6 +574,17 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
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);
|
||||
if (transformMatrix != null) {
|
||||
setTransformProperty(view, transformMatrix, transformOrigin);
|
||||
} else {
|
||||
resetTransformProperty(view);
|
||||
}
|
||||
view.setTag(R.id.invalidate_transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -121,6 +121,9 @@ public abstract class BaseViewManagerDelegate<T extends View, U extends BaseView
|
||||
case ViewProps.TRANSFORM:
|
||||
mViewManager.setTransform(view, (ReadableArray) value);
|
||||
break;
|
||||
case ViewProps.TRANSFORM_ORIGIN:
|
||||
mViewManager.setTransformOrigin(view, (ReadableArray) value);
|
||||
break;
|
||||
case ViewProps.TRANSLATE_X:
|
||||
mViewManager.setTranslateX(view, value == null ? 0.0f : ((Double) value).floatValue());
|
||||
break;
|
||||
|
||||
+2
@@ -72,6 +72,8 @@ public interface BaseViewManagerInterface<T extends View> {
|
||||
|
||||
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);
|
||||
|
||||
+101
-45
@@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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";
|
||||
|
||||
@@ -45,6 +45,15 @@
|
||||
<!-- tag is used store bitset of pointer events observed -->
|
||||
<item type="id" name="pointer_events"/>
|
||||
|
||||
<!-- tag is used store raw transform style on the view -->
|
||||
<item type="id" name="transform"/>
|
||||
|
||||
<!-- tag is used store raw transform origin style on the view -->
|
||||
<item type="id" name="transform_origin"/>
|
||||
|
||||
<!-- tag is used to store role tag-->
|
||||
<item type="id" name="role"/>
|
||||
|
||||
<!-- tag is used to invalidate transform style in view manager -->
|
||||
<item type="id" name="invalidate_transform"/>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user