Skip to content

Commit

Permalink
Refactor findTargetPathAndCoordinatesForTouch to improve perf of even…
Browse files Browse the repository at this point in the history
…t delivery

Summary:
Refactor of TouchTargetHelper.findTargetPathAndCoordinatesForTouch to avoid unnecessary lookup of views during the dispatching of Hover Events

changelog: [internal] internal

Reviewed By: lunaleaps, mdvacca

Differential Revision: D32296003

fbshipit-source-id: 93834c37331ad5d75645a5665a1c8c3d965765fb
  • Loading branch information
ryancat authored and facebook-github-bot committed Apr 25, 2022
1 parent 6563c99 commit f40976c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.TouchTargetHelper.ViewTarget;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.PointerEvent;
import com.facebook.react.uimanager.events.PointerEventHelper;
Expand Down Expand Up @@ -40,7 +41,7 @@ public class JSPointerDispatcher {

// Set globally for hover interactions, referenced for coalescing hover events
private long mHoverInteractionKey = TouchEvent.UNSET;
private List<Integer> mLastHitPath = Collections.emptyList();
private List<ViewTarget> mLastHitPath = Collections.emptyList();
private final float[] mLastEventCoordinates = new float[2];

public JSPointerDispatcher(ViewGroup viewGroup) {
Expand All @@ -56,7 +57,7 @@ public void onChildStartedNativeGesture(
return;
}

List<Integer> hitPath =
List<ViewTarget> hitPath =
TouchTargetHelper.findTargetPathAndCoordinatesForTouch(
motionEvent.getX(), motionEvent.getY(), mRootViewGroup, mTargetCoordinates);
dispatchCancelEvent(hitPath, motionEvent, eventDispatcher);
Expand All @@ -74,11 +75,14 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp

int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
int action = motionEvent.getActionMasked();
List<Integer> hitPath =
List<ViewTarget> hitPath =
TouchTargetHelper.findTargetPathAndCoordinatesForTouch(
motionEvent.getX(), motionEvent.getY(), mRootViewGroup, mTargetCoordinates);

int targetTag = hitPath.get(0);
if (hitPath.isEmpty()) {
return;
}
int targetTag = hitPath.get(0).getViewId();

if (supportsHover) {
if (action == MotionEvent.ACTION_HOVER_MOVE) {
Expand All @@ -105,7 +109,7 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
if (!supportsHover) {
// Enter root -> child
for (int i = hitPath.size(); i-- > 0; ) {
int tag = hitPath.get(i);
int tag = hitPath.get(i).getViewId();
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_ENTER, surfaceId, tag, motionEvent));
}
Expand Down Expand Up @@ -161,7 +165,7 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
if (!supportsHover) {
// Leave child -> root
for (int i = 0; i < hitPath.size(); i++) {
int tag = hitPath.get(i);
int tag = hitPath.get(i).getViewId();
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent));
}
Expand Down Expand Up @@ -195,7 +199,7 @@ private void handleHoverEvent(
MotionEvent motionEvent,
EventDispatcher eventDispatcher,
int surfaceId,
List<Integer> hitPath) {
List<ViewTarget> hitPath) {

int action = motionEvent.getActionMasked();
if (action != MotionEvent.ACTION_HOVER_MOVE) {
Expand All @@ -222,13 +226,17 @@ private void handleHoverEvent(

// If child is handling, eliminate target tags under handling child
if (mChildHandlingNativeGesture > 0) {
int index = hitPath.indexOf(mChildHandlingNativeGesture);
if (index > 0) {
hitPath.subList(0, index).clear();
int index = 0;
for (ViewTarget viewTarget : hitPath) {
if (viewTarget.getViewId() == mChildHandlingNativeGesture) {
hitPath.subList(0, index).clear();
break;
}
index++;
}
}

int targetTag = hitPath.size() > 0 ? hitPath.get(0) : -1;
int targetTag = hitPath.isEmpty() ? -1 : hitPath.get(0).getViewId();
// If targetTag is empty, we should bail?
if (targetTag == -1) {
return;
Expand All @@ -252,26 +260,31 @@ private void handleHoverEvent(
// If something has changed in either enter/exit, let's start a new coalescing key
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mHoverInteractionKey);

List<Integer> enterTargetTags = hitPath.subList(0, hitPath.size() - firstDivergentIndex);
if (enterTargetTags.size() > 0) {
List<ViewTarget> enterViewTargets = hitPath.subList(0, hitPath.size() - firstDivergentIndex);
if (enterViewTargets.size() > 0) {
// root -> child
for (int i = enterTargetTags.size(); i-- > 0; ) {
int enterTargetTag = enterTargetTags.get(i);
for (int i = enterViewTargets.size(); i-- > 0; ) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_ENTER, surfaceId, enterTargetTag, motionEvent));
PointerEventHelper.POINTER_ENTER,
surfaceId,
enterViewTargets.get(i).getViewId(),
motionEvent));
}
}

// Fire all relevant exit events
List<Integer> exitTargetTags =
List<ViewTarget> exitViewTargets =
mLastHitPath.subList(0, mLastHitPath.size() - firstDivergentIndex);
if (exitTargetTags.size() > 0) {
if (exitViewTargets.size() > 0) {
// child -> root
for (Integer exitTargetTag : exitTargetTags) {
for (ViewTarget exitViewTarget : exitViewTargets) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_LEAVE, surfaceId, exitTargetTag, motionEvent));
PointerEventHelper.POINTER_LEAVE,
surfaceId,
exitViewTarget.getViewId(),
motionEvent));
}
}
}
Expand All @@ -287,7 +300,7 @@ private void handleHoverEvent(
}

private void dispatchCancelEvent(
List<Integer> hitPath, MotionEvent motionEvent, EventDispatcher eventDispatcher) {
List<ViewTarget> hitPath, MotionEvent motionEvent, EventDispatcher eventDispatcher) {
// This means the gesture has already ended, via some other CANCEL or UP event. This is not
// expected to happen very often as it would mean some child View has decided to intercept the
// touch stream and start a native gesture only upon receiving the UP/CANCEL event.
Expand All @@ -297,16 +310,19 @@ private void dispatchCancelEvent(
"Expected to not have already sent a cancel for this gesture");
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);

int targetTag = hitPath.get(0);
// Question: Does cancel fire on all in hit path?
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
if (!hitPath.isEmpty()) {
int targetTag = hitPath.get(0).getViewId();
// Question: Does cancel fire on all in hit path?
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));

for (int tag : hitPath) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent));
for (ViewTarget viewTarget : hitPath) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_LEAVE, surfaceId, viewTarget.getViewId(), motionEvent));
}
}

mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

/**
* Class responsible for identifying which react view should handle a given {@link MotionEvent}. It
Expand Down Expand Up @@ -109,19 +110,20 @@ public static int findTargetTagAndCoordinatesForTouch(
* @param eventY the Y screen coordinate of the touch location
* @param viewGroup the container view to traverse
* @param viewCoords an out parameter that will return the X,Y value in the target view
* @return If a target was found, returns a path through the view tree of all react tags that are
* a container for the touch target, ordered from target to root (last element)
* @return If a target was found, returns a {@link Lis<ViewTarget>} containing the path through
* the view tree of all react tags and views that are a container for the touch target,
* ordered from target to root (last element)
*/
@SuppressLint("ResourceType")
public static List<Integer> findTargetPathAndCoordinatesForTouch(
public static List<ViewTarget> findTargetPathAndCoordinatesForTouch(
float eventX, float eventY, ViewGroup viewGroup, float[] viewCoords) {
UiThreadUtil.assertOnUiThread();

// Store eventCoords in array so that they are modified to be relative to the targetView found.
viewCoords[0] = eventX;
viewCoords[1] = eventY;

List<Integer> pathAccumulator = new ArrayList<>();
List<ViewTarget> pathAccumulator = new ArrayList<>();
View targetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup, pathAccumulator);

if (targetView != null) {
Expand All @@ -140,7 +142,7 @@ public static List<Integer> findTargetPathAndCoordinatesForTouch(

int targetTag = getTouchTargetForView(reactTargetView, eventX, eventY);
if (targetTag != reactTargetView.getId()) {
pathAccumulator.add(0, targetTag);
pathAccumulator.add(0, new ViewTarget(targetTag, (View) null));
}
}

Expand Down Expand Up @@ -178,7 +180,7 @@ private static View findTouchTargetView(
float[] eventCoords,
View view,
EnumSet<TouchTargetReturnType> allowReturnTouchTargetTypes,
List<Integer> pathAccumulator) {
List<ViewTarget> pathAccumulator) {
// We prefer returning a child, so we check for a child that can handle the touch first
if (allowReturnTouchTargetTypes.contains(TouchTargetReturnType.CHILD)
&& view instanceof ViewGroup) {
Expand Down Expand Up @@ -303,7 +305,7 @@ private static void getChildPoint(
* its descendants are the touch target.
*/
private static @Nullable View findTouchTargetViewWithPointerEvents(
float eventCoords[], View view, @Nullable List<Integer> pathAccumulator) {
float eventCoords[], View view, @Nullable List<ViewTarget> pathAccumulator) {
PointerEvents pointerEvents =
view instanceof ReactPointerEventsView
? ((ReactPointerEventsView) view).getPointerEvents()
Expand All @@ -330,7 +332,7 @@ private static void getChildPoint(
findTouchTargetView(
eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF), pathAccumulator);
if (targetView != null && pathAccumulator != null) {
pathAccumulator.add(view.getId());
pathAccumulator.add(new ViewTarget(view.getId(), view));
}
return targetView;

Expand All @@ -341,7 +343,7 @@ private static void getChildPoint(
eventCoords, view, EnumSet.of(TouchTargetReturnType.CHILD), pathAccumulator);
if (targetView != null) {
if (pathAccumulator != null) {
pathAccumulator.add(view.getId());
pathAccumulator.add(new ViewTarget(view.getId(), view));
}
return targetView;
}
Expand All @@ -358,7 +360,7 @@ && isTouchPointInView(eventCoords[0], eventCoords[1], view)) {
// make sure we exclude the View itself because of the PointerEvents.BOX_NONE
if (reactTag != view.getId()) {
if (pathAccumulator != null) {
pathAccumulator.add(view.getId());
pathAccumulator.add(new ViewTarget(view.getId(), view));
}
return view;
}
Expand All @@ -372,7 +374,7 @@ && isTouchPointInView(eventCoords[0], eventCoords[1], view)) {
&& isTouchPointInView(eventCoords[0], eventCoords[1], view)
&& ((ReactCompoundViewGroup) view).interceptsTouchEvent(eventCoords[0], eventCoords[1])) {
if (pathAccumulator != null) {
pathAccumulator.add(view.getId());
pathAccumulator.add(new ViewTarget(view.getId(), view));
}
return view;
}
Expand All @@ -384,7 +386,7 @@ && isTouchPointInView(eventCoords[0], eventCoords[1], view)
EnumSet.of(TouchTargetReturnType.SELF, TouchTargetReturnType.CHILD),
pathAccumulator);
if (result != null && pathAccumulator != null) {
pathAccumulator.add(view.getId());
pathAccumulator.add(new ViewTarget(view.getId(), view));
}
return result;

Expand All @@ -402,4 +404,47 @@ private static int getTouchTargetForView(View targetView, float eventX, float ev
}
return targetView.getId();
}

public static class ViewTarget {
private final int mViewId;
private final @Nullable View mView;

private ViewTarget(int viewId, @Nullable View view) {
mViewId = viewId;
mView = view;
}

public int getViewId() {
return mViewId;
}

@Nullable
public View getView() {
return mView;
}

@Override
public boolean equals(Object o) {
// If the object is compared with itself then return true
if (o == this) {
return true;
}

// Check if o is an instance of ViewTarget. Note that "null instanceof ViewTarget" also
// returns false.
if (!(o instanceof ViewTarget)) {
return false;
}

ViewTarget other = (ViewTarget) o;
// We only need to compare view id, as we assume the same view id will always map to the same
// view. TargetView is not mutable so this should be safe.
return other.getViewId() == mViewId;
}

@Override
public int hashCode() {
return Objects.hashCode(mViewId);
}
}
}

0 comments on commit f40976c

Please sign in to comment.