From d7eac8a228d00b1c7531165544fb09bf7a030658 Mon Sep 17 00:00:00 2001 From: Vincent Riemer <1398555+vincentriemer@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:32:42 -0700 Subject: [PATCH] Fix proper pointercancel/leave behavior when scrolling (#39081) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39081 Changelog: [Internal] - Fix pointer cancel/leave behavior when a child view scrolls Differential Revision: D48472486 fbshipit-source-id: 8c4d269f0f73c2f0b356928443f41349cd4f724b --- .../react/uimanager/JSPointerDispatcher.java | 102 ++++++------------ 1 file changed, 34 insertions(+), 68 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java index d65ecbea8bcfbc..0367329068fabd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java @@ -61,6 +61,7 @@ public JSPointerDispatcher(ViewGroup 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 @@ -69,48 +70,24 @@ public void onChildStartedNativeGesture( } MotionEvent motionInRoot = convertMotionToRootFrame(childView, motionEvent); + motionInRoot.setAction(MotionEvent.ACTION_CANCEL); + handleMotionEvent(motionInRoot, eventDispatcher, false); - dispatchCancelEventForTarget(childView, motionInRoot, eventDispatcher); mChildHandlingNativeGesture = childView.getId(); - - // clear "previous" state since interaction was canceled - resetPreviousStateForMotionEvent(motionEvent); } private MotionEvent convertMotionToRootFrame(View childView, MotionEvent childMotion) { MotionEvent motionInRoot = MotionEvent.obtain(childMotion); - final int cX = (int) childMotion.getX(); - final int cY = (int) childMotion.getY(); - Rect childCoords = new Rect(cX, cY, cX + 1, cY + 1); - // note: should be safe since we're only looking at descendants of the root - mRootViewGroup.offsetDescendantRectToMyCoords(childView, childCoords); - motionInRoot.setLocation(childCoords.left, childCoords.top); - - return motionInRoot; - } - private void updatePreviousStateFromEvent(MotionEvent event, PointerEventState eventState) { - // Caching the event state so we have a new "last" - // note: we need to make copies here as the eventState may be accessed later and we don't want - // mutations of these instance vars to affect it - mLastHitPathByPointerId = new HashMap<>(eventState.getHitPathByPointerId()); - mLastEventCoordinatesByPointerId = new HashMap<>(eventState.getEventCoordinatesByPointerId()); - mLastButtonState = event.getButtonState(); + int[] location = new int[2]; + mRootViewGroup.getLocationOnScreen(location); + float screenX = childMotion.getRawX(); + float screenY = childMotion.getRawY(); + float clientX = screenX - location[0]; + float clientY = screenY - location[1]; + motionInRoot.setLocation(clientX, clientY); - // Clean up any stale pointerIds - Set allPointerIds = mLastEventCoordinatesByPointerId.keySet(); - mHoveringPointerIds.retainAll(allPointerIds); - } - - private void resetPreviousStateForMotionEvent(MotionEvent event) { - int activePointerId = event.getPointerId(event.getActionIndex()); - if (mLastHitPathByPointerId != null) { - mLastHitPathByPointerId.remove(activePointerId); - } - if (mLastEventCoordinatesByPointerId != null) { - mLastEventCoordinatesByPointerId.remove(activePointerId); - } - mLastButtonState = 0; + return motionInRoot; } public void onChildEndedNativeGesture() { @@ -391,6 +368,7 @@ public void handleMotionEvent( break; case MotionEvent.ACTION_CANCEL: dispatchCancelEventForTarget(activeTargetView, eventState, motionEvent, eventDispatcher); + handleHitStateDivergence(-1, eventState, motionEvent, eventDispatcher); break; case MotionEvent.ACTION_HOVER_ENTER: // Ignore these events as enters will be calculated from HOVER_MOVE @@ -409,8 +387,6 @@ public void handleMotionEvent( "Motion Event was ignored. Action=" + action + " Target=" + activeTargetTag); return; } - - updatePreviousStateFromEvent(motionEvent, eventState); } private static boolean isAnyoneListeningForBubblingEvent( @@ -485,7 +461,10 @@ private void handleHitStateDivergence( MotionEvent motionEvent, EventDispatcher eventDispatcher) { int activePointerId = eventState.getActivePointerId(); - List activeHitPath = eventState.getHitPathByPointerId().get(activePointerId); + List activeHitPath = + targetTag != -1 + ? eventState.getHitPathByPointerId().get(activePointerId) + : new ArrayList(); List lastHitPath = mLastHitPathByPointerId != null && mLastHitPathByPointerId.containsKey(activePointerId) ? mLastHitPathByPointerId.get(activePointerId) @@ -580,6 +559,24 @@ private void handleHitStateDivergence( eventDispatcher); } } + + Map> nextHitPathByPointerId = + new HashMap<>(eventState.getHitPathByPointerId()); + Map nextEventCoordinatesByPointerId = + new HashMap<>(eventState.getEventCoordinatesByPointerId()); + + if (targetTag == -1) { + nextHitPathByPointerId.remove(activePointerId); + nextEventCoordinatesByPointerId.remove(activePointerId); + } + + mLastHitPathByPointerId = nextHitPathByPointerId; + mLastEventCoordinatesByPointerId = nextEventCoordinatesByPointerId; + mLastButtonState = motionEvent.getButtonState(); + + // Clean up any stale pointerIds + Set allPointerIds = mLastEventCoordinatesByPointerId.keySet(); + mHoveringPointerIds.retainAll(allPointerIds); } private void onMove( @@ -603,18 +600,6 @@ private void onMove( } } - private void dispatchCancelEventForTarget( - View targetView, MotionEvent motionEvent, EventDispatcher eventDispatcher) { - Assertions.assertCondition( - mChildHandlingNativeGesture == -1, - "Expected to not have already sent a cancel for this gesture"); - - int activeIndex = motionEvent.getActionIndex(); - int activePointerId = motionEvent.getPointerId(activeIndex); - PointerEventState eventState = createEventState(activePointerId, motionEvent); - dispatchCancelEventForTarget(targetView, eventState, motionEvent, eventDispatcher); - } - private void dispatchCancelEventForTarget( View targetView, PointerEventState eventState, @@ -650,26 +635,7 @@ private void dispatchCancelEventForTarget( motionEvent)); } - // Need to fire pointer out + pointer leave here as well: - // https://w3c.github.io/pointerevents/#dfn-suppress-a-pointer-event-stream - List outViewTargets = - filterByShouldDispatch(activeHitPath, EVENT.OUT, EVENT.OUT_CAPTURE, false); - List leaveViewTargets = - filterByShouldDispatch(activeHitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false); - - // dispatch from target -> root - dispatchEventForViewTargets( - PointerEventHelper.POINTER_OUT, eventState, motionEvent, outViewTargets, eventDispatcher); - dispatchEventForViewTargets( - PointerEventHelper.POINTER_LEAVE, - eventState, - motionEvent, - leaveViewTargets, - eventDispatcher); - incrementCoalescingKey(); - mHoveringPointerIds.remove(mPrimaryPointerId); - mHoveringPointerIds.remove(activePointerId); mPrimaryPointerId = UNSET_POINTER_ID; } }