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
This commit is contained in:
parent
e0d53e1c48
commit
0c2ee5d480
|
@ -61,6 +61,9 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
|
|||
private @Nullable String mJSModuleName;
|
||||
private @Nullable Bundle mLaunchOptions;
|
||||
private int mTargetTag = -1;
|
||||
// Note mTargetCoordinates are Y,X
|
||||
// TODO: t9136625 tracks moving to X,Y
|
||||
private final float[] mTargetCoordinates = new float[2];
|
||||
private boolean mChildIsHandlingNativeGesture = false;
|
||||
private boolean mWasMeasured = false;
|
||||
private boolean mAttachScheduled = false;
|
||||
|
@ -143,9 +146,19 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
|
|||
// {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling
|
||||
// this gesture
|
||||
mChildIsHandlingNativeGesture = false;
|
||||
mTargetTag = TouchTargetHelper.findTargetTagForTouch(ev.getY(), ev.getX(), this);
|
||||
mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
|
||||
ev.getY(),
|
||||
ev.getX(),
|
||||
this,
|
||||
mTargetCoordinates);
|
||||
eventDispatcher.dispatchEvent(
|
||||
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
|
||||
TouchEvent.obtain(
|
||||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.START,
|
||||
ev,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
} else if (mChildIsHandlingNativeGesture) {
|
||||
// If the touch was intercepted by a child, we've already sent a cancel event to JS for this
|
||||
// gesture, so we shouldn't send any more touches related to it.
|
||||
|
@ -161,20 +174,44 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
|
|||
// End of the gesture. We reset target tag to -1 and expect no further event associated with
|
||||
// this gesture.
|
||||
eventDispatcher.dispatchEvent(
|
||||
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
|
||||
TouchEvent.obtain(
|
||||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.END,
|
||||
ev,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
mTargetTag = -1;
|
||||
} else if (action == MotionEvent.ACTION_MOVE) {
|
||||
// Update pointer position for current gesture
|
||||
eventDispatcher.dispatchEvent(
|
||||
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.MOVE, ev));
|
||||
TouchEvent.obtain(
|
||||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.MOVE,
|
||||
ev,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
} else if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
|
||||
eventDispatcher.dispatchEvent(
|
||||
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
|
||||
TouchEvent.obtain(
|
||||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.START,
|
||||
ev,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
} else if (action == MotionEvent.ACTION_POINTER_UP) {
|
||||
// Exactly onw of the pointers goes up
|
||||
eventDispatcher.dispatchEvent(
|
||||
TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
|
||||
TouchEvent.obtain(
|
||||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.END,
|
||||
ev,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
} else if (action == MotionEvent.ACTION_CANCEL) {
|
||||
dispatchCancelEvent(ev);
|
||||
mTargetTag = -1;
|
||||
|
@ -223,7 +260,9 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
|
|||
mTargetTag,
|
||||
SystemClock.uptimeMillis(),
|
||||
TouchEventType.CANCEL,
|
||||
androidEvent));
|
||||
androidEvent,
|
||||
mTargetCoordinates[1],
|
||||
mTargetCoordinates[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -39,17 +39,34 @@ public class TouchTargetHelper {
|
|||
float eventY,
|
||||
float eventX,
|
||||
ViewGroup viewGroup) {
|
||||
return findTargetTagAndCoordinatesForTouch(eventY, eventX, viewGroup, mEventCoords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find touch event target view within the provided container given the coordinates provided
|
||||
* via {@link MotionEvent}.
|
||||
*
|
||||
* @param eventY the Y screen coordinate of the touch location
|
||||
* @param eventX the X screen coordinate of the touch location
|
||||
* @param viewGroup the container view to traverse
|
||||
* @param viewCoords an out parameter that will return the Y,X value in the target view
|
||||
* @return the react tag ID of the child view that should handle the event
|
||||
*/
|
||||
public static int findTargetTagAndCoordinatesForTouch(
|
||||
float eventY,
|
||||
float eventX,
|
||||
ViewGroup viewGroup,
|
||||
float[] viewCoords) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
int targetTag = viewGroup.getId();
|
||||
// Store eventCoords in array so that they are modified to be relative to the targetView found.
|
||||
float[] eventCoords = mEventCoords;
|
||||
eventCoords[0] = eventY;
|
||||
eventCoords[1] = eventX;
|
||||
View nativeTargetView = findTouchTargetView(eventCoords, viewGroup);
|
||||
viewCoords[0] = eventY;
|
||||
viewCoords[1] = eventX;
|
||||
View nativeTargetView = findTouchTargetView(viewCoords, viewGroup);
|
||||
if (nativeTargetView != null) {
|
||||
View reactTargetView = findClosestReactAncestor(nativeTargetView);
|
||||
if (reactTargetView != null) {
|
||||
targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]);
|
||||
targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
|
||||
}
|
||||
}
|
||||
return targetTag;
|
||||
|
|
|
@ -35,12 +35,14 @@ public class TouchEvent extends Event<TouchEvent> {
|
|||
int viewTag,
|
||||
long timestampMs,
|
||||
TouchEventType touchEventType,
|
||||
MotionEvent motionEventToCopy) {
|
||||
MotionEvent motionEventToCopy,
|
||||
float viewX,
|
||||
float viewY) {
|
||||
TouchEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new TouchEvent();
|
||||
}
|
||||
event.init(viewTag, timestampMs, touchEventType, motionEventToCopy);
|
||||
event.init(viewTag, timestampMs, touchEventType, motionEventToCopy, viewX, viewY);
|
||||
return event;
|
||||
}
|
||||
|
||||
|
@ -48,6 +50,10 @@ public class TouchEvent extends Event<TouchEvent> {
|
|||
private @Nullable TouchEventType mTouchEventType;
|
||||
private short mCoalescingKey;
|
||||
|
||||
// Coordinates in the ViewTag coordinate space
|
||||
private float mViewX;
|
||||
private float mViewY;
|
||||
|
||||
private TouchEvent() {
|
||||
}
|
||||
|
||||
|
@ -55,7 +61,9 @@ public class TouchEvent extends Event<TouchEvent> {
|
|||
int viewTag,
|
||||
long timestampMs,
|
||||
TouchEventType touchEventType,
|
||||
MotionEvent motionEventToCopy) {
|
||||
MotionEvent motionEventToCopy,
|
||||
float viewX,
|
||||
float viewY) {
|
||||
super.init(viewTag, timestampMs);
|
||||
|
||||
short coalescingKey = 0;
|
||||
|
@ -84,6 +92,8 @@ public class TouchEvent extends Event<TouchEvent> {
|
|||
mTouchEventType = touchEventType;
|
||||
mMotionEvent = MotionEvent.obtain(motionEventToCopy);
|
||||
mCoalescingKey = coalescingKey;
|
||||
mViewX = viewX;
|
||||
mViewY = viewY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -126,6 +136,19 @@ public class TouchEvent extends Event<TouchEvent> {
|
|||
rctEventEmitter,
|
||||
Assertions.assertNotNull(mTouchEventType),
|
||||
getViewTag(),
|
||||
Assertions.assertNotNull(mMotionEvent));
|
||||
this);
|
||||
}
|
||||
|
||||
public MotionEvent getMotionEvent() {
|
||||
Assertions.assertNotNull(mMotionEvent);
|
||||
return mMotionEvent;
|
||||
}
|
||||
|
||||
public float getViewX() {
|
||||
return mViewX;
|
||||
}
|
||||
|
||||
public float getViewY() {
|
||||
return mViewY;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import com.facebook.react.uimanager.PixelUtil;
|
|||
private static final String TIMESTAMP_KEY = "timeStamp";
|
||||
private static final String POINTER_IDENTIFIER_KEY = "identifier";
|
||||
|
||||
// TODO(7351435): remove when we standardize touchEvent payload, since iOS uses locationXYZ but
|
||||
// Android uses pageXYZ. As a temporary solution, Android currently sends both.
|
||||
private static final String LOCATION_X_KEY = "locationX";
|
||||
private static final String LOCATION_Y_KEY = "locationY";
|
||||
|
||||
|
@ -37,23 +35,35 @@ import com.facebook.react.uimanager.PixelUtil;
|
|||
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
|
||||
* target view id associated with current gesture.
|
||||
*/
|
||||
private static WritableArray createsPointersArray(int reactTarget, MotionEvent event) {
|
||||
private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) {
|
||||
WritableArray touches = Arguments.createArray();
|
||||
MotionEvent motionEvent = event.getMotionEvent();
|
||||
|
||||
// Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the
|
||||
// pointer at index 0. We use those value to calculate "raw" coordinates for other pointers
|
||||
float offsetX = event.getRawX() - event.getX();
|
||||
float offsetY = event.getRawY() - event.getY();
|
||||
// Calculate the coordinates for the target view.
|
||||
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
|
||||
// The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
|
||||
// Subtracting them allows us to get the coordinates of the target view's top left corner
|
||||
// We then use this when computing the view specific touches below
|
||||
// Since only one view is actually handling even multiple touches, the values are all relative
|
||||
// to this one target view.
|
||||
float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
|
||||
float targetViewCoordinateY = motionEvent.getY() - event.getViewY();
|
||||
|
||||
for (int index = 0; index < event.getPointerCount(); index++) {
|
||||
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
|
||||
WritableMap touch = Arguments.createMap();
|
||||
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX));
|
||||
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY));
|
||||
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index)));
|
||||
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index)));
|
||||
// pageX,Y values are relative to the RootReactView
|
||||
// the motionEvent already contains coordinates in that view
|
||||
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
|
||||
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
|
||||
// locationX,Y values are relative to the target view
|
||||
// To compute the values for the view, we subtract that views location from the event X,Y
|
||||
float locationX = motionEvent.getX(index) - targetViewCoordinateX;
|
||||
float locationY = motionEvent.getY(index) - targetViewCoordinateY;
|
||||
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
|
||||
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
|
||||
touch.putInt(TARGET_KEY, reactTarget);
|
||||
touch.putDouble(TIMESTAMP_KEY, event.getEventTime());
|
||||
touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index));
|
||||
touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime());
|
||||
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
|
||||
touches.pushMap(touch);
|
||||
}
|
||||
|
||||
|
@ -67,25 +77,26 @@ import com.facebook.react.uimanager.PixelUtil;
|
|||
* @param rctEventEmitter Event emitter used to execute JS module call
|
||||
* @param type type of the touch event (see {@link TouchEventType})
|
||||
* @param reactTarget target view react id associated with this gesture
|
||||
* @param androidMotionEvent native touch event to read pointers count and coordinates from
|
||||
* @param touchEvent native touch event to read pointers count and coordinates from
|
||||
*/
|
||||
public static void sendTouchEvent(
|
||||
RCTEventEmitter rctEventEmitter,
|
||||
TouchEventType type,
|
||||
int reactTarget,
|
||||
MotionEvent androidMotionEvent) {
|
||||
TouchEvent touchEvent) {
|
||||
|
||||
WritableArray pointers = createsPointersArray(reactTarget, androidMotionEvent);
|
||||
WritableArray pointers = createsPointersArray(reactTarget, touchEvent);
|
||||
MotionEvent motionEvent = touchEvent.getMotionEvent();
|
||||
|
||||
// For START and END events send only index of the pointer that is associated with that event
|
||||
// For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
|
||||
WritableArray changedIndices = Arguments.createArray();
|
||||
if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
|
||||
for (int i = 0; i < androidMotionEvent.getPointerCount(); i++) {
|
||||
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
|
||||
changedIndices.pushInt(i);
|
||||
}
|
||||
} else if (type == TouchEventType.START || type == TouchEventType.END) {
|
||||
changedIndices.pushInt(androidMotionEvent.getActionIndex());
|
||||
changedIndices.pushInt(motionEvent.getActionIndex());
|
||||
} else {
|
||||
throw new RuntimeException("Unknown touch type: " + type);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue