Skip to content

Commit 64158aa

Browse files
qflenclaude
andcommitted
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). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ae2b5ba commit 64158aa

3 files changed

Lines changed: 11 additions & 3 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
@@ -606,7 +606,10 @@ protected void setTransformProperty(
606606
view.setScaleY(1);
607607
view.setCameraDistance(0);
608608
view.setAnimationMatrix(affine);
609-
view.setTag(R.id.skew_animation_matrix, Boolean.TRUE);
609+
// Tag value is the matrix itself so TouchTargetHelper can use it for hit testing -- View's
610+
// own getMatrix() does not compose mAnimationMatrix, so without this fallback the React
611+
// hit-test path would still see the original rectangular bounds.
612+
view.setTag(R.id.skew_animation_matrix, affine);
610613
return;
611614
}
612615

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
@@ -288,7 +289,11 @@ public object TouchTargetHelper {
288289
) {
289290
var localX = x + parent.scrollX - child.left
290291
var localY = y + parent.scrollY - child.top
291-
val matrix = child.matrix
292+
// BaseViewManager applies skewX / skewY through View.setAnimationMatrix, which is composed
293+
// for drawing but not exposed via View.getMatrix(); fall back to the stashed matrix so hit
294+
// testing follows the rendered parallelogram and not the original rectangle.
295+
val skewMatrix = child.getTag(R.id.skew_animation_matrix) as? Matrix
296+
val matrix = skewMatrix ?: child.matrix
292297
if (!matrix.isIdentity) {
293298
val localXY = matrixTransformCoords
294299
localXY[0] = localX

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ function SkewExample(): React.Node {
207207
<View testID="transform-skew-27649" style={styles.skewContainer}>
208208
<Text style={styles.skewLabel}>Last tapped: {tapped}</Text>
209209
<Text style={styles.skewNote}>
210-
Tap any box to confirm hit testing follows the rendered shape.
210+
Tap any box to confirm hit testing follows the rendered parallelogram on both platforms.
211211
</Text>
212212
<View style={styles.skewRow}>
213213
<Pressable

0 commit comments

Comments
 (0)