Emit non-hover pointer events on Android (#33526)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/33526

Changelog: [Internal] Experimental pointer event dispatching on Android: touch pointer support only, hover events to come later.

Reviewed By: vincentriemer

Differential Revision: D34132667

fbshipit-source-id: 6506d43b4ad16e11b10c3cd23e0231428209411f
This commit is contained in:
Luna Wei
2022-03-30 19:12:42 -07:00
committed by Facebook GitHub Bot
parent ceae48c0f7
commit c50e6b52fe
11 changed files with 633 additions and 6 deletions
@@ -53,6 +53,7 @@ import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.surface.ReactStage;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.JSPointerDispatcher;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactClippingProhibitedView;
@@ -97,6 +98,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private boolean mIsAttachedToInstance;
private boolean mShouldLogContentAppeared;
private @Nullable JSTouchDispatcher mJSTouchDispatcher;
private @Nullable JSPointerDispatcher mJSPointerDispatcher;
private final ReactAndroidHWInputDeviceHelper mAndroidHWInputDeviceHelper =
new ReactAndroidHWInputDeviceHelper(this);
private boolean mWasMeasured = false;
@@ -188,6 +190,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
@Override
public void onChildStartedNativeGesture(MotionEvent ev) {
onChildStartedNativeGesture(null, ev);
}
@Override
public void onChildStartedNativeGesture(View childView, MotionEvent ev) {
if (!isDispatcherReady()) {
return;
}
@@ -197,14 +204,12 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
if (uiManager != null) {
EventDispatcher eventDispatcher = uiManager.getEventDispatcher();
mJSTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher);
if (childView != null && mJSPointerDispatcher != null) {
mJSPointerDispatcher.onChildStartedNativeGesture(childView, ev, eventDispatcher);
}
}
}
@Override
public void onChildStartedNativeGesture(View childView, MotionEvent ev) {
onChildStartedNativeGesture(ev);
}
@Override
public void onChildEndedNativeGesture(View childView, MotionEvent ev) {
if (!isDispatcherReady()) {
@@ -216,6 +221,9 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
if (uiManager != null) {
EventDispatcher eventDispatcher = uiManager.getEventDispatcher();
mJSTouchDispatcher.onChildEndedNativeGesture(ev, eventDispatcher);
if (mJSPointerDispatcher != null) {
mJSPointerDispatcher.onChildEndedNativeGesture();
}
}
}
@@ -230,6 +238,10 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
FLog.w(TAG, "Unable to dispatch touch to JS before the dispatcher is available");
return false;
}
if (ReactFeatureFlags.dispatchPointerEvents && mJSPointerDispatcher == null) {
FLog.w(TAG, "Unable to dispatch pointer events to JS before the dispatcher is available");
return false;
}
return true;
}
@@ -245,6 +257,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
if (shouldDispatchJSTouchEvent(ev)) {
dispatchJSTouchEvent(ev);
}
dispatchJSPointerEvent(ev);
return super.onInterceptTouchEvent(ev);
}
@@ -253,6 +266,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
if (shouldDispatchJSTouchEvent(ev)) {
dispatchJSTouchEvent(ev);
}
dispatchJSPointerEvent(ev);
super.onTouchEvent(ev);
// In case when there is no children interested in handling touch event, we return true from
// the root view in order to receive subsequent events related to that gesture
@@ -312,6 +326,29 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
super.requestChildFocus(child, focused);
}
private void dispatchJSPointerEvent(MotionEvent event) {
if (mReactInstanceManager == null
|| !mIsAttachedToInstance
|| mReactInstanceManager.getCurrentReactContext() == null) {
FLog.w(TAG, "Unable to dispatch touch to JS as the catalyst instance has not been attached");
return;
}
if (mJSPointerDispatcher == null) {
if (!ReactFeatureFlags.dispatchPointerEvents) {
return;
}
FLog.w(TAG, "Unable to dispatch pointer events to JS before the dispatcher is available");
return;
}
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
UIManager uiManager = UIManagerHelper.getUIManager(reactContext, getUIManagerType());
if (uiManager != null) {
EventDispatcher eventDispatcher = uiManager.getEventDispatcher();
mJSPointerDispatcher.handleMotionEvent(event, eventDispatcher);
}
}
private void dispatchJSTouchEvent(MotionEvent event) {
if (mReactInstanceManager == null
|| !mIsAttachedToInstance
@@ -629,6 +666,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
// them. Otherwise, these events might break the states expected by JS.
// Note that this callback was invoked from within the UI thread.
mJSTouchDispatcher = new JSTouchDispatcher(this);
if (ReactFeatureFlags.dispatchPointerEvents) {
mJSPointerDispatcher = new JSPointerDispatcher(this);
}
if (mRootViewEventListener != null) {
mRootViewEventListener.onAttachedToReactInstance(this);
}
@@ -709,6 +751,9 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
/* package */ void simulateAttachForTesting() {
mIsAttachedToInstance = true;
mJSTouchDispatcher = new JSTouchDispatcher(this);
if (ReactFeatureFlags.dispatchPointerEvents) {
mJSPointerDispatcher = new JSPointerDispatcher(this);
}
}
@VisibleForTesting
@@ -112,4 +112,6 @@ public class ReactFeatureFlags {
/** TODO: T113245006 Delete this flag. Enables caching of spannables for text */
public static boolean enableSpannableCache = false;
public static boolean dispatchPointerEvents = false;
}
@@ -443,6 +443,61 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
updateViewAccessibility(view);
}
@Override
public @Nullable Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
Map<String, Object> baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants();
Map<String, Object> eventTypeConstants =
baseEventTypeConstants == null ? new HashMap<String, Object>() : baseEventTypeConstants;
eventTypeConstants.putAll(
MapBuilder.<String, Object>builder()
.put(
"topPointerCancel",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of(
"bubbled", "onPointerCancel", "captured", "onPointerCancelCapture")))
.put(
"topPointerDown",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPointerDown", "captured", "onPointerDownCapture")))
.put(
"topPointerEnter2",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of(
"bubbled",
"onPointerEnter2",
"captured",
"onPointerEnter2Capture",
"skipBubbling",
true)))
.put(
"topPointerLeave2",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of(
"bubbled",
"onPointerLeave2",
"captured",
"onPointerLeave2Capture",
"skipBubbling",
true)))
.put(
"topPointerMove2",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of(
"bubbled", "onPointerMove2", "captured", "onPointerMove2Capture")))
.put(
"topPointerUp",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onPointerUp", "captured", "onPointerUpCapture")))
.build());
return eventTypeConstants;
}
@Override
public @Nullable Map<String, Object> getExportedCustomDirectEventTypeConstants() {
@Nullable
@@ -0,0 +1,186 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.ReactConstants;
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.TouchEvent;
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper;
/**
* JSPointerDispatcher handles dispatching pointer events to JS from RootViews. If you implement
* RootView you need to call handleMotionEvent from onTouchEvent, onInterceptTouchEvent,
* onHoverEvent, onInterceptHoverEvent. It will correctly find the right view to handle the touch
* and also dispatch the appropriate event to JS
*/
public class JSPointerDispatcher {
private final float[] mTargetCoordinates = new float[2];
private int mChildHandlingNativeGesture = -1;
private long mDownStartTime = TouchEvent.UNSET;
private final ViewGroup mRootViewGroup;
private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper =
new TouchEventCoalescingKeyHelper();
public JSPointerDispatcher(ViewGroup viewGroup) {
mRootViewGroup = viewGroup;
}
public void onChildStartedNativeGesture(
View childView, MotionEvent motionEvent, EventDispatcher eventDispatcher) {
if (mChildHandlingNativeGesture != -1 || childView == null) {
// This means we previously had another child start handling this native gesture and now a
// different native parent of that child has decided to intercept the touch stream and handle
// the gesture itself. Example where this can happen: HorizontalScrollView in a ScrollView.
return;
}
int targetTag = findTargetTagAndSetCoordinates(motionEvent);
dispatchCancelEvent(targetTag, motionEvent, eventDispatcher);
mChildHandlingNativeGesture = childView.getId();
}
public void onChildEndedNativeGesture() {
// There should be only one child gesture at any given time. We can safely turn off the flag.
mChildHandlingNativeGesture = -1;
}
public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDispatcher) {
// Ignore if child is handling native gesture
if (mChildHandlingNativeGesture != -1) {
return;
}
boolean supportsHover =
PointerEventHelper.supportsHover(motionEvent.getToolType(motionEvent.getActionIndex()));
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
int action = motionEvent.getActionMasked();
int targetTag = findTargetTagAndSetCoordinates(motionEvent);
// First down pointer
if (action == MotionEvent.ACTION_DOWN) {
// Start a "down" coalescing key
mDownStartTime = motionEvent.getEventTime();
mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime);
if (!supportsHover) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_ENTER, surfaceId, targetTag, motionEvent));
}
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
return;
}
// 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);
if (!supportsHover) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_ENTER, surfaceId, targetTag, motionEvent));
}
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
return;
}
if (action == MotionEvent.ACTION_MOVE) {
int coalescingKey = mTouchEventCoalescingKeyHelper.getCoalescingKey(mDownStartTime);
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_MOVE, surfaceId, targetTag, 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));
return;
}
// Last pointer comes up
if (action == MotionEvent.ACTION_UP) {
// End of a "down" coalescing key
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
mDownStartTime = TouchEvent.UNSET;
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
if (!supportsHover) {
eventDispatcher.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_LEAVE, surfaceId, targetTag, motionEvent));
}
return;
}
if (action == MotionEvent.ACTION_CANCEL) {
dispatchCancelEvent(targetTag, motionEvent, eventDispatcher);
return;
}
FLog.w(
ReactConstants.TAG,
"Warning : Motion Event was ignored. Action="
+ action
+ " Target="
+ targetTag
+ " 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 void dispatchCancelEvent(
int targetTag, 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.
Assertions.assertCondition(
mChildHandlingNativeGesture == -1,
"Expected to not have already sent a cancel for this gesture");
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
Assertions.assertNotNull(eventDispatcher)
.dispatchEvent(
PointerEvent.obtain(
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
eventDispatcher.dispatchEvent(
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, targetTag, motionEvent));
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
mDownStartTime = TouchEvent.UNSET;
}
}
@@ -0,0 +1,180 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager.events;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.core.util.Pools;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.WritableMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PointerEvent extends Event<PointerEvent> {
private static final String TAG = PointerEvent.class.getSimpleName();
private static final int POINTER_EVENTS_POOL_SIZE = 6;
private static final Pools.SynchronizedPool<PointerEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(POINTER_EVENTS_POOL_SIZE);
private static final int UNSET_COALESCING_KEY = -1;
public static PointerEvent obtain(
String eventName, int surfaceId, int viewTag, MotionEvent motionEventToCopy) {
PointerEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new PointerEvent();
}
event.init(eventName, surfaceId, viewTag, Assertions.assertNotNull(motionEventToCopy), 0);
return event;
}
public static PointerEvent obtain(
String eventName,
int surfaceId,
int viewTag,
MotionEvent motionEventToCopy,
int coalescingKey) {
PointerEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new PointerEvent();
}
event.init(
eventName, surfaceId, viewTag, Assertions.assertNotNull(motionEventToCopy), coalescingKey);
return event;
}
private @Nullable MotionEvent mMotionEvent;
private @Nullable String mEventName;
private int mCoalescingKey = UNSET_COALESCING_KEY;
private void init(
String eventName,
int surfaceId,
int viewTag,
MotionEvent motionEventToCopy,
int coalescingKey) {
super.init(surfaceId, viewTag, motionEventToCopy.getEventTime());
mEventName = eventName;
mMotionEvent = MotionEvent.obtain(motionEventToCopy);
mCoalescingKey = coalescingKey;
}
private PointerEvent() {}
@Override
public String getEventName() {
return mEventName;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
// Skip legacy stuff for now?
return;
}
@Override
public void onDispose() {
MotionEvent motionEvent = mMotionEvent;
mMotionEvent = null;
if (motionEvent != null) {
motionEvent.recycle();
}
// Either `this` is in the event pool, or motionEvent
// is null. It is in theory not possible for a PointerEvent to
// be in the EVENTS_POOL but for motionEvent to be null. However,
// out of an abundance of caution and to avoid memory leaks or
// other crashes at all costs, we attempt to release here and log
// a soft exception here if release throws an IllegalStateException
// due to `this` being over-released. This may indicate that there is
// a logic error in our events system or pooling mechanism.
try {
EVENTS_POOL.release(this);
} catch (IllegalStateException e) {
ReactSoftExceptionLogger.logSoftException(TAG, e);
}
}
private ArrayList<WritableMap> createPointerEvents() {
MotionEvent motionEvent = mMotionEvent;
ArrayList<WritableMap> pointerEvents = new ArrayList<>();
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
pointerEvents.add(this.createPointerEvent(index));
}
return pointerEvents;
}
private WritableMap createPointerEvent(int index) {
WritableMap pointerEvent = Arguments.createMap();
pointerEvent.putDouble("pointerId", mMotionEvent.getPointerId(index));
pointerEvent.putDouble("pressure", mMotionEvent.getPressure(index));
pointerEvent.putString(
"pointerType", PointerEventHelper.getW3CPointerType(mMotionEvent.getToolType(index)));
// Client refers to upper left edge of the content area (viewport)
// We define the viewport to be ReactRootView
pointerEvent.putDouble("clientX", mMotionEvent.getX(index));
pointerEvent.putDouble("clientY", mMotionEvent.getY(index));
pointerEvent.putInt("target", this.getViewTag());
pointerEvent.putDouble("timestamp", this.getTimestampMs());
return pointerEvent;
}
@Override
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
if (mMotionEvent == null) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new IllegalStateException(
"Cannot dispatch a Pointer that has no MotionEvent; the PointerEvehas been recycled"));
return;
}
List<WritableMap> relevantPointerEventData = null;
int activePointerIndex = mMotionEvent.getActionIndex();
switch (mEventName) {
// Cases where all pointer info is relevant
case PointerEventHelper.POINTER_MOVE:
case PointerEventHelper.POINTER_CANCEL:
relevantPointerEventData = createPointerEvents();
break;
// Cases where only the "active" pointer info is relevant
case PointerEventHelper.POINTER_ENTER:
case PointerEventHelper.POINTER_DOWN:
case PointerEventHelper.POINTER_UP:
case PointerEventHelper.POINTER_LEAVE:
relevantPointerEventData = Arrays.asList(createPointerEvent(activePointerIndex));
break;
}
if (relevantPointerEventData == null) {
// No relevant MotionEvent to dispatch
return;
}
boolean shouldCopy = relevantPointerEventData.size() > 1;
for (WritableMap pointerEventData : relevantPointerEventData) {
WritableMap eventData = shouldCopy ? pointerEventData.copy() : pointerEventData;
rctEventEmitter.receiveEvent(
this.getSurfaceId(),
this.getViewTag(),
mEventName,
mCoalescingKey != UNSET_COALESCING_KEY,
mCoalescingKey,
eventData,
PointerEventHelper.getEventCategory(mEventName));
}
}
}
@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager.events;
import android.view.MotionEvent;
/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
public class PointerEventHelper {
public static final String POINTER_TYPE_TOUCH = "touch";
public static final String POINTER_TYPE_PEN = "pen";
public static final String POINTER_TYPE_MOUSE = "mouse";
public static final String POINTER_TYPE_UNKNOWN = "";
public static final String POINTER_CANCEL = "topPointerCancel";
public static final String POINTER_DOWN = "topPointerDown";
public static final String POINTER_ENTER = "topPointerEnter2";
public static final String POINTER_LEAVE = "topPointerLeave2";
public static final String POINTER_MOVE = "topPointerMove2";
public static final String POINTER_UP = "topPointerUp";
public static String getW3CPointerType(final int toolType) {
// https://www.w3.org/TR/pointerevents3/#dom-pointerevent-pointertype
switch (toolType) {
case MotionEvent.TOOL_TYPE_FINGER:
return POINTER_TYPE_TOUCH;
case MotionEvent.TOOL_TYPE_STYLUS:
return POINTER_TYPE_PEN;
case MotionEvent.TOOL_TYPE_MOUSE:
return POINTER_TYPE_MOUSE;
}
return POINTER_TYPE_UNKNOWN;
}
public static int getEventCategory(String pointerEventType) {
if (pointerEventType == null) {
return EventCategoryDef.UNSPECIFIED;
}
// Following:
// https://github.com/facebook/react/blob/main/packages/react-dom/src/events/ReactDOMEventListener.js#L435-L437
switch (pointerEventType) {
case POINTER_DOWN:
case POINTER_CANCEL:
case POINTER_UP:
return EventCategoryDef.DISCRETE;
case POINTER_MOVE:
case POINTER_ENTER:
case POINTER_LEAVE:
return EventCategoryDef.CONTINUOUS;
}
return EventCategoryDef.UNSPECIFIED;
}
public static boolean supportsHover(final int toolType) {
String pointerType = getW3CPointerType(toolType);
if (pointerType.equals(POINTER_TYPE_MOUSE)) {
return true;
} else if (pointerType.equals(POINTER_TYPE_PEN)) {
return true; // true?
} else if (pointerType.equals(POINTER_TYPE_TOUCH)) {
return false;
}
return false;
}
}
@@ -24,6 +24,7 @@ rn_android_library(
react_native_root_target(":generated_components_java-FBReactNativeComponentSpec"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/touch:touch"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
@@ -34,7 +34,9 @@ import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.uimanager.FabricViewStateManager;
import com.facebook.react.uimanager.JSPointerDispatcher;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.RootView;
@@ -413,9 +415,13 @@ public class ReactModalHostView extends ViewGroup
private final FabricViewStateManager mFabricViewStateManager = new FabricViewStateManager();
private final JSTouchDispatcher mJSTouchDispatcher = new JSTouchDispatcher(this);
@Nullable private JSPointerDispatcher mJSPointerDispatcher;
public DialogRootViewGroup(Context context) {
super(context);
if (ReactFeatureFlags.dispatchPointerEvents) {
mJSPointerDispatcher = new JSPointerDispatcher(this);
}
}
private void setEventDispatcher(EventDispatcher eventDispatcher) {
@@ -515,12 +521,18 @@ public class ReactModalHostView extends ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
mJSTouchDispatcher.handleTouchEvent(event, mEventDispatcher);
if (mJSPointerDispatcher != null) {
mJSPointerDispatcher.handleMotionEvent(event, mEventDispatcher);
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mJSTouchDispatcher.handleTouchEvent(event, mEventDispatcher);
if (mJSPointerDispatcher != null) {
mJSPointerDispatcher.handleMotionEvent(event, mEventDispatcher);
}
super.onTouchEvent(event);
// In case when there is no children interested in handling touch event, we return true from
// the root view in order to receive subsequent events related to that gesture
@@ -529,17 +541,23 @@ public class ReactModalHostView extends ViewGroup
@Override
public void onChildStartedNativeGesture(MotionEvent ev) {
mJSTouchDispatcher.onChildStartedNativeGesture(ev, mEventDispatcher);
this.onChildStartedNativeGesture(null, ev);
}
@Override
public void onChildStartedNativeGesture(View childView, MotionEvent ev) {
mJSTouchDispatcher.onChildStartedNativeGesture(ev, mEventDispatcher);
if (mJSPointerDispatcher != null) {
mJSPointerDispatcher.onChildStartedNativeGesture(childView, ev, mEventDispatcher);
}
}
@Override
public void onChildEndedNativeGesture(View childView, MotionEvent ev) {
mJSTouchDispatcher.onChildEndedNativeGesture(ev, mEventDispatcher);
if (mJSPointerDispatcher != null) {
mJSPointerDispatcher.onChildEndedNativeGesture();
}
}
@Override