mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-12 17:30:12 +08:00
Summary: public This fixes the ordering of methods in touch handling to take their arguments as X,Y instead of Y,X. This is really just internal cleanup of native touch handling. Reviewed By: andreicoman11 Differential Revision: D2703003 fb-gh-sync-id: d169436d21fd11c1a9cb251e7e0b57b2094699e4
203 lines
7.8 KiB
Java
203 lines
7.8 KiB
Java
/**
|
|
* 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;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import android.graphics.Matrix;
|
|
import android.graphics.PointF;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
|
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
import com.facebook.react.bridge.UiThreadUtil;
|
|
|
|
/**
|
|
* Class responsible for identifying which react view should handle a given {@link MotionEvent}.
|
|
* It uses the event coordinates to traverse the view hierarchy and return a suitable view.
|
|
*/
|
|
public class TouchTargetHelper {
|
|
|
|
private static final float[] mEventCoords = new float[2];
|
|
private static final PointF mTempPoint = new PointF();
|
|
private static final float[] mMatrixTransformCoords = new float[2];
|
|
private static final Matrix mInverseMatrix = new Matrix();
|
|
|
|
/**
|
|
* Find touch event target view within the provided container given the coordinates provided
|
|
* via {@link MotionEvent}.
|
|
*
|
|
* @param eventX the X screen coordinate of the touch location
|
|
* @param eventY the Y screen coordinate of the touch location
|
|
* @param viewGroup the container view to traverse
|
|
* @return the react tag ID of the child view that should handle the event
|
|
*/
|
|
public static int findTargetTagForTouch(
|
|
float eventX,
|
|
float eventY,
|
|
ViewGroup viewGroup) {
|
|
return findTargetTagAndCoordinatesForTouch(eventX, eventY, viewGroup, mEventCoords);
|
|
}
|
|
|
|
/**
|
|
* Find touch event target view within the provided container given the coordinates provided
|
|
* via {@link MotionEvent}.
|
|
*
|
|
* @param eventX the X screen coordinate of the touch location
|
|
* @param eventY the Y screen coordinate of the touch location
|
|
* @param viewGroup the container view to traverse
|
|
* @param viewCoords an out parameter that will return the X,Y value in the target view
|
|
* @return the react tag ID of the child view that should handle the event
|
|
*/
|
|
public static int findTargetTagAndCoordinatesForTouch(
|
|
float eventX,
|
|
float eventY,
|
|
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.
|
|
viewCoords[0] = eventX;
|
|
viewCoords[1] = eventY;
|
|
View nativeTargetView = findTouchTargetView(viewCoords, viewGroup);
|
|
if (nativeTargetView != null) {
|
|
View reactTargetView = findClosestReactAncestor(nativeTargetView);
|
|
if (reactTargetView != null) {
|
|
targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
|
|
}
|
|
}
|
|
return targetTag;
|
|
}
|
|
|
|
private static View findClosestReactAncestor(View view) {
|
|
while (view != null && view.getId() <= 0) {
|
|
view = (View) view.getParent();
|
|
}
|
|
return view;
|
|
}
|
|
|
|
/**
|
|
* Returns the touch target View that is either viewGroup or one if its descendants.
|
|
* This is a recursive DFS since view the entire tree must be parsed until the target is found.
|
|
* If the search does not backtrack, it is possible to follow a branch that cannot be a target
|
|
* (because of pointerEvents). For example, if both C and E can be the target of an event:
|
|
* A (pointerEvents: auto) - B (pointerEvents: box-none) - C (pointerEvents: none)
|
|
* \ D (pointerEvents: auto) - E (pointerEvents: auto)
|
|
* If the search goes down the first branch, it would return A as the target, which is incorrect.
|
|
* NB: This modifies the eventCoords to always be relative to the current viewGroup. When the
|
|
* method returns, it will contain the eventCoords relative to the targetView found.
|
|
*/
|
|
private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup) {
|
|
int childrenCount = viewGroup.getChildCount();
|
|
for (int i = childrenCount - 1; i >= 0; i--) {
|
|
View child = viewGroup.getChildAt(i);
|
|
PointF childPoint = mTempPoint;
|
|
if (isTransformedTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child, childPoint)) {
|
|
// If it is contained within the child View, the childPoint value will contain the view
|
|
// coordinates relative to the child
|
|
// We need to store the existing X,Y for the viewGroup away as it is possible this child
|
|
// will not actually be the target and so we restore them if not
|
|
float restoreX = eventCoords[0];
|
|
float restoreY = eventCoords[1];
|
|
eventCoords[0] = childPoint.x;
|
|
eventCoords[1] = childPoint.y;
|
|
View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
|
|
if (targetView != null) {
|
|
return targetView;
|
|
}
|
|
eventCoords[0] = restoreX;
|
|
eventCoords[1] = restoreY;
|
|
}
|
|
}
|
|
return viewGroup;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the touch point is within the child View
|
|
* It is transform aware and will invert the transform Matrix to find the true local points
|
|
* This code is taken from {@link ViewGroup#isTransformedTouchPointInView()}
|
|
*/
|
|
private static boolean isTransformedTouchPointInView(
|
|
float x,
|
|
float y,
|
|
ViewGroup parent,
|
|
View child,
|
|
PointF outLocalPoint) {
|
|
float localX = x + parent.getScrollX() - child.getLeft();
|
|
float localY = y + parent.getScrollY() - child.getTop();
|
|
Matrix matrix = child.getMatrix();
|
|
if (!matrix.isIdentity()) {
|
|
float[] localXY = mMatrixTransformCoords;
|
|
localXY[0] = localX;
|
|
localXY[1] = localY;
|
|
Matrix inverseMatrix = mInverseMatrix;
|
|
matrix.invert(inverseMatrix);
|
|
inverseMatrix.mapPoints(localXY);
|
|
localX = localXY[0];
|
|
localY = localXY[1];
|
|
}
|
|
if ((localX >= 0 && localX < (child.getRight() - child.getLeft()))
|
|
&& (localY >= 0 && localY < (child.getBottom() - child.getTop()))) {
|
|
outLocalPoint.set(localX, localY);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the touch target View of the event given, or null if neither the given View nor any of
|
|
* its descendants are the touch target.
|
|
*/
|
|
private static @Nullable View findTouchTargetViewWithPointerEvents(
|
|
float eventCoords[], View view) {
|
|
PointerEvents pointerEvents = view instanceof ReactPointerEventsView ?
|
|
((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO;
|
|
if (pointerEvents == PointerEvents.NONE) {
|
|
// This view and its children can't be the target
|
|
return null;
|
|
|
|
} else if (pointerEvents == PointerEvents.BOX_ONLY) {
|
|
// This view is the target, its children don't matter
|
|
return view;
|
|
|
|
} else if (pointerEvents == PointerEvents.BOX_NONE) {
|
|
// This view can't be the target, but its children might
|
|
if (view instanceof ViewGroup) {
|
|
View targetView = findTouchTargetView(eventCoords, (ViewGroup) view);
|
|
return targetView != view ? targetView : null;
|
|
}
|
|
return null;
|
|
|
|
} else if (pointerEvents == PointerEvents.AUTO) {
|
|
// Either this view or one of its children is the target
|
|
if (view instanceof ViewGroup) {
|
|
return findTouchTargetView(eventCoords, (ViewGroup) view);
|
|
}
|
|
return view;
|
|
|
|
} else {
|
|
throw new JSApplicationIllegalArgumentException(
|
|
"Unknown pointer event type: " + pointerEvents.toString());
|
|
}
|
|
}
|
|
|
|
private static int getTouchTargetForView(View targetView, float eventX, float eventY) {
|
|
if (targetView instanceof ReactCompoundView) {
|
|
// Use coordinates relative to the view, which have been already computed by
|
|
// {@link #findTouchTargetView()}.
|
|
return ((ReactCompoundView) targetView).reactTagForTouch(eventX, eventY);
|
|
}
|
|
return targetView.getId();
|
|
}
|
|
|
|
}
|