-
Notifications
You must be signed in to change notification settings - Fork 25k
feat: android transform origin #38558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
83719e5
3ecc8ec
1f4abea
71a7be5
c494b02
070787f
bf53d65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,7 @@ | |
| * 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,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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: It seems potentially error prone that this is only called when a transform is present. I.e. I could imagine someone adding code here, not realizing that it isn't always called.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true, just didn't want to attach a listener if it was not needed. Should we write a comment maybe? // Currently, the onLayout listener is only attached when transform-origin prop is being used.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also removed the listener when transform-origin is null. |
||
| 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( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,12 +20,12 @@ | |
| public class TransformHelper { | ||
|
|
||
| private static ThreadLocal<double[]> sHelperMatrix = | ||
| new ThreadLocal<double[]>() { | ||
| @Override | ||
| protected double[] initialValue() { | ||
| return new double[16]; | ||
| } | ||
| }; | ||
| new ThreadLocal<double[]>() { | ||
| @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; | ||
intergalacticspacehighway marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. answering #37606 (comment) We also reset the helperMatrix which behaves weird when there are multiple view transform animations running. I think it's reusing the instance between different animations. |
||
| } | ||
|
|
||
| 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}; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
layout on a view is updated here. We can use onSizeChanged callback from
ReactViewGroupinstead of a listener here but we'll have to move the transform logic there and might need refactor asMatrixHelperclass is inuimanagerpackage and has some fields private. Also do some type cast here to callReactViewGroup's set transform.