mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Re-implement hover state calculation to support nested Text nodes
Summary: Changelog: [Internal] Add a path calculation method to TouchTargetHelper to avoid relying on the view hierarchy ourselves and better support virtual touch targets. Reviewed By: JoshuaGross Differential Revision: D30993410 fbshipit-source-id: 17577c815413ab0f03bcb6a2140ea1d4c9fd694a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c3e294f2a8
commit
133705b130
@@ -17,7 +17,9 @@ import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.touch.ReactHitSlopView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class responsible for identifying which react view should handle a given {@link MotionEvent}. It
|
||||
@@ -81,7 +83,7 @@ public class TouchTargetHelper {
|
||||
// Store eventCoords in array so that they are modified to be relative to the targetView found.
|
||||
viewCoords[0] = eventX;
|
||||
viewCoords[1] = eventY;
|
||||
View nativeTargetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup);
|
||||
View nativeTargetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup, null);
|
||||
if (nativeTargetView != null) {
|
||||
View reactTargetView = findClosestReactAncestor(nativeTargetView);
|
||||
if (reactTargetView != null) {
|
||||
@@ -94,6 +96,39 @@ public class TouchTargetHelper {
|
||||
return targetTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 If a target was found, returns a path through the view tree of all react tags that are
|
||||
* a container for the touch target, ordered from target to root (last element)
|
||||
*/
|
||||
public static List<Integer> findTargetPathAndCoordinatesForTouch(
|
||||
float eventX, float eventY, ViewGroup viewGroup, float[] viewCoords) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Store eventCoords in array so that they are modified to be relative to the targetView found.
|
||||
viewCoords[0] = eventX;
|
||||
viewCoords[1] = eventY;
|
||||
|
||||
List<Integer> pathAccumulator = new ArrayList<>();
|
||||
View targetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup, pathAccumulator);
|
||||
|
||||
if (targetView != null) {
|
||||
View reactTargetView = findClosestReactAncestor(targetView);
|
||||
int targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
|
||||
if (targetTag != pathAccumulator.get(0)) {
|
||||
pathAccumulator.add(0, targetTag);
|
||||
}
|
||||
}
|
||||
|
||||
return pathAccumulator;
|
||||
}
|
||||
|
||||
private static View findClosestReactAncestor(View view) {
|
||||
while (view != null && view.getId() <= 0) {
|
||||
view = (View) view.getParent();
|
||||
@@ -121,7 +156,10 @@ public class TouchTargetHelper {
|
||||
* relative to the targetView found.
|
||||
*/
|
||||
private static View findTouchTargetView(
|
||||
float[] eventCoords, View view, EnumSet<TouchTargetReturnType> allowReturnTouchTargetTypes) {
|
||||
float[] eventCoords,
|
||||
View view,
|
||||
EnumSet<TouchTargetReturnType> allowReturnTouchTargetTypes,
|
||||
List<Integer> pathAccumulator) {
|
||||
// We prefer returning a child, so we check for a child that can handle the touch first
|
||||
if (allowReturnTouchTargetTypes.contains(TouchTargetReturnType.CHILD)
|
||||
&& view instanceof ViewGroup) {
|
||||
@@ -143,7 +181,7 @@ public class TouchTargetHelper {
|
||||
float restoreY = eventCoords[1];
|
||||
eventCoords[0] = childPoint.x;
|
||||
eventCoords[1] = childPoint.y;
|
||||
View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
|
||||
View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child, pathAccumulator);
|
||||
|
||||
if (targetView != null) {
|
||||
// We don't allow touches on views that are outside the bounds of an `overflow: hidden`
|
||||
@@ -224,7 +262,7 @@ public class TouchTargetHelper {
|
||||
* its descendants are the touch target.
|
||||
*/
|
||||
private static @Nullable View findTouchTargetViewWithPointerEvents(
|
||||
float eventCoords[], View view) {
|
||||
float eventCoords[], View view, @Nullable List<Integer> pathAccumulator) {
|
||||
PointerEvents pointerEvents =
|
||||
view instanceof ReactPointerEventsView
|
||||
? ((ReactPointerEventsView) view).getPointerEvents()
|
||||
@@ -247,13 +285,23 @@ public class TouchTargetHelper {
|
||||
|
||||
} else if (pointerEvents == PointerEvents.BOX_ONLY) {
|
||||
// This view may be the target, its children don't matter
|
||||
return findTouchTargetView(eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF));
|
||||
View targetView =
|
||||
findTouchTargetView(
|
||||
eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF), pathAccumulator);
|
||||
if (targetView != null && pathAccumulator != null) {
|
||||
pathAccumulator.add(view.getId());
|
||||
}
|
||||
return targetView;
|
||||
|
||||
} else if (pointerEvents == PointerEvents.BOX_NONE) {
|
||||
// This view can't be the target, but its children might.
|
||||
View targetView =
|
||||
findTouchTargetView(eventCoords, view, EnumSet.of(TouchTargetReturnType.CHILD));
|
||||
findTouchTargetView(
|
||||
eventCoords, view, EnumSet.of(TouchTargetReturnType.CHILD), pathAccumulator);
|
||||
if (targetView != null) {
|
||||
if (pathAccumulator != null) {
|
||||
pathAccumulator.add(view.getId());
|
||||
}
|
||||
return targetView;
|
||||
}
|
||||
|
||||
@@ -266,8 +314,11 @@ public class TouchTargetHelper {
|
||||
if (view instanceof ReactCompoundView
|
||||
&& isTouchPointInView(eventCoords[0], eventCoords[1], view)) {
|
||||
int reactTag = ((ReactCompoundView) view).reactTagForTouch(eventCoords[0], eventCoords[1]);
|
||||
// make sure we exclude the View itself because of the PointerEvents.BOX_NONE
|
||||
if (reactTag != view.getId()) {
|
||||
// make sure we exclude the View itself because of the PointerEvents.BOX_NONE
|
||||
if (pathAccumulator != null) {
|
||||
pathAccumulator.add(view.getId());
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -279,10 +330,22 @@ public class TouchTargetHelper {
|
||||
if (view instanceof ReactCompoundViewGroup
|
||||
&& isTouchPointInView(eventCoords[0], eventCoords[1], view)
|
||||
&& ((ReactCompoundViewGroup) view).interceptsTouchEvent(eventCoords[0], eventCoords[1])) {
|
||||
if (pathAccumulator != null) {
|
||||
pathAccumulator.add(view.getId());
|
||||
}
|
||||
return view;
|
||||
}
|
||||
return findTouchTargetView(
|
||||
eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF, TouchTargetReturnType.CHILD));
|
||||
|
||||
View result =
|
||||
findTouchTargetView(
|
||||
eventCoords,
|
||||
view,
|
||||
EnumSet.of(TouchTargetReturnType.SELF, TouchTargetReturnType.CHILD),
|
||||
pathAccumulator);
|
||||
if (result != null && pathAccumulator != null) {
|
||||
pathAccumulator.add(view.getId());
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
|
||||
Reference in New Issue
Block a user