From 4ecfa0c800ef600f229979b564aff9bf4ee8176e Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Wed, 15 Jun 2016 12:23:13 -0700 Subject: [PATCH] Fix touch inspector when using Nodes Summary: Fix touch inspector when using Nodes by implementing custom logic. This logic now takes into account that non-View nodes need to be clickable. Reviewed By: astreet Differential Revision: D3433927 --- .../react/flat/FlatUIViewOperationQueue.java | 86 +++++++++++++++++++ .../facebook/react/flat/FlatViewGroup.java | 14 +++ .../com/facebook/react/flat/NodeRegion.java | 4 + .../facebook/react/flat/TextNodeRegion.java | 18 ++++ 4 files changed, 122 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java index 00d4b2554f5..7e7081d856f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java @@ -12,11 +12,15 @@ package com.facebook.react.flat; import javax.annotation.Nullable; import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.NoSuchNativeViewException; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.TouchTargetHelper; import com.facebook.react.uimanager.UIViewOperationQueue; /** @@ -225,6 +229,78 @@ import com.facebook.react.uimanager.UIViewOperationQueue; } } + private final class FindTargetForTouchOperation implements UIOperation { + + private final int mReactTag; + private final float mTargetX; + private final float mTargetY; + private final Callback mCallback; + private final int[] NATIVE_VIEW_BUFFER = new int[1]; + + private FindTargetForTouchOperation( + final int reactTag, + final float targetX, + final float targetY, + final Callback callback) { + super(); + mReactTag = reactTag; + mTargetX = targetX; + mTargetY = targetY; + mCallback = callback; + } + + @Override + public void execute() { + try { + mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER); + } catch (IllegalViewOperationException e) { + mCallback.invoke(); + return; + } + + // Because React coordinates are relative to root container, and measure() operates + // on screen coordinates, we need to offset values using root container location. + final float containerX = (float) MEASURE_BUFFER[0]; + final float containerY = (float) MEASURE_BUFFER[1]; + + View view = mNativeViewHierarchyManager.getView(mReactTag); + final int touchTargetReactTag = TouchTargetHelper.findTargetTagForTouch( + mTargetX, + mTargetY, + (ViewGroup) view, + NATIVE_VIEW_BUFFER); + + try { + mNativeViewHierarchyManager.measure( + NATIVE_VIEW_BUFFER[0], + MEASURE_BUFFER); + } catch (IllegalViewOperationException e) { + mCallback.invoke(); + return; + } + + NodeRegion region = NodeRegion.EMPTY; + boolean isNativeView = NATIVE_VIEW_BUFFER[0] == touchTargetReactTag; + if (!isNativeView) { + // NATIVE_VIEW_BUFFER[0] is a FlatViewGroup, touchTargetReactTag is the touch target and + // isn't an Android View - try to get its NodeRegion + view = mNativeViewHierarchyManager.getView(NATIVE_VIEW_BUFFER[0]); + if (view instanceof FlatViewGroup) { + region = ((FlatViewGroup) view).getNodeRegionForTag(mReactTag); + } + } + + int resultTag = region == NodeRegion.EMPTY ? touchTargetReactTag : region.mTag; + float x = PixelUtil.toDIPFromPixel(region.mLeft + MEASURE_BUFFER[0] - containerX); + float y = PixelUtil.toDIPFromPixel(region.mTop + MEASURE_BUFFER[1] - containerY); + float width = PixelUtil.toDIPFromPixel(isNativeView ? + MEASURE_BUFFER[2] : region.mRight - region.mLeft); + float height = PixelUtil.toDIPFromPixel(isNativeView ? + MEASURE_BUFFER[3] : region.mBottom - region.mTop); + mCallback.invoke(resultTag, x, y, width, height); + } + } + public FlatUIViewOperationQueue( ReactApplicationContext reactContext, FlatNativeViewHierarchyManager nativeViewHierarchyManager) { @@ -309,4 +385,14 @@ import com.facebook.react.uimanager.UIViewOperationQueue; enqueueUIOperation(op); return op; } + + @Override + public void enqueueFindTargetForTouch( + final int reactTag, + final float targetX, + final float targetY, + final Callback callback) { + enqueueUIOperation( + new FindTargetForTouchOperation(reactTag, targetX, targetY, callback)); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java index 0bfb8d2db1e..a451714072b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java @@ -410,6 +410,20 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; invalidate(); } + /** + * Finds a NodeRegion which matches the said reactTag + * @param reactTag the reactTag to look for + * @return the NodeRegion, or NodeRegion.EMPTY + */ + /* package */ NodeRegion getNodeRegionForTag(int reactTag) { + for (NodeRegion region : mNodeRegions) { + if (region.matchesTag(reactTag)) { + return region; + } + } + return NodeRegion.EMPTY; + } + /** * Return a list of FlatViewGroups that are detached (due to being clipped) but that we have a * strong reference to. This is used by the FlatNativeViewHierarchyManager to explicitly clean up diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java b/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java index 41f72549f5c..be20ab0a5d2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java @@ -42,4 +42,8 @@ package com.facebook.react.flat; /* package */ int getReactTag(float touchX, float touchY) { return mTag; } + + /* package */ boolean matchesTag(int tag) { + return mTag == tag; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java b/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java index 8c9821673ab..1bfed8e6af0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java @@ -59,4 +59,22 @@ import android.text.Spanned; return super.getReactTag(touchX, touchY); } + + @Override + boolean matchesTag(int tag) { + if (super.matchesTag(tag)) { + return true; + } + + if (mLayout != null) { + Spanned text = (Spanned) mLayout.getText(); + RCTRawText[] spans = text.getSpans(0, text.length(), RCTRawText.class); + for (RCTRawText span : spans) { + if (span.getReactTag() == tag) { + return true; + } + } + } + return false; + } }