From f01272b031e81850dfdeae04c58ca8ec2b2c5eeb Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Sat, 17 Oct 2015 06:24:55 -0700 Subject: [PATCH] Fix touches with pointer events backtrack Differential Revision: D2553642 fb-gh-sync-id: b1788879bfbb564a291ada0e7ac206f567780f8a --- .../react/uimanager/TouchTargetHelper.java | 59 +++++++++---------- .../react/views/text/ReactTextView.java | 6 -- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 6826bc663d..0658f7005c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -11,7 +11,6 @@ package com.facebook.react.uimanager; import javax.annotation.Nullable; -import android.graphics.Rect; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -25,8 +24,7 @@ import com.facebook.react.bridge.UiThreadUtil; */ public class TouchTargetHelper { - private static final Rect mVisibleRect = new Rect(); - private static final int[] mViewLocationInScreen = {0, 0}; + private static final float[] mEventCoords = new float[2]; /** * Find touch event target view within the provided container given the coordinates provided @@ -43,11 +41,15 @@ public class TouchTargetHelper { ViewGroup viewGroup) { UiThreadUtil.assertOnUiThread(); int targetTag = viewGroup.getId(); - View nativeTargetView = findTouchTargetView(eventX, eventY, viewGroup); + // Store eventCoords in array so that they are modified to be relative to the targetView found. + float[] eventCoords = mEventCoords; + eventCoords[0] = eventY; + eventCoords[1] = eventX; + View nativeTargetView = findTouchTargetView(eventCoords, viewGroup); if (nativeTargetView != null) { View reactTargetView = findClosestReactAncestor(nativeTargetView); if (reactTargetView != null) { - targetTag = getTouchTargetForView(reactTargetView, eventX, eventY); + targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]); } } return targetTag; @@ -68,38 +70,36 @@ public class TouchTargetHelper { * A (pointerEvents: auto) - B (pointerEvents: box-none) - C (pointerEvents: none) * \ D (pointerEvents: auto) - E (pointerEvents: auto) * If the search goes down the first branch, it would return A as the target, which is incorrect. - * NB: This method is not thread-safe as it uses static instance of {@link Rect} + * NB: This modifies the eventCoords to always be relative to the current viewGroup. When the + * method returns, it will contain the eventCoords relative to the targetView found. */ - private static View findTouchTargetView(float eventX, float eventY, ViewGroup viewGroup) { + private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup) { int childrenCount = viewGroup.getChildCount(); for (int i = childrenCount - 1; i >= 0; i--) { View child = viewGroup.getChildAt(i); - // Views with `removeClippedSubviews` are exposing removed subviews through `getChildAt` to - // support proper view cleanup. Views removed by this option will be detached from it's - // parent, therefore `getGlobalVisibleRect` call will return bogus result as it treat view - // with no parent as a root of the view hierarchy. To prevent this from happening we check - // that view has a parent before visiting it. - if (child.getParent() != null && isTouchPointInView(eventX, eventY, viewGroup, child)) { + if (isTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child)) { // Apply offset to event coordinates to transform them into the coordinate space of the // child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}. - eventX += viewGroup.getScrollX() - child.getLeft(); - eventY += viewGroup.getScrollY() - child.getTop(); - View targetView = findTouchTargetViewWithPointerEvents(eventX, eventY, child); + eventCoords[0] += viewGroup.getScrollY() - child.getTop(); + eventCoords[1] += viewGroup.getScrollX() - child.getLeft(); + View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child); if (targetView != null) { return targetView; } + eventCoords[0] -= viewGroup.getScrollY() - child.getTop(); + eventCoords[1] -= viewGroup.getScrollX() - child.getLeft(); } } return viewGroup; } // Taken from {@link ViewGroup#isTransformedTouchPointInView()} - private static boolean isTouchPointInView(float x, float y, ViewGroup parent, View child) { - float localX = x + parent.getScrollX() - child.getLeft(); + private static boolean isTouchPointInView(float y, float x, ViewGroup parent, View child) { float localY = y + parent.getScrollY() - child.getTop(); + float localX = x + parent.getScrollX() - child.getLeft(); // Taken from {@link View#pointInView()}. - return localX >= 0 && localX < (child.getRight() - child.getLeft()) - && localY >= 0 && localY < (child.getBottom() - child.getTop()); + return localY >= 0 && localY < (child.getBottom() - child.getTop()) + && localX >= 0 && localX < (child.getRight() - child.getLeft()); } /** @@ -107,9 +107,7 @@ public class TouchTargetHelper { * its descendants are the touch target. */ private static @Nullable View findTouchTargetViewWithPointerEvents( - float eventX, - float eventY, - View view) { + float eventCoords[], View view) { PointerEvents pointerEvents = view instanceof ReactPointerEventsView ? ((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO; if (pointerEvents == PointerEvents.NONE) { @@ -123,7 +121,7 @@ public class TouchTargetHelper { } else if (pointerEvents == PointerEvents.BOX_NONE) { // This view can't be the target, but its children might if (view instanceof ViewGroup) { - View targetView = findTouchTargetView(eventX, eventY, (ViewGroup) view); + View targetView = findTouchTargetView(eventCoords, (ViewGroup) view); return targetView != view ? targetView : null; } return null; @@ -131,7 +129,7 @@ public class TouchTargetHelper { } else if (pointerEvents == PointerEvents.AUTO) { // Either this view or one of its children is the target if (view instanceof ViewGroup) { - return findTouchTargetView(eventX, eventY, (ViewGroup) view); + return findTouchTargetView(eventCoords, (ViewGroup) view); } return view; @@ -141,14 +139,11 @@ public class TouchTargetHelper { } } - private static int getTouchTargetForView(View targetView, float eventX, float eventY) { + private static int getTouchTargetForView(View targetView, float eventY, float eventX) { if (targetView instanceof ReactCompoundView) { - // Use coordinates relative to the view. Use getLocationOnScreen() API, which is slightly more - // expensive than getGlobalVisibleRect(), otherwise partially visible views offset is wrong. - targetView.getLocationOnScreen(mViewLocationInScreen); - return ((ReactCompoundView) targetView).reactTagForTouch( - eventX - mViewLocationInScreen[0], - eventY - mViewLocationInScreen[1]); + // Use coordinates relative to the view, which have been already computed by + // {@link #findTouchTargetView()}. + return ((ReactCompoundView) targetView).reactTagForTouch(eventX, eventY); } return targetView.getId(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 36d3923fec..5bcdb43ede 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -30,12 +30,6 @@ public class ReactTextView extends TextView implements ReactCompoundView { int x = (int) touchX; int y = (int) touchY; - x -= getTotalPaddingLeft(); - y -= getTotalPaddingTop(); - - x += getScrollX(); - y += getScrollY(); - Layout layout = getLayout(); int line = layout.getLineForVertical(y);