1818import com .facebook .react .bridge .UiThreadUtil ;
1919import com .facebook .react .touch .ReactHitSlopView ;
2020
21+ import java .util .EnumSet ;
22+
2123/**
2224 * Class responsible for identifying which react view should handle a given {@link MotionEvent}. It
2325 * uses the event coordinates to traverse the view hierarchy and return a suitable view.
@@ -80,7 +82,7 @@ public static int findTargetTagAndCoordinatesForTouch(
8082 // Store eventCoords in array so that they are modified to be relative to the targetView found.
8183 viewCoords [0 ] = eventX ;
8284 viewCoords [1 ] = eventY ;
83- View nativeTargetView = findTouchTargetView (viewCoords , viewGroup );
85+ View nativeTargetView = findTouchTargetViewWithPointerEvents (viewCoords , viewGroup );
8486 if (nativeTargetView != null ) {
8587 View reactTargetView = findClosestReactAncestor (nativeTargetView );
8688 if (reactTargetView != null ) {
@@ -100,6 +102,20 @@ private static View findClosestReactAncestor(View view) {
100102 return view ;
101103 }
102104
105+ /**
106+ * Types of allowed return values from {@link #findTouchTargetView}.
107+ */
108+ private enum TouchTargetReturnType {
109+ /**
110+ * Allow returning the view passed in through the parameters.
111+ */
112+ SELF ,
113+ /**
114+ * Allow returning children of the view passed in through parameters.
115+ */
116+ CHILD ,
117+ }
118+
103119 /**
104120 * Returns the touch target View that is either viewGroup or one if its descendants. This is a
105121 * recursive DFS since view the entire tree must be parsed until the target is found. If the
@@ -111,18 +127,21 @@ private static View findClosestReactAncestor(View view) {
111127 * be relative to the current viewGroup. When the method returns, it will contain the eventCoords
112128 * relative to the targetView found.
113129 */
114- private static View findTouchTargetView (float [] eventCoords , ViewGroup viewGroup ) {
115- int childrenCount = viewGroup .getChildCount ();
116- // Consider z-index when determining the touch target.
117- ReactZIndexedViewGroup zIndexedViewGroup =
130+ private static View findTouchTargetView (
131+ float [] eventCoords , View view , EnumSet <TouchTargetReturnType > allowReturnTouchTargetTypes ) {
132+ if (allowReturnTouchTargetTypes .contains (TouchTargetReturnType .CHILD )
133+ && view instanceof ViewGroup ) {
134+ ViewGroup viewGroup = (ViewGroup ) view ;
135+ int childrenCount = viewGroup .getChildCount ();
136+ // Consider z-index when determining the touch target.
137+ ReactZIndexedViewGroup zIndexedViewGroup =
118138 viewGroup instanceof ReactZIndexedViewGroup ? (ReactZIndexedViewGroup ) viewGroup : null ;
119- for (int i = childrenCount - 1 ; i >= 0 ; i --) {
120- int childIndex =
139+ for (int i = childrenCount - 1 ; i >= 0 ; i --) {
140+ int childIndex =
121141 zIndexedViewGroup != null ? zIndexedViewGroup .getZIndexMappedChildIndex (i ) : i ;
122- View child = viewGroup .getChildAt (childIndex );
123- PointF childPoint = mTempPoint ;
124- if (isTransformedTouchPointInView (
125- eventCoords [0 ], eventCoords [1 ], viewGroup , child , childPoint )) {
142+ View child = viewGroup .getChildAt (childIndex );
143+ PointF childPoint = mTempPoint ;
144+ getChildPoint (eventCoords [0 ], eventCoords [1 ], viewGroup , child , childPoint );
126145 // If it is contained within the child View, the childPoint value will contain the view
127146 // coordinates relative to the child
128147 // We need to store the existing X,Y for the viewGroup away as it is possible this child
@@ -132,22 +151,66 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup
132151 eventCoords [0 ] = childPoint .x ;
133152 eventCoords [1 ] = childPoint .y ;
134153 View targetView = findTouchTargetViewWithPointerEvents (eventCoords , child );
154+
135155 if (targetView != null ) {
136- return targetView ;
156+ // We don't allow touches on views that are outside the bounds of an `overflow: hidden`
157+ // View
158+ boolean inOverflowBounds = true ;
159+ if (viewGroup instanceof ReactOverflowView ) {
160+ @ Nullable String overflow = ((ReactOverflowView ) viewGroup ).getOverflow ();
161+ if (ViewProps .HIDDEN .equals (overflow )
162+ && !isTouchPointInView (restoreX , restoreY , view )) {
163+ inOverflowBounds = false ;
164+ }
165+ }
166+ if (inOverflowBounds ) {
167+ return targetView ;
168+ }
137169 }
138170 eventCoords [0 ] = restoreX ;
139171 eventCoords [1 ] = restoreY ;
140172 }
141173 }
142- return viewGroup ;
174+
175+ if (allowReturnTouchTargetTypes .contains (TouchTargetReturnType .SELF )
176+ && isTouchPointInView (eventCoords [0 ], eventCoords [1 ], view )) {
177+ return view ;
178+ }
179+
180+ return null ;
181+ }
182+
183+ /**
184+ * Checks whether a touch at {@code x} and {@code y} are within the bounds of the View. Both
185+ * {@code x} and {@code y} must be relative to the top-left corner of the view.
186+ */
187+ private static boolean isTouchPointInView (float x , float y , View view ) {
188+ if (view instanceof ReactHitSlopView && ((ReactHitSlopView ) view ).getHitSlopRect () != null ) {
189+ Rect hitSlopRect = ((ReactHitSlopView ) view ).getHitSlopRect ();
190+ if ((x >= -hitSlopRect .left
191+ && x < (view .getRight () - view .getLeft ()) + hitSlopRect .right )
192+ && (y >= -hitSlopRect .top
193+ && y < (view .getBottom () - view .getTop ()) + hitSlopRect .bottom )) {
194+ return true ;
195+ }
196+
197+ return false ;
198+ } else {
199+ if ((x >= 0 && x < (view .getRight () - view .getLeft ()))
200+ && (y >= 0 && y < (view .getBottom () - view .getTop ()))) {
201+ return true ;
202+ }
203+
204+ return false ;
205+ }
143206 }
144207
145208 /**
146- * Returns whether the touch point is within the child View It is transform aware and will invert
209+ * Returns the coordinates of a touch in the child View. It is transform aware and will invert
147210 * the transform Matrix to find the true local points This code is taken from {@link
148211 * ViewGroup#isTransformedTouchPointInView()}
149212 */
150- private static boolean isTransformedTouchPointInView (
213+ private static void getChildPoint (
151214 float x , float y , ViewGroup parent , View child , PointF outLocalPoint ) {
152215 float localX = x + parent .getScrollX () - child .getLeft ();
153216 float localY = y + parent .getScrollY () - child .getTop ();
@@ -162,26 +225,7 @@ private static boolean isTransformedTouchPointInView(
162225 localX = localXY [0 ];
163226 localY = localXY [1 ];
164227 }
165- if (child instanceof ReactHitSlopView && ((ReactHitSlopView ) child ).getHitSlopRect () != null ) {
166- Rect hitSlopRect = ((ReactHitSlopView ) child ).getHitSlopRect ();
167- if ((localX >= -hitSlopRect .left
168- && localX < (child .getRight () - child .getLeft ()) + hitSlopRect .right )
169- && (localY >= -hitSlopRect .top
170- && localY < (child .getBottom () - child .getTop ()) + hitSlopRect .bottom )) {
171- outLocalPoint .set (localX , localY );
172- return true ;
173- }
174-
175- return false ;
176- } else {
177- if ((localX >= 0 && localX < (child .getRight () - child .getLeft ()))
178- && (localY >= 0 && localY < (child .getBottom () - child .getTop ()))) {
179- outLocalPoint .set (localX , localY );
180- return true ;
181- }
182-
183- return false ;
184- }
228+ outLocalPoint .set (localX , localY );
185229 }
186230
187231 /**
@@ -211,32 +255,32 @@ private static boolean isTransformedTouchPointInView(
211255 return null ;
212256
213257 } else if (pointerEvents == PointerEvents .BOX_ONLY ) {
214- // This view is the target, its children don't matter
215- return view ;
258+ // This view may be the target, its children don't matter
259+ return findTouchTargetView ( eventCoords , view , EnumSet . of ( TouchTargetReturnType . SELF )) ;
216260
217261 } else if (pointerEvents == PointerEvents .BOX_NONE ) {
218262 // This view can't be the target, but its children might.
219- if ( view instanceof ViewGroup ) {
220- View targetView = findTouchTargetView (eventCoords , ( ViewGroup ) view );
221- if (targetView != view ) {
222- return targetView ;
223- }
263+ View targetView =
264+ findTouchTargetView (eventCoords , view , EnumSet . of ( TouchTargetReturnType . CHILD ) );
265+ if (targetView != null ) {
266+ return targetView ;
267+ }
224268
225- // PointerEvents.BOX_NONE means that this react element cannot receive pointer events.
226- // However, there might be virtual children that can receive pointer events, in which case
227- // we still want to return this View and dispatch a pointer event to the virtual element.
228- // Note that this currently only applies to Nodes/FlatViewGroup as it's the only class that
229- // is both a ViewGroup and ReactCompoundView (ReactTextView is a ReactCompoundView but not a
230- // ViewGroup).
231- if (view instanceof ReactCompoundView ) {
232- int reactTag =
233- ((ReactCompoundView ) view ).reactTagForTouch (eventCoords [0 ], eventCoords [1 ]);
234- if (reactTag != view .getId ()) {
235- // make sure we exclude the View itself because of the PointerEvents.BOX_NONE
236- return view ;
237- }
269+ // PointerEvents.BOX_NONE means that this react element cannot receive pointer events.
270+ // However, there might be virtual children that can receive pointer events, in which case
271+ // we still want to return this View and dispatch a pointer event to the virtual element.
272+ // Note that this currently only applies to Nodes/FlatViewGroup as it's the only class that
273+ // is both a ViewGroup and ReactCompoundView (ReactTextView is a ReactCompoundView but not a
274+ // ViewGroup).
275+ if (view instanceof ReactCompoundView ) {
276+ int reactTag =
277+ ((ReactCompoundView ) view ).reactTagForTouch (eventCoords [0 ], eventCoords [1 ]);
278+ if (reactTag != view .getId ()) {
279+ // make sure we exclude the View itself because of the PointerEvents.BOX_NONE
280+ return view ;
238281 }
239282 }
283+
240284 return null ;
241285
242286 } else if (pointerEvents == PointerEvents .AUTO ) {
@@ -246,10 +290,8 @@ private static boolean isTransformedTouchPointInView(
246290 return view ;
247291 }
248292 }
249- if (view instanceof ViewGroup ) {
250- return findTouchTargetView (eventCoords , (ViewGroup ) view );
251- }
252- return view ;
293+ return findTouchTargetView (eventCoords , view ,
294+ EnumSet .of (TouchTargetReturnType .SELF , TouchTargetReturnType .CHILD ));
253295
254296 } else {
255297 throw new JSApplicationIllegalArgumentException (
0 commit comments