mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-09 09:12:06 +08:00
152 lines
6.1 KiB
Java
152 lines
6.1 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.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];
|
|
|
|
/**
|
|
* 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
|
|
* @return the react tag ID of the child view that should handle the event
|
|
*/
|
|
public static int findTargetTagForTouch(
|
|
float eventY,
|
|
float eventX,
|
|
ViewGroup viewGroup) {
|
|
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);
|
|
if (nativeTargetView != null) {
|
|
View reactTargetView = findClosestReactAncestor(nativeTargetView);
|
|
if (reactTargetView != null) {
|
|
targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[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);
|
|
if (isTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child)) {
|
|
// Apply offset to event coordinates to transform them into the coordinate space of the
|
|
// child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}.
|
|
eventCoords[0] += viewGroup.getScrollY() - child.getTop();
|
|
eventCoords[1] += viewGroup.getScrollX() - child.getLeft();
|
|
View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
|
|
if (targetView != null) {
|
|
return targetView;
|
|
}
|
|
eventCoords[0] -= viewGroup.getScrollY() - child.getTop();
|
|
eventCoords[1] -= viewGroup.getScrollX() - child.getLeft();
|
|
}
|
|
}
|
|
return viewGroup;
|
|
}
|
|
|
|
// Taken from {@link ViewGroup#isTransformedTouchPointInView()}
|
|
private static boolean isTouchPointInView(float y, float x, ViewGroup parent, View child) {
|
|
float localY = y + parent.getScrollY() - child.getTop();
|
|
float localX = x + parent.getScrollX() - child.getLeft();
|
|
// Taken from {@link View#pointInView()}.
|
|
return localY >= 0 && localY < (child.getBottom() - child.getTop())
|
|
&& localX >= 0 && localX < (child.getRight() - child.getLeft());
|
|
}
|
|
|
|
/**
|
|
* 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 eventY, float eventX) {
|
|
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();
|
|
}
|
|
|
|
}
|