/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.uimanager.events; import android.view.MotionEvent; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.PixelUtil; /** * Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */ /*package*/ class TouchesHelper { private static final String PAGE_X_KEY = "pageX"; private static final String PAGE_Y_KEY = "pageY"; private static final String TARGET_KEY = "target"; 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"; /** * Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from * 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) { WritableArray touches = Arguments.createArray(); // 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(); for (int index = 0; index < event.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))); touch.putInt(TARGET_KEY, reactTarget); touch.putDouble(TIMESTAMP_KEY, event.getEventTime()); touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index)); touches.pushMap(touch); } return touches; } /** * Generate and send touch event to RCTEventEmitter JS module associated with the given * {@param context}. Touch event can encode multiple concurrent touches (pointers). * * @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 */ public static void sendTouchEvent( RCTEventEmitter rctEventEmitter, TouchEventType type, int reactTarget, MotionEvent androidMotionEvent) { WritableArray pointers = createsPointersArray(reactTarget, androidMotionEvent); // 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++) { changedIndices.pushInt(i); } } else if (type == TouchEventType.START || type == TouchEventType.END) { changedIndices.pushInt(androidMotionEvent.getActionIndex()); } else { throw new RuntimeException("Unknown touch type: " + type); } rctEventEmitter.receiveTouches( type.getJSEventName(), pointers, changedIndices); } }