Skip to content

Commit bea6baf

Browse files
committed
Make TouchTargetHelper hit-test the parallelogram, not the rectangle
`View.setAnimationMatrix(Matrix)` composes for drawing but `View.getMatrix()` does not include `mAnimationMatrix` in its return value. RN's hit test in `TouchTargetHelper.getChildPoint` inverse-maps touch coordinates through `child.matrix` and falls back to the original rectangular bounds. Net result: after the rendering fix, skewed Views render as parallelograms but tip taps that fall outside the rectangular bounds miss, and rect-corner taps that are visually empty post-skew still register. iOS gets parity for free because UIKit hit-testing inverse-maps through the layer's `CATransform3D`. Store the affine `Matrix` on `R.id.skew_animation_matrix` (instead of `Boolean.TRUE`) and have `TouchTargetHelper.getChildPoint` consult the tag, falling back to `child.matrix` when absent. Net effect: hit testing follows the rendered parallelogram on both platforms. Verified empirically by sweeping tap coordinates 1 px on either side of every parallelogram edge: - (100, 555): inside parallelogram top-left tip / outside rect bounds. Before this commit: missed. After: registers as `skewX 20deg`. - (330, 731): inside parallelogram bottom-right tip / outside rect. Same flip. - (208, 640): rect center / parallelogram center. Registers in both states. - (350, 640): outside parallelogram at vertical-pivot y. Misses in both states (correct).
1 parent ab88d5a commit bea6baf

3 files changed

Lines changed: 17 additions & 5 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,10 @@ protected void setTransformProperty(
600600
view.setScaleY(1);
601601
view.setCameraDistance(0);
602602
view.setAnimationMatrix(affine);
603-
view.setTag(R.id.skew_animation_matrix, Boolean.TRUE);
603+
// Tag value is the matrix itself so TouchTargetHelper can use it for hit testing -- View's
604+
// own getMatrix() does not compose mAnimationMatrix, so without this fallback the React
605+
// hit-test path would still see the original rectangular bounds.
606+
view.setTag(R.id.skew_animation_matrix, affine);
604607
return;
605608
}
606609

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.graphics.PointF
1313
import android.view.View
1414
import android.view.ViewGroup
1515
import com.facebook.common.logging.FLog
16+
import com.facebook.react.R
1617
import com.facebook.react.bridge.UiThreadUtil
1718
import com.facebook.react.common.ReactConstants
1819
import com.facebook.react.touch.ReactHitSlopView
@@ -298,7 +299,11 @@ public object TouchTargetHelper {
298299
): Boolean {
299300
var localX = x + parent.scrollX - child.left
300301
var localY = y + parent.scrollY - child.top
301-
val matrix = child.matrix
302+
// BaseViewManager applies skewX / skewY through View.setAnimationMatrix, which is composed
303+
// for drawing but not exposed via View.getMatrix(); fall back to the stashed matrix so hit
304+
// testing follows the rendered parallelogram and not the original rectangle.
305+
val skewMatrix = child.getTag(R.id.skew_animation_matrix) as? Matrix
306+
val matrix = skewMatrix ?: child.matrix
302307
if (!matrix.isIdentity) {
303308
val inverseMatrix = inverseMatrix
304309
if (!matrix.invert(inverseMatrix)) {

packages/rn-tester/js/examples/Transform/TransformExample.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,18 @@ function SkewExample(): React.Node {
251251
};
252252
const tapStyle =
253253
(extra: ViewStyleProp) =>
254-
({pressed}: PressableStateCallbackType): ViewStyleProp =>
255-
[styles.skewBox, extra, pressed && styles.skewBoxPressed];
254+
({pressed}: PressableStateCallbackType): ViewStyleProp => [
255+
styles.skewBox,
256+
extra,
257+
pressed && styles.skewBoxPressed,
258+
];
256259

257260
return (
258261
<View testID="transform-skew-27649" style={styles.skewContainer}>
259262
<Text style={styles.skewLabel}>Last tapped: {tapped}</Text>
260263
<Text style={styles.skewNote}>
261-
Tap any box to confirm hit testing follows the rendered shape.
264+
Tap any box to confirm hit testing follows the rendered parallelogram on
265+
both platforms.
262266
</Text>
263267
<View style={styles.skewRow}>
264268
<Pressable

0 commit comments

Comments
 (0)