Skip to content

Commit 0c2ee5d

Browse files
Dave Millerfacebook-github-bot-3
authored andcommitted
Update Android Touch events
Summary: public This moves Android touch events to parity with iOS. The locationX,Y value passed to js now is view relative to the view that is handling the touch. The pageX,Y is now relative to the root view. Reviewed By: andreicoman11 Differential Revision: D2670028 fb-gh-sync-id: 5438640d6c78633629b9a308a59cc306bb07815e
1 parent e0d53e1 commit 0c2ee5d

4 files changed

Lines changed: 125 additions & 35 deletions

File tree

ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
6161
private @Nullable String mJSModuleName;
6262
private @Nullable Bundle mLaunchOptions;
6363
private int mTargetTag = -1;
64+
// Note mTargetCoordinates are Y,X
65+
// TODO: t9136625 tracks moving to X,Y
66+
private final float[] mTargetCoordinates = new float[2];
6467
private boolean mChildIsHandlingNativeGesture = false;
6568
private boolean mWasMeasured = false;
6669
private boolean mAttachScheduled = false;
@@ -143,9 +146,19 @@ private void handleTouchEvent(MotionEvent ev) {
143146
// {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling
144147
// this gesture
145148
mChildIsHandlingNativeGesture = false;
146-
mTargetTag = TouchTargetHelper.findTargetTagForTouch(ev.getY(), ev.getX(), this);
149+
mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
150+
ev.getY(),
151+
ev.getX(),
152+
this,
153+
mTargetCoordinates);
147154
eventDispatcher.dispatchEvent(
148-
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
155+
TouchEvent.obtain(
156+
mTargetTag,
157+
SystemClock.uptimeMillis(),
158+
TouchEventType.START,
159+
ev,
160+
mTargetCoordinates[1],
161+
mTargetCoordinates[0]));
149162
} else if (mChildIsHandlingNativeGesture) {
150163
// If the touch was intercepted by a child, we've already sent a cancel event to JS for this
151164
// gesture, so we shouldn't send any more touches related to it.
@@ -161,20 +174,44 @@ private void handleTouchEvent(MotionEvent ev) {
161174
// End of the gesture. We reset target tag to -1 and expect no further event associated with
162175
// this gesture.
163176
eventDispatcher.dispatchEvent(
164-
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
177+
TouchEvent.obtain(
178+
mTargetTag,
179+
SystemClock.uptimeMillis(),
180+
TouchEventType.END,
181+
ev,
182+
mTargetCoordinates[1],
183+
mTargetCoordinates[0]));
165184
mTargetTag = -1;
166185
} else if (action == MotionEvent.ACTION_MOVE) {
167186
// Update pointer position for current gesture
168187
eventDispatcher.dispatchEvent(
169-
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.MOVE, ev));
188+
TouchEvent.obtain(
189+
mTargetTag,
190+
SystemClock.uptimeMillis(),
191+
TouchEventType.MOVE,
192+
ev,
193+
mTargetCoordinates[1],
194+
mTargetCoordinates[0]));
170195
} else if (action == MotionEvent.ACTION_POINTER_DOWN) {
171196
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
172197
eventDispatcher.dispatchEvent(
173-
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
198+
TouchEvent.obtain(
199+
mTargetTag,
200+
SystemClock.uptimeMillis(),
201+
TouchEventType.START,
202+
ev,
203+
mTargetCoordinates[1],
204+
mTargetCoordinates[0]));
174205
} else if (action == MotionEvent.ACTION_POINTER_UP) {
175206
// Exactly onw of the pointers goes up
176207
eventDispatcher.dispatchEvent(
177-
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
208+
TouchEvent.obtain(
209+
mTargetTag,
210+
SystemClock.uptimeMillis(),
211+
TouchEventType.END,
212+
ev,
213+
mTargetCoordinates[1],
214+
mTargetCoordinates[0]));
178215
} else if (action == MotionEvent.ACTION_CANCEL) {
179216
dispatchCancelEvent(ev);
180217
mTargetTag = -1;
@@ -223,7 +260,9 @@ private void dispatchCancelEvent(MotionEvent androidEvent) {
223260
mTargetTag,
224261
SystemClock.uptimeMillis(),
225262
TouchEventType.CANCEL,
226-
androidEvent));
263+
androidEvent,
264+
mTargetCoordinates[1],
265+
mTargetCoordinates[0]));
227266
}
228267

229268
@Override

ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,34 @@ public static int findTargetTagForTouch(
3939
float eventY,
4040
float eventX,
4141
ViewGroup viewGroup) {
42+
return findTargetTagAndCoordinatesForTouch(eventY, eventX, viewGroup, mEventCoords);
43+
}
44+
45+
/**
46+
* Find touch event target view within the provided container given the coordinates provided
47+
* via {@link MotionEvent}.
48+
*
49+
* @param eventY the Y screen coordinate of the touch location
50+
* @param eventX the X screen coordinate of the touch location
51+
* @param viewGroup the container view to traverse
52+
* @param viewCoords an out parameter that will return the Y,X value in the target view
53+
* @return the react tag ID of the child view that should handle the event
54+
*/
55+
public static int findTargetTagAndCoordinatesForTouch(
56+
float eventY,
57+
float eventX,
58+
ViewGroup viewGroup,
59+
float[] viewCoords) {
4260
UiThreadUtil.assertOnUiThread();
4361
int targetTag = viewGroup.getId();
4462
// Store eventCoords in array so that they are modified to be relative to the targetView found.
45-
float[] eventCoords = mEventCoords;
46-
eventCoords[0] = eventY;
47-
eventCoords[1] = eventX;
48-
View nativeTargetView = findTouchTargetView(eventCoords, viewGroup);
63+
viewCoords[0] = eventY;
64+
viewCoords[1] = eventX;
65+
View nativeTargetView = findTouchTargetView(viewCoords, viewGroup);
4966
if (nativeTargetView != null) {
5067
View reactTargetView = findClosestReactAncestor(nativeTargetView);
5168
if (reactTargetView != null) {
52-
targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]);
69+
targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
5370
}
5471
}
5572
return targetTag;

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,35 @@ public static TouchEvent obtain(
3535
int viewTag,
3636
long timestampMs,
3737
TouchEventType touchEventType,
38-
MotionEvent motionEventToCopy) {
38+
MotionEvent motionEventToCopy,
39+
float viewX,
40+
float viewY) {
3941
TouchEvent event = EVENTS_POOL.acquire();
4042
if (event == null) {
4143
event = new TouchEvent();
4244
}
43-
event.init(viewTag, timestampMs, touchEventType, motionEventToCopy);
45+
event.init(viewTag, timestampMs, touchEventType, motionEventToCopy, viewX, viewY);
4446
return event;
4547
}
4648

4749
private @Nullable MotionEvent mMotionEvent;
4850
private @Nullable TouchEventType mTouchEventType;
4951
private short mCoalescingKey;
5052

53+
// Coordinates in the ViewTag coordinate space
54+
private float mViewX;
55+
private float mViewY;
56+
5157
private TouchEvent() {
5258
}
5359

5460
private void init(
5561
int viewTag,
5662
long timestampMs,
5763
TouchEventType touchEventType,
58-
MotionEvent motionEventToCopy) {
64+
MotionEvent motionEventToCopy,
65+
float viewX,
66+
float viewY) {
5967
super.init(viewTag, timestampMs);
6068

6169
short coalescingKey = 0;
@@ -84,6 +92,8 @@ private void init(
8492
mTouchEventType = touchEventType;
8593
mMotionEvent = MotionEvent.obtain(motionEventToCopy);
8694
mCoalescingKey = coalescingKey;
95+
mViewX = viewX;
96+
mViewY = viewY;
8797
}
8898

8999
@Override
@@ -126,6 +136,19 @@ public void dispatch(RCTEventEmitter rctEventEmitter) {
126136
rctEventEmitter,
127137
Assertions.assertNotNull(mTouchEventType),
128138
getViewTag(),
129-
Assertions.assertNotNull(mMotionEvent));
139+
this);
140+
}
141+
142+
public MotionEvent getMotionEvent() {
143+
Assertions.assertNotNull(mMotionEvent);
144+
return mMotionEvent;
145+
}
146+
147+
public float getViewX() {
148+
return mViewX;
149+
}
150+
151+
public float getViewY() {
152+
return mViewY;
130153
}
131154
}

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
private static final String TIMESTAMP_KEY = "timeStamp";
2828
private static final String POINTER_IDENTIFIER_KEY = "identifier";
2929

30-
// TODO(7351435): remove when we standardize touchEvent payload, since iOS uses locationXYZ but
31-
// Android uses pageXYZ. As a temporary solution, Android currently sends both.
3230
private static final String LOCATION_X_KEY = "locationX";
3331
private static final String LOCATION_Y_KEY = "locationY";
3432

@@ -37,23 +35,35 @@
3735
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
3836
* target view id associated with current gesture.
3937
*/
40-
private static WritableArray createsPointersArray(int reactTarget, MotionEvent event) {
38+
private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) {
4139
WritableArray touches = Arguments.createArray();
40+
MotionEvent motionEvent = event.getMotionEvent();
4241

43-
// Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the
44-
// pointer at index 0. We use those value to calculate "raw" coordinates for other pointers
45-
float offsetX = event.getRawX() - event.getX();
46-
float offsetY = event.getRawY() - event.getY();
42+
// Calculate the coordinates for the target view.
43+
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
44+
// The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
45+
// Subtracting them allows us to get the coordinates of the target view's top left corner
46+
// We then use this when computing the view specific touches below
47+
// Since only one view is actually handling even multiple touches, the values are all relative
48+
// to this one target view.
49+
float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
50+
float targetViewCoordinateY = motionEvent.getY() - event.getViewY();
4751

48-
for (int index = 0; index < event.getPointerCount(); index++) {
52+
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
4953
WritableMap touch = Arguments.createMap();
50-
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX));
51-
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY));
52-
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index)));
53-
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index)));
54+
// pageX,Y values are relative to the RootReactView
55+
// the motionEvent already contains coordinates in that view
56+
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
57+
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
58+
// locationX,Y values are relative to the target view
59+
// To compute the values for the view, we subtract that views location from the event X,Y
60+
float locationX = motionEvent.getX(index) - targetViewCoordinateX;
61+
float locationY = motionEvent.getY(index) - targetViewCoordinateY;
62+
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
63+
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
5464
touch.putInt(TARGET_KEY, reactTarget);
55-
touch.putDouble(TIMESTAMP_KEY, event.getEventTime());
56-
touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index));
65+
touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime());
66+
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
5767
touches.pushMap(touch);
5868
}
5969

@@ -67,25 +77,26 @@ private static WritableArray createsPointersArray(int reactTarget, MotionEvent e
6777
* @param rctEventEmitter Event emitter used to execute JS module call
6878
* @param type type of the touch event (see {@link TouchEventType})
6979
* @param reactTarget target view react id associated with this gesture
70-
* @param androidMotionEvent native touch event to read pointers count and coordinates from
80+
* @param touchEvent native touch event to read pointers count and coordinates from
7181
*/
7282
public static void sendTouchEvent(
7383
RCTEventEmitter rctEventEmitter,
7484
TouchEventType type,
7585
int reactTarget,
76-
MotionEvent androidMotionEvent) {
86+
TouchEvent touchEvent) {
7787

78-
WritableArray pointers = createsPointersArray(reactTarget, androidMotionEvent);
88+
WritableArray pointers = createsPointersArray(reactTarget, touchEvent);
89+
MotionEvent motionEvent = touchEvent.getMotionEvent();
7990

8091
// For START and END events send only index of the pointer that is associated with that event
8192
// For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
8293
WritableArray changedIndices = Arguments.createArray();
8394
if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
84-
for (int i = 0; i < androidMotionEvent.getPointerCount(); i++) {
95+
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
8596
changedIndices.pushInt(i);
8697
}
8798
} else if (type == TouchEventType.START || type == TouchEventType.END) {
88-
changedIndices.pushInt(androidMotionEvent.getActionIndex());
99+
changedIndices.pushInt(motionEvent.getActionIndex());
89100
} else {
90101
throw new RuntimeException("Unknown touch type: " + type);
91102
}

0 commit comments

Comments
 (0)