mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
PointerEvents: Mark when listened to for touch interactions
Summary: Changelog: [Internal] - Bypass dispatching an event if no view along the hierarchy is listening to it. Only applied for touch-based interactions. Next change will add optimization for mouse interactions Reviewed By: vincentriemer Differential Revision: D35739417 fbshipit-source-id: 134ffefef3bb4f97bf3e63b6bccc0caca464dfbd
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0cff2efd3a
commit
471907c047
@@ -55,6 +55,12 @@ constexpr MapBuffer::Key VP_SHADOW_COLOR = 31;
|
||||
constexpr MapBuffer::Key VP_TEST_ID = 32;
|
||||
constexpr MapBuffer::Key VP_TRANSFORM = 33;
|
||||
constexpr MapBuffer::Key VP_ZINDEX = 34;
|
||||
constexpr MapBuffer::Key VP_POINTER_ENTER2 = 35;
|
||||
constexpr MapBuffer::Key VP_POINTER_LEAVE2 = 36;
|
||||
constexpr MapBuffer::Key VP_POINTER_MOVE2 = 37;
|
||||
constexpr MapBuffer::Key VP_POINTER_ENTER2_CAPTURE = 38;
|
||||
constexpr MapBuffer::Key VP_POINTER_LEAVE2_CAPTURE = 39;
|
||||
constexpr MapBuffer::Key VP_POINTER_MOVE2_CAPTURE = 40;
|
||||
|
||||
// Yoga values
|
||||
constexpr MapBuffer::Key YG_BORDER_WIDTH = 100;
|
||||
@@ -462,6 +468,22 @@ static inline MapBuffer viewPropsDiff(
|
||||
VP_POINTER_LEAVE, newProps.events[ViewEvents::Offset::PointerLeave]);
|
||||
builder.putBool(
|
||||
VP_POINTER_MOVE, newProps.events[ViewEvents::Offset::PointerMove]);
|
||||
|
||||
builder.putBool(
|
||||
VP_POINTER_ENTER2, newProps.events[ViewEvents::Offset::PointerEnter2]);
|
||||
builder.putBool(
|
||||
VP_POINTER_ENTER2_CAPTURE,
|
||||
newProps.events[ViewEvents::Offset::PointerEnter2Capture]);
|
||||
builder.putBool(
|
||||
VP_POINTER_LEAVE2, newProps.events[ViewEvents::Offset::PointerLeave2]);
|
||||
builder.putBool(
|
||||
VP_POINTER_LEAVE2_CAPTURE,
|
||||
newProps.events[ViewEvents::Offset::PointerLeave2Capture]);
|
||||
builder.putBool(
|
||||
VP_POINTER_MOVE2, newProps.events[ViewEvents::Offset::PointerMove2]);
|
||||
builder.putBool(
|
||||
VP_POINTER_MOVE2_CAPTURE,
|
||||
newProps.events[ViewEvents::Offset::PointerMove2Capture]);
|
||||
}
|
||||
|
||||
if (oldProps.removeClippedSubviews != newProps.removeClippedSubviews) {
|
||||
|
||||
@@ -557,6 +557,39 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
view.setTag(R.id.pointer_move, value);
|
||||
}
|
||||
|
||||
/* Experimental W3C Pointer events start */
|
||||
@ReactProp(name = "onPointerEnter2")
|
||||
public void setPointerEnter2(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_enter2, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "onPointerEnter2Capture")
|
||||
public void setPointerEnter2Capture(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_enter2_capture, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "onPointerLeave2")
|
||||
public void setPointerLeave2(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_leave2, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "onPointerLeave2Capture")
|
||||
public void setPointerLeave2Capture(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_leave2_capture, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "onPointerMove2")
|
||||
public void setPointerMove2(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_move2, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "onPointerMove2Capture")
|
||||
public void setPointerMove2Capture(@NonNull T view, boolean value) {
|
||||
view.setTag(R.id.pointer_move2_capture, value);
|
||||
}
|
||||
|
||||
/* Experimental W3C Pointer events end */
|
||||
|
||||
@ReactProp(name = "onMoveShouldSetResponder")
|
||||
public void setMoveShouldSetResponder(@NonNull T view, boolean value) {
|
||||
// no-op, handled by JSResponder
|
||||
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
import com.facebook.react.uimanager.events.PointerEventHelper.EVENT;
|
||||
import com.facebook.react.uimanager.events.TouchEvent;
|
||||
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper;
|
||||
import java.util.Collections;
|
||||
@@ -37,7 +38,7 @@ public class JSPointerDispatcher {
|
||||
private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper =
|
||||
new TouchEventCoalescingKeyHelper();
|
||||
|
||||
private static final float ONMOVE_EPSILON = 1f;
|
||||
private static final float ONMOVE_EPSILON = 0.1f;
|
||||
|
||||
// Set globally for hover interactions, referenced for coalescing hover events
|
||||
private long mHoverInteractionKey = TouchEvent.UNSET;
|
||||
@@ -82,7 +83,9 @@ public class JSPointerDispatcher {
|
||||
if (hitPath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int targetTag = hitPath.get(0).getViewId();
|
||||
|
||||
TouchTargetHelper.ViewTarget activeViewTarget = hitPath.get(0);
|
||||
int activeTargetTag = activeViewTarget.getViewId();
|
||||
|
||||
if (supportsHover) {
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
@@ -107,15 +110,17 @@ public class JSPointerDispatcher {
|
||||
mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime);
|
||||
|
||||
if (!supportsHover) {
|
||||
// Enter root -> child
|
||||
for (int i = hitPath.size(); i-- > 0; ) {
|
||||
int tag = hitPath.get(i).getViewId();
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_ENTER, surfaceId, tag, motionEvent));
|
||||
}
|
||||
dispatchNonBubblingEventForPathWhenListened(
|
||||
EVENT.ENTER, EVENT.ENTER_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
|
||||
}
|
||||
|
||||
boolean listeningForDown =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
|
||||
if (listeningForDown) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
|
||||
}
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -129,25 +134,47 @@ public class JSPointerDispatcher {
|
||||
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
|
||||
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
|
||||
|
||||
boolean listeningForDown =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
|
||||
if (listeningForDown) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
int coalescingKey = mTouchEventCoalescingKeyHelper.getCoalescingKey(mDownStartTime);
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_MOVE, surfaceId, targetTag, motionEvent, coalescingKey));
|
||||
|
||||
boolean listeningForMove =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
|
||||
if (listeningForMove) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_MOVE,
|
||||
surfaceId,
|
||||
activeTargetTag,
|
||||
motionEvent,
|
||||
coalescingKey));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Exactly one of the pointers goes up, not the last one
|
||||
if (action == MotionEvent.ACTION_POINTER_UP) {
|
||||
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
|
||||
|
||||
boolean listeningForUp =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
|
||||
if (listeningForUp) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -159,16 +186,17 @@ public class JSPointerDispatcher {
|
||||
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
|
||||
mDownStartTime = TouchEvent.UNSET;
|
||||
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
|
||||
boolean listeningForUp =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
|
||||
if (listeningForUp) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
|
||||
}
|
||||
|
||||
if (!supportsHover) {
|
||||
// Leave child -> root
|
||||
for (int i = 0; i < hitPath.size(); i++) {
|
||||
int tag = hitPath.get(i).getViewId();
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent));
|
||||
}
|
||||
dispatchNonBubblingEventForPathWhenListened(
|
||||
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -183,15 +211,54 @@ public class JSPointerDispatcher {
|
||||
"Warning : Motion Event was ignored. Action="
|
||||
+ action
|
||||
+ " Target="
|
||||
+ targetTag
|
||||
+ activeTargetTag
|
||||
+ " Supports Hover="
|
||||
+ supportsHover);
|
||||
}
|
||||
|
||||
private int findTargetTagAndSetCoordinates(MotionEvent ev) {
|
||||
// This method updates `mTargetCoordinates` with coordinates for the motion event.
|
||||
return TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
|
||||
ev.getX(), ev.getY(), mRootViewGroup, mTargetCoordinates, null);
|
||||
private static boolean isAnyoneListeningForBubblingEvent(
|
||||
List<ViewTarget> hitPath, EVENT event, EVENT captureEvent) {
|
||||
for (ViewTarget viewTarget : hitPath) {
|
||||
if (PointerEventHelper.isListening(viewTarget.getView(), event)
|
||||
|| PointerEventHelper.isListening(viewTarget.getView(), captureEvent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Dispatch event only if ancestor is listening to relevant event.
|
||||
This should only be relevant for ENTER/LEAVE events.
|
||||
@param hitPath - ordered from inner target to root
|
||||
*/
|
||||
|
||||
/** Dispatch non-bubbling event along the hit path only when relevant listeners */
|
||||
private static void dispatchNonBubblingEventForPathWhenListened(
|
||||
EVENT event,
|
||||
EVENT captureEvent,
|
||||
List<ViewTarget> hitPath,
|
||||
EventDispatcher dispatcher,
|
||||
int surfaceId,
|
||||
MotionEvent motionEvent) {
|
||||
|
||||
boolean ancestorListening = false;
|
||||
String eventName = PointerEventHelper.getDispatchableEventName(event);
|
||||
if (eventName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate through hitPath from ancestor -> target
|
||||
for (int i = hitPath.size() - 1; i >= 0; i--) {
|
||||
View view = hitPath.get(i).getView();
|
||||
int viewId = hitPath.get(i).getViewId();
|
||||
if (ancestorListening
|
||||
|| (i == 0 && PointerEventHelper.isListening(view, event))
|
||||
|| PointerEventHelper.isListening(view, captureEvent)) {
|
||||
dispatcher.dispatchEvent(PointerEvent.obtain(eventName, surfaceId, viewId, motionEvent));
|
||||
ancestorListening = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called on hover_move motion events only
|
||||
@@ -311,21 +378,21 @@ public class JSPointerDispatcher {
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
|
||||
|
||||
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 (ViewTarget viewTarget : hitPath) {
|
||||
eventDispatcher.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_LEAVE, surfaceId, viewTarget.getViewId(), motionEvent));
|
||||
boolean listeningForCancel =
|
||||
isAnyoneListeningForBubblingEvent(hitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE);
|
||||
if (listeningForCancel) {
|
||||
int targetTag = hitPath.get(0).getViewId();
|
||||
Assertions.assertNotNull(eventDispatcher)
|
||||
.dispatchEvent(
|
||||
PointerEvent.obtain(
|
||||
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
|
||||
}
|
||||
}
|
||||
|
||||
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
|
||||
mDownStartTime = TouchEvent.UNSET;
|
||||
dispatchNonBubblingEventForPathWhenListened(
|
||||
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
|
||||
|
||||
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
|
||||
mDownStartTime = TouchEvent.UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+85
@@ -8,6 +8,11 @@
|
||||
package com.facebook.react.uimanager.events;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.R;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
||||
/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
|
||||
public class PointerEventHelper {
|
||||
@@ -17,6 +22,21 @@ public class PointerEventHelper {
|
||||
public static final String POINTER_TYPE_MOUSE = "mouse";
|
||||
public static final String POINTER_TYPE_UNKNOWN = "";
|
||||
|
||||
public static enum EVENT {
|
||||
CANCEL,
|
||||
CANCEL_CAPTURE,
|
||||
DOWN,
|
||||
DOWN_CAPTURE,
|
||||
ENTER,
|
||||
ENTER_CAPTURE,
|
||||
LEAVE,
|
||||
LEAVE_CAPTURE,
|
||||
MOVE,
|
||||
MOVE_CAPTURE,
|
||||
UP,
|
||||
UP_CAPTURE,
|
||||
};
|
||||
|
||||
public static final String POINTER_CANCEL = "topPointerCancel";
|
||||
public static final String POINTER_DOWN = "topPointerDown";
|
||||
public static final String POINTER_ENTER = "topPointerEnter2";
|
||||
@@ -24,6 +44,27 @@ public class PointerEventHelper {
|
||||
public static final String POINTER_MOVE = "topPointerMove2";
|
||||
public static final String POINTER_UP = "topPointerUp";
|
||||
|
||||
/** We don't dispatch capture events from native; that's currently handled by JS. */
|
||||
public static @Nullable String getDispatchableEventName(EVENT event) {
|
||||
switch (event) {
|
||||
case LEAVE:
|
||||
return PointerEventHelper.POINTER_LEAVE;
|
||||
case DOWN:
|
||||
return PointerEventHelper.POINTER_DOWN;
|
||||
case MOVE:
|
||||
return PointerEventHelper.POINTER_MOVE;
|
||||
case ENTER:
|
||||
return PointerEventHelper.POINTER_ENTER;
|
||||
case CANCEL:
|
||||
return PointerEventHelper.POINTER_CANCEL;
|
||||
case UP:
|
||||
return PointerEventHelper.POINTER_UP;
|
||||
default:
|
||||
FLog.e(ReactConstants.TAG, "No dispatchable event name for type: " + event);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getW3CPointerType(final int toolType) {
|
||||
// https://www.w3.org/TR/pointerevents3/#dom-pointerevent-pointertype
|
||||
switch (toolType) {
|
||||
@@ -39,6 +80,50 @@ public class PointerEventHelper {
|
||||
return POINTER_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
public static boolean isListening(@Nullable View view, EVENT event) {
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object value = null;
|
||||
switch (event) {
|
||||
case DOWN:
|
||||
case DOWN_CAPTURE:
|
||||
case UP:
|
||||
case UP_CAPTURE:
|
||||
case CANCEL:
|
||||
case CANCEL_CAPTURE:
|
||||
return true;
|
||||
case ENTER:
|
||||
value = view.getTag(R.id.pointer_enter2);
|
||||
break;
|
||||
case ENTER_CAPTURE:
|
||||
value = view.getTag(R.id.pointer_enter2_capture);
|
||||
break;
|
||||
case LEAVE:
|
||||
value = view.getTag(R.id.pointer_leave2);
|
||||
break;
|
||||
case LEAVE_CAPTURE:
|
||||
value = view.getTag(R.id.pointer_leave2_capture);
|
||||
break;
|
||||
case MOVE:
|
||||
value = view.getTag(R.id.pointer_move2);
|
||||
break;
|
||||
case MOVE_CAPTURE:
|
||||
value = view.getTag(R.id.pointer_move2_capture);
|
||||
break;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getEventCategory(String pointerEventType) {
|
||||
if (pointerEventType == null) {
|
||||
return EventCategoryDef.UNSPECIFIED;
|
||||
|
||||
@@ -55,6 +55,12 @@ object ReactMapBufferPropSetter {
|
||||
private const val VP_TEST_ID = 32
|
||||
private const val VP_TRANSFORM = 33
|
||||
private const val VP_ZINDEX = 34
|
||||
private const val VP_POINTER_ENTER2 = 35
|
||||
private const val VP_POINTER_LEAVE2 = 36
|
||||
private const val VP_POINTER_MOVE2 = 37
|
||||
private const val VP_POINTER_ENTER2_CAPTURE = 38
|
||||
private const val VP_POINTER_LEAVE2_CAPTURE = 39
|
||||
private const val VP_POINTER_MOVE2_CAPTURE = 40
|
||||
|
||||
// Yoga values
|
||||
private const val YG_BORDER_WIDTH = 100
|
||||
@@ -185,6 +191,24 @@ object ReactMapBufferPropSetter {
|
||||
VP_POINTER_MOVE -> {
|
||||
viewManager.setPointerMove(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_ENTER2 -> {
|
||||
viewManager.setPointerEnter2(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_LEAVE2 -> {
|
||||
viewManager.setPointerLeave2(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_MOVE2 -> {
|
||||
viewManager.setPointerMove2(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_ENTER2_CAPTURE -> {
|
||||
viewManager.setPointerEnter2Capture(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_LEAVE2_CAPTURE -> {
|
||||
viewManager.setPointerLeave2Capture(view, entry.booleanValue)
|
||||
}
|
||||
VP_POINTER_MOVE2_CAPTURE -> {
|
||||
viewManager.setPointerMove2Capture(view, entry.booleanValue)
|
||||
}
|
||||
VP_REMOVE_CLIPPED_SUBVIEW -> {
|
||||
viewManager.setRemoveClippedSubviews(view, entry.booleanValue)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user