Fix touches with pointer events backtrack

Differential Revision: D2553642

fb-gh-sync-id: b1788879bfbb564a291ada0e7ac206f567780f8a
This commit is contained in:
Andrei Coman 2015-10-17 06:24:55 -07:00 коммит произвёл facebook-github-bot-3
Родитель a2524bac33
Коммит f01272b031
2 изменённых файлов: 27 добавлений и 38 удалений

Просмотреть файл

@ -11,7 +11,6 @@ package com.facebook.react.uimanager;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import android.graphics.Rect;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -25,8 +24,7 @@ import com.facebook.react.bridge.UiThreadUtil;
*/ */
public class TouchTargetHelper { public class TouchTargetHelper {
private static final Rect mVisibleRect = new Rect(); private static final float[] mEventCoords = new float[2];
private static final int[] mViewLocationInScreen = {0, 0};
/** /**
* Find touch event target view within the provided container given the coordinates provided * Find touch event target view within the provided container given the coordinates provided
@ -43,11 +41,15 @@ public class TouchTargetHelper {
ViewGroup viewGroup) { ViewGroup viewGroup) {
UiThreadUtil.assertOnUiThread(); UiThreadUtil.assertOnUiThread();
int targetTag = viewGroup.getId(); 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) { if (nativeTargetView != null) {
View reactTargetView = findClosestReactAncestor(nativeTargetView); View reactTargetView = findClosestReactAncestor(nativeTargetView);
if (reactTargetView != null) { if (reactTargetView != null) {
targetTag = getTouchTargetForView(reactTargetView, eventX, eventY); targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]);
} }
} }
return targetTag; return targetTag;
@ -68,38 +70,36 @@ public class TouchTargetHelper {
* A (pointerEvents: auto) - B (pointerEvents: box-none) - C (pointerEvents: none) * A (pointerEvents: auto) - B (pointerEvents: box-none) - C (pointerEvents: none)
* \ D (pointerEvents: auto) - E (pointerEvents: auto) * \ D (pointerEvents: auto) - E (pointerEvents: auto)
* If the search goes down the first branch, it would return A as the target, which is incorrect. * 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(); int childrenCount = viewGroup.getChildCount();
for (int i = childrenCount - 1; i >= 0; i--) { for (int i = childrenCount - 1; i >= 0; i--) {
View child = viewGroup.getChildAt(i); View child = viewGroup.getChildAt(i);
// Views with `removeClippedSubviews` are exposing removed subviews through `getChildAt` to if (isTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child)) {
// 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)) {
// Apply offset to event coordinates to transform them into the coordinate space of the // Apply offset to event coordinates to transform them into the coordinate space of the
// child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}. // child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}.
eventX += viewGroup.getScrollX() - child.getLeft(); eventCoords[0] += viewGroup.getScrollY() - child.getTop();
eventY += viewGroup.getScrollY() - child.getTop(); eventCoords[1] += viewGroup.getScrollX() - child.getLeft();
View targetView = findTouchTargetViewWithPointerEvents(eventX, eventY, child); View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
if (targetView != null) { if (targetView != null) {
return targetView; return targetView;
} }
eventCoords[0] -= viewGroup.getScrollY() - child.getTop();
eventCoords[1] -= viewGroup.getScrollX() - child.getLeft();
} }
} }
return viewGroup; return viewGroup;
} }
// Taken from {@link ViewGroup#isTransformedTouchPointInView()} // Taken from {@link ViewGroup#isTransformedTouchPointInView()}
private static boolean isTouchPointInView(float x, float y, ViewGroup parent, View child) { private static boolean isTouchPointInView(float y, float x, ViewGroup parent, View child) {
float localX = x + parent.getScrollX() - child.getLeft();
float localY = y + parent.getScrollY() - child.getTop(); float localY = y + parent.getScrollY() - child.getTop();
float localX = x + parent.getScrollX() - child.getLeft();
// Taken from {@link View#pointInView()}. // Taken from {@link View#pointInView()}.
return localX >= 0 && localX < (child.getRight() - child.getLeft()) return localY >= 0 && localY < (child.getBottom() - child.getTop())
&& 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. * its descendants are the touch target.
*/ */
private static @Nullable View findTouchTargetViewWithPointerEvents( private static @Nullable View findTouchTargetViewWithPointerEvents(
float eventX, float eventCoords[], View view) {
float eventY,
View view) {
PointerEvents pointerEvents = view instanceof ReactPointerEventsView ? PointerEvents pointerEvents = view instanceof ReactPointerEventsView ?
((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO; ((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO;
if (pointerEvents == PointerEvents.NONE) { if (pointerEvents == PointerEvents.NONE) {
@ -123,7 +121,7 @@ public class TouchTargetHelper {
} else if (pointerEvents == PointerEvents.BOX_NONE) { } else if (pointerEvents == PointerEvents.BOX_NONE) {
// This view can't be the target, but its children might // This view can't be the target, but its children might
if (view instanceof ViewGroup) { if (view instanceof ViewGroup) {
View targetView = findTouchTargetView(eventX, eventY, (ViewGroup) view); View targetView = findTouchTargetView(eventCoords, (ViewGroup) view);
return targetView != view ? targetView : null; return targetView != view ? targetView : null;
} }
return null; return null;
@ -131,7 +129,7 @@ public class TouchTargetHelper {
} else if (pointerEvents == PointerEvents.AUTO) { } else if (pointerEvents == PointerEvents.AUTO) {
// Either this view or one of its children is the target // Either this view or one of its children is the target
if (view instanceof ViewGroup) { if (view instanceof ViewGroup) {
return findTouchTargetView(eventX, eventY, (ViewGroup) view); return findTouchTargetView(eventCoords, (ViewGroup) view);
} }
return 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) { if (targetView instanceof ReactCompoundView) {
// Use coordinates relative to the view. Use getLocationOnScreen() API, which is slightly more // Use coordinates relative to the view, which have been already computed by
// expensive than getGlobalVisibleRect(), otherwise partially visible views offset is wrong. // {@link #findTouchTargetView()}.
targetView.getLocationOnScreen(mViewLocationInScreen); return ((ReactCompoundView) targetView).reactTagForTouch(eventX, eventY);
return ((ReactCompoundView) targetView).reactTagForTouch(
eventX - mViewLocationInScreen[0],
eventY - mViewLocationInScreen[1]);
} }
return targetView.getId(); return targetView.getId();
} }

Просмотреть файл

@ -30,12 +30,6 @@ public class ReactTextView extends TextView implements ReactCompoundView {
int x = (int) touchX; int x = (int) touchX;
int y = (int) touchY; int y = (int) touchY;
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout(); Layout layout = getLayout();
int line = layout.getLineForVertical(y); int line = layout.getLineForVertical(y);