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
This commit is contained in:
Ahmed El-Helw 2016-06-15 12:23:13 -07:00
parent 5f3f9caceb
commit 4ecfa0c800
4 changed files with 122 additions and 0 deletions

View File

@ -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));
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}