Implementing perspective, rotateX, rotateY, skewX and skewY for Android #3522
Conversation
By analyzing the blame information on this pull request, we identified @kmagiera, @browniefed and @mkonicek to be potential reviewers. |
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at cla@fb.com. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
@@ -581,7 +581,7 @@ var MatrixMath = { | |||
skew, | |||
translation, | |||
|
|||
rotate: rotationDegrees[2], | |||
perspectiveZ: perspective[2] != 0 ? -1/perspective[2] : 1280, |
chirag04
Oct 19, 2015
Contributor
we should keep it just perspective
, just like ios. I see some perspective code already in decomposed matrix. you can pass perspective from js like:
transform: { perspective: 1280 }
we should keep it just perspective
, just like ios. I see some perspective code already in decomposed matrix. you can pass perspective from js like:
transform: { perspective: 1280 }
felipemartim
Oct 19, 2015
Author
I called it "perspectiveZ" because there's already a perspective array being returned on this statement. I don't know if it will be useful for something.
One thing that I couldn't manage to do is to retrieve the pixel density, as in Android View docs:
float scale = context.getResources().getDisplayMetrics().density;
view.setCameraDistance(distance * scale);
The default distance depends on the screen density. For instance, on a medium density display, the default
distance is 1280. On a high density display, the default distance is 1920."
I called it "perspectiveZ" because there's already a perspective array being returned on this statement. I don't know if it will be useful for something.
One thing that I couldn't manage to do is to retrieve the pixel density, as in Android View docs:
float scale = context.getResources().getDisplayMetrics().density;
view.setCameraDistance(distance * scale);
The default distance depends on the screen density. For instance, on a medium density display, the default
distance is 1280. On a high density display, the default distance is 1920."
extends ViewManager<T, C> { | ||
|
||
private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR; | ||
private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix"; | ||
private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate"; | ||
private static final String PROP_DECOMPOSED_MATRIX_PERSPECTIVE_Z = "perspectiveZ"; |
chirag04
Oct 19, 2015
Contributor
keep it only perspective
keep it only perspective
@@ -30,7 +33,10 @@ | |||
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility"; | |||
|
|||
// DEPRECATED |
@felipemartim @chirag04 Really looking forward to have this PR merged! Can I somehow support here to bring it to a state where it can be reviewed / merged by @kmagiera? |
@PhilippKrone sure. feel free to review and drop comments. I would use uiexplorer perspective example and see if it works after applying this PR. |
@PhilippKrone sure! I already identified a bug with perspective. Depending on the order of the transform properties, -1/perspective[2] can be a completely different number. This has to do with how the perspectiveMatrix is calculated. I believe it would be better to set the perspective value specified on "styles" directly to "setCameraDistance()" Android View without all that magic that's happening in "decomposeMatrix()". Another issue is to set this value correctly according to the device pixel density. Unfortunately I don't have the time to work on this right now and this is my first PR ever, so I don't know how can I help here. Are you able to evolve this PR? |
@felipemartim @PhilippKrone I really want to work on this but can't get the uiexplorer to compile on master branch. I will spend some more time on it. |
|
||
/** | ||
* Base class that should be suitable for the majority of subclasses of {@link ViewManager}. | ||
* It provides support for base view properties such as backgroundColor, opacity, etc. | ||
*/ | ||
public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode> | ||
public abstract class BaseViewManager<T extends View, C extends ReactShadowNode> |
kmagiera
Oct 21, 2015
Contributor
Please revert this line
Please revert this line
I got the perspective to work with all the examples on the uiexplore transform. I will share the update tomorrow. @kmagiera would you have ideas about how to implement backfaceVisibility of ios on android? It is implemented on ios already. I can do it if you can give some info on java side of things. Goal is to get the flip animation example working on android as well. https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility |
@chirag04 cool! thanks! :) The backfaceVisibility topic is exactly the last thing missing for me to have my animation / screen working! |
diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js
index 23db738..702578f 100755
--- a/Libraries/Utilities/MatrixMath.js
+++ b/Libraries/Utilities/MatrixMath.js
@@ -423,62 +423,26 @@ var MatrixMath = {
);
// output values
- var perspective = [];
var quaternion = [];
var scale = [];
var skew = [];
var translation = [];
// create normalized, 2d array matrix
- // and normalized 1d array perspectiveMatrix with redefined 4th column
if (!transformMatrix[15]) {
return;
}
var matrix = [];
- var perspectiveMatrix = [];
for (var i = 0; i < 4; i++) {
matrix.push([]);
for (var j = 0; j < 4; j++) {
var value = transformMatrix[(i * 4) + j] / transformMatrix[15];
matrix[i].push(value);
- perspectiveMatrix.push(j === 3 ? 0 : value);
}
}
- perspectiveMatrix[15] = 1;
- // test for singularity of upper 3x3 part of the perspective matrix
- if (!MatrixMath.determinant(perspectiveMatrix)) {
- return;
- }
-
- // isolate perspective
- if (matrix[0][3] !== 0 || matrix[1][3] !== 0 || matrix[2][3] !== 0) {
- // rightHandSide is the right hand side of the equation.
- // rightHandSide is a vector, or point in 3d space relative to the origin.
- var rightHandSide = [
- matrix[0][3],
- matrix[1][3],
- matrix[2][3],
- matrix[3][3]
- ];
-
- // Solve the equation by inverting perspectiveMatrix and multiplying
- // rightHandSide by the inverse.
- var inversePerspectiveMatrix = MatrixMath.inverse3x3(
- perspectiveMatrix
- );
- var transposedInversePerspectiveMatrix = MatrixMath.transpose4x4(
- inversePerspectiveMatrix
- );
- var perspective = MatrixMath.multiplyVectorByMatrix(
- rightHandSide,
- transposedInversePerspectiveMatrix
- );
- } else {
- // no perspective
- perspective[0] = perspective[1] = perspective[2] = 0;
- perspective[3] = 1;
- }
+ //perspective
+ var perspective = -1 / matrix[2][3];
// translation is simple
for (var i = 0; i < 3; i++) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
index fbbae4c..b2d28a2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
@@ -6,6 +6,7 @@ import android.graphics.Color;
import android.os.Build;
import android.view.View;
+import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
@@ -17,7 +18,8 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR;
private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix";
- private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate";
+ private static final String PROP_DECOMPOSED_MATRIX_PERSPECTIVE = "perspective";
+ private static final String PROP_DECOMPOSED_MATRIX_ROTATION = "rotationDegrees";
private static final String PROP_DECOMPOSED_MATRIX_SCALE_X = "scaleX";
private static final String PROP_DECOMPOSED_MATRIX_SCALE_Y = "scaleY";
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_X = "translateX";
@@ -142,12 +144,18 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_X)));
view.setTranslationY(PixelUtil.toPixelFromDIP(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_Y)));
- view.setRotation(
- (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE));
view.setScaleX(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_X));
view.setScaleY(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_Y));
+
+ float scale = DisplayMetricsHolder.getDisplayMetrics().density;
+ view.setCameraDistance(
+ (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_PERSPECTIVE) * scale);
+ ReadableArray rotationArray = matrix.getArray(PROP_DECOMPOSED_MATRIX_ROTATION);
+ view.setRotationX((float) rotationArray.getDouble(0));
+ view.setRotationY((float) rotationArray.getDouble(1));
+ view.setRotation((float) rotationArray.getDouble(2));
}
private static void resetTransformMatrix(View view) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewPropertyApplicator.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewPropertyApplicator.java
index ec9117e..0613efd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewPropertyApplicator.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewPropertyApplicator.java
@@ -16,6 +16,7 @@ import java.util.HashMap;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
+import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
@@ -26,7 +27,8 @@ import com.facebook.react.bridge.ReadableMap;
public class BaseViewPropertyApplicator {
private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix";
- private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate";
+ private static final String PROP_DECOMPOSED_MATRIX_PERSPECTIVE = "perspective";
+ private static final String PROP_DECOMPOSED_MATRIX_ROTATION = "rotationDegrees";
private static final String PROP_DECOMPOSED_MATRIX_SCALE_X = "scaleX";
private static final String PROP_DECOMPOSED_MATRIX_SCALE_Y = "scaleY";
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_X = "translateX";
@@ -130,23 +132,6 @@ public class BaseViewPropertyApplicator {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
}
-
- // DEPRECATED
- if (props.hasKey(PROP_ROTATION)) {
- view.setRotation(props.getFloat(PROP_ROTATION, 0));
- }
- if (props.hasKey(PROP_SCALE_X)) {
- view.setScaleX(props.getFloat(PROP_SCALE_X, 1.f));
- }
- if (props.hasKey(PROP_SCALE_Y)) {
- view.setScaleY(props.getFloat(PROP_SCALE_Y, 1.f));
- }
- if (props.hasKey(PROP_TRANSLATE_X)) {
- view.setTranslationX(PixelUtil.toPixelFromDIP(props.getFloat(PROP_TRANSLATE_X, 0)));
- }
- if (props.hasKey(PROP_TRANSLATE_Y)) {
- view.setTranslationY(PixelUtil.toPixelFromDIP(props.getFloat(PROP_TRANSLATE_Y, 0)));
- }
}
private static void setTransformMatrix(View view, ReadableMap matrix) {
@@ -154,12 +139,19 @@ public class BaseViewPropertyApplicator {
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_X)));
view.setTranslationY(PixelUtil.toPixelFromDIP(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_Y)));
- view.setRotation(
- (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE));
view.setScaleX(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_X));
view.setScaleY(
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_Y));
+
+ float scale = DisplayMetricsHolder.getDisplayMetrics().density;
+ view.setCameraDistance(
+ (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_PERSPECTIVE) * scale);
+
+ ReadableArray rotationArray = matrix.getArray(PROP_DECOMPOSED_MATRIX_ROTATION);
+ view.setRotationX((float) rotationArray.getDouble(0));
+ view.setRotationY((float) rotationArray.getDouble(1));
+ view.setRotation((float) rotationArray.getDouble(2));
}
private static void resetTransformMatrix(View view) { Above is the diff for getting flip. We really need to understand backfaceVisibility for android. Hoping for ideas from @kmagiera . I couldn't find anything related to backfaceVisibility on android. |
@felipemartim updated the pull request. |
@chirag04 Isnt that something we could use? http://developer.android.com/training/animation/cardflip.html |
@PhilippKrone I saw that link and it seem it uses native animation lib. I'm not sure how we can using the js animated library to work with the native one. If it's a blocker, I think specific cardflip animation would be possible by bridging a new package and using the native animated lib. That's what i think we are going to do. |
@chirag04 From how I understand it, currently no cardflip animation is possible at all in android as you will always see the backside, wont you? |
quaternion, | ||
scale, | ||
skew, | ||
translation, | ||
|
||
rotate: rotationDegrees[2], | ||
perspective: perspective[2] != 0 ? -1/perspective[2] : 1280, | ||
scaleX: scale[0], | ||
scaleY: scale[1], | ||
translateX: translation[0], |
mkonicek
Oct 23, 2015
Contributor
Why are the changes in this file needed? What's your test plan to make sure it doesn't break existing iOS behavior?
Why are the changes in this file needed? What's your test plan to make sure it doesn't break existing iOS behavior?
view.setRotationX(rotation); | ||
} | ||
|
||
@Deprecated |
mkonicek
Oct 23, 2015
Contributor
Why Deprecated? You've just added this code.
Why Deprecated? You've just added this code.
Not sure we want to merge this in the current form. In any case, features like this need an example in the UIExplorer. Is there an iOS example? Have you tried running it on Android? Provide lots of screenshots. |
I was trying to do some animations today, realized I've applied current diff to my local react-native and I'm getting this exception: I'm doing a very basic Cheers, |
@chirag04 can you make a new PR with your proposed changes? Perhaps we can get this stuff merged soon then |
@kmagiera Sorry for pushing this, it's kind of a show stopper for me at the moment. Do you have an idea concerning the implementation of the css attribute "backfaceVisiblity" in android? (https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility) |
@chirag04 I get an exception using your diff. It seems that if matrix[2][3] is 0, then perspective is infinite, which will be handled as "null" in java. Following this, the line "view.setCameraDistance( |
@PhilippKrone I'm out of the office this week so won't be able to dig into this and answer your question anytime soon. Maybe @astreet can help? |
I'm not very familiar with the Android 3D APIs, but I doubt anything like backface-visibility is available on the View class. You'll probably just need to mimic it (or polyfill it in JS using the standard 3D transform APIs?) |
@PhilippKrone I managed to overcome the backfaceVisibility issue by setting the opacity of the front side of the card to 0 when the rotation reached 90º. |
quaternion, | ||
scale, | ||
skew, | ||
translation, | ||
|
||
rotate: rotationDegrees[2], | ||
perspective: perspective[2] != 0 ? -1/perspective[2] : 1280, |
mkonicek
Nov 14, 2015
Contributor
Why 1280? Can you add a comment explaining this?
Why 1280? Can you add a comment explaining this?
view.setScaleX(1); | ||
view.setScaleY(1); | ||
float scale = DisplayMetricsHolder.getDisplayMetrics().density; | ||
view.setCameraDistance(1280 * scale); |
mkonicek
Nov 14, 2015
Contributor
Why 1280? Can you add a comment?
Why 1280? Can you add a comment?
Can you check all of the places where |
@@ -17,9 +18,11 @@ | |||
|
|||
private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR; | |||
private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix"; | |||
private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate"; | |||
private static final String PROP_DECOMPOSED_MATRIX_PERSPECTIVE = "perspective"; | |||
private static final String PROP_DECOMPOSED_MATRIX_ROTATION = "rotationDegrees"; |
mkonicek
Nov 14, 2015
Contributor
Oh OK, is it rotationDegrees
, rather than rotate
on iOS as well?
Oh OK, is it rotationDegrees
, rather than rotate
on iOS as well?
I have the same problem. Ugly, I use state to control rotation |
Any updates on this @felipemartim ? |
@satya164 I'll probably have to create a pull request to set rotateY, rotateX, skewX, skewY separated from perspective. I'll try to do it this weekend. |
Awesome. Thanks for working on this @felipemartim |
Setting 3D transform properties to the Android View. The "perspective" property is a workaround and maybe needs to be thought a little better.