2727import com .facebook .react .bridge .UiThreadUtil ;
2828import com .facebook .react .bridge .WritableMap ;
2929import com .facebook .react .common .ReactConstants ;
30- import com .facebook .react .common .SystemClock ;
3130import com .facebook .react .common .annotations .VisibleForTesting ;
3231import com .facebook .react .modules .core .DeviceEventManagerModule ;
3332import com .facebook .react .uimanager .DisplayMetricsHolder ;
3433import com .facebook .react .uimanager .PixelUtil ;
3534import com .facebook .react .uimanager .RootView ;
3635import com .facebook .react .uimanager .SizeMonitoringFrameLayout ;
37- import com .facebook .react .uimanager .TouchTargetHelper ;
36+ import com .facebook .react .uimanager .JSTouchDispatcher ;
3837import com .facebook .react .uimanager .UIManagerModule ;
3938import com .facebook .react .uimanager .events .EventDispatcher ;
40- import com .facebook .react .uimanager .events .TouchEvent ;
41- import com .facebook .react .uimanager .events .TouchEventType ;
4239
4340/**
4441 * Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI
4542 * manager can re-layout its elements.
46- * It is also responsible for handling touch events passed to any of it's child view's and sending
47- * those events to JS via RCTEventEmitter module. This view is overriding
48- * {@link ViewGroup#onInterceptTouchEvent} method in order to be notified about the events for all
49- * of it's children and it's also overriding {@link ViewGroup#requestDisallowInterceptTouchEvent}
50- * to make sure that {@link ViewGroup#onInterceptTouchEvent} will get events even when some child
51- * view start intercepting it. In case when no child view is interested in handling some particular
43+ * It delegates handling touch events for itself and child views and sending those events to JS by
44+ * using JSTouchDispatcher.
45+ * This view is overriding {@link ViewGroup#onInterceptTouchEvent} method in order to be notified
46+ * about the events for all of it's children and it's also overriding
47+ * {@link ViewGroup#requestDisallowInterceptTouchEvent} to make sure that
48+ * {@link ViewGroup#onInterceptTouchEvent} will get events even when some child view start
49+ * intercepting it. In case when no child view is interested in handling some particular
5250 * touch event this view's {@link View#onTouchEvent} will still return true in order to be notified
5351 * about all subsequent touch events related to that gesture (in case when JS code want to handle
5452 * that gesture).
@@ -59,13 +57,11 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
5957 private @ Nullable String mJSModuleName ;
6058 private @ Nullable Bundle mLaunchOptions ;
6159 private @ Nullable KeyboardListener mKeyboardListener ;
62- private int mTargetTag = -1 ;
63- private final float [] mTargetCoordinates = new float [2 ];
64- private boolean mChildIsHandlingNativeGesture = false ;
6560 private boolean mWasMeasured = false ;
6661 private boolean mAttachScheduled = false ;
6762 private boolean mIsAttachedToWindow = false ;
6863 private boolean mIsAttachedToInstance = false ;
64+ private final JSTouchDispatcher mJSTouchDispatcher = new JSTouchDispatcher (this );
6965
7066 public ReactRootView (Context context ) {
7167 super (context );
@@ -112,171 +108,50 @@ public void run() {
112108 }
113109 }
114110
115- /**
116- * Main catalyst view is responsible for collecting and sending touch events to JS. This method
117- * reacts for an incoming android native touch events ({@link MotionEvent}) and calls into
118- * {@link com.facebook.react.uimanager.events.EventDispatcher} when appropriate.
119- * It uses {@link com.facebook.react.uimanager.TouchTargetManagerHelper#findTouchTargetView}
120- * helper method for figuring out a react view ID in the case of ACTION_DOWN
121- * event (when the gesture starts).
122- */
123- private void handleTouchEvent (MotionEvent ev ) {
111+ @ Override
112+ public void onChildStartedNativeGesture (MotionEvent androidEvent ) {
124113 if (mReactInstanceManager == null || !mIsAttachedToInstance ||
125- mReactInstanceManager .getCurrentReactContext () == null ) {
114+ mReactInstanceManager .getCurrentReactContext () == null ) {
126115 FLog .w (
127- ReactConstants .TAG ,
128- "Unable to handle touch in JS as the catalyst instance has not been attached" );
116+ ReactConstants .TAG ,
117+ "Unable to dispatch touch to JS as the catalyst instance has not been attached" );
129118 return ;
130119 }
131- int action = ev .getAction () & MotionEvent .ACTION_MASK ;
132120 ReactContext reactContext = mReactInstanceManager .getCurrentReactContext ();
133121 EventDispatcher eventDispatcher = reactContext .getNativeModule (UIManagerModule .class )
134- .getEventDispatcher ();
135- if (action == MotionEvent .ACTION_DOWN ) {
136- if (mTargetTag != -1 ) {
137- FLog .e (
138- ReactConstants .TAG ,
139- "Got DOWN touch before receiving UP or CANCEL from last gesture" );
140- }
141-
142- // First event for this gesture. We expect tag to be set to -1, and we use helper method
143- // {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling
144- // this gesture
145- mChildIsHandlingNativeGesture = false ;
146- mTargetTag = TouchTargetHelper .findTargetTagAndCoordinatesForTouch (
147- ev .getX (),
148- ev .getY (),
149- this ,
150- mTargetCoordinates );
151- eventDispatcher .dispatchEvent (
152- TouchEvent .obtain (
153- mTargetTag ,
154- SystemClock .nanoTime (),
155- TouchEventType .START ,
156- ev ,
157- mTargetCoordinates [0 ],
158- mTargetCoordinates [1 ]));
159- } else if (mChildIsHandlingNativeGesture ) {
160- // If the touch was intercepted by a child, we've already sent a cancel event to JS for this
161- // gesture, so we shouldn't send any more touches related to it.
162- return ;
163- } else if (mTargetTag == -1 ) {
164- // All the subsequent action types are expected to be called after ACTION_DOWN thus target
165- // is supposed to be set for them.
166- FLog .e (
167- ReactConstants .TAG ,
168- "Unexpected state: received touch event but didn't get starting ACTION_DOWN for this " +
169- "gesture before" );
170- } else if (action == MotionEvent .ACTION_UP ) {
171- // End of the gesture. We reset target tag to -1 and expect no further event associated with
172- // this gesture.
173- eventDispatcher .dispatchEvent (
174- TouchEvent .obtain (
175- mTargetTag ,
176- SystemClock .nanoTime (),
177- TouchEventType .END ,
178- ev ,
179- mTargetCoordinates [0 ],
180- mTargetCoordinates [1 ]));
181- mTargetTag = -1 ;
182- } else if (action == MotionEvent .ACTION_MOVE ) {
183- // Update pointer position for current gesture
184- eventDispatcher .dispatchEvent (
185- TouchEvent .obtain (
186- mTargetTag ,
187- SystemClock .nanoTime (),
188- TouchEventType .MOVE ,
189- ev ,
190- mTargetCoordinates [0 ],
191- mTargetCoordinates [1 ]));
192- } else if (action == MotionEvent .ACTION_POINTER_DOWN ) {
193- // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
194- eventDispatcher .dispatchEvent (
195- TouchEvent .obtain (
196- mTargetTag ,
197- SystemClock .nanoTime (),
198- TouchEventType .START ,
199- ev ,
200- mTargetCoordinates [0 ],
201- mTargetCoordinates [1 ]));
202- } else if (action == MotionEvent .ACTION_POINTER_UP ) {
203- // Exactly onw of the pointers goes up
204- eventDispatcher .dispatchEvent (
205- TouchEvent .obtain (
206- mTargetTag ,
207- SystemClock .nanoTime (),
208- TouchEventType .END ,
209- ev ,
210- mTargetCoordinates [0 ],
211- mTargetCoordinates [1 ]));
212- } else if (action == MotionEvent .ACTION_CANCEL ) {
213- dispatchCancelEvent (ev );
214- mTargetTag = -1 ;
215- } else {
216- FLog .w (
217- ReactConstants .TAG ,
218- "Warning : touch event was ignored. Action=" + action + " Target=" + mTargetTag );
219- }
220- }
221-
222- @ Override
223- public void onChildStartedNativeGesture (MotionEvent androidEvent ) {
224- if (mChildIsHandlingNativeGesture ) {
225- // This means we previously had another child start handling this native gesture and now a
226- // different native parent of that child has decided to intercept the touch stream and handle
227- // the gesture itself. Example where this can happen: HorizontalScrollView in a ScrollView.
228- return ;
229- }
230-
231- dispatchCancelEvent (androidEvent );
232- mChildIsHandlingNativeGesture = true ;
233- mTargetTag = -1 ;
234- }
235-
236- private void dispatchCancelEvent (MotionEvent androidEvent ) {
237- // This means the gesture has already ended, via some other CANCEL or UP event. This is not
238- // expected to happen very often as it would mean some child View has decided to intercept the
239- // touch stream and start a native gesture only upon receiving the UP/CANCEL event.
240- if (mTargetTag == -1 ) {
241- FLog .w (
242- ReactConstants .TAG ,
243- "Can't cancel already finished gesture. Is a child View trying to start a gesture from " +
244- "an UP/CANCEL event?" );
245- return ;
246- }
247-
248- EventDispatcher eventDispatcher = mReactInstanceManager .getCurrentReactContext ()
249- .getNativeModule (UIManagerModule .class )
250- .getEventDispatcher ();
251-
252- Assertions .assertCondition (
253- !mChildIsHandlingNativeGesture ,
254- "Expected to not have already sent a cancel for this gesture" );
255- Assertions .assertNotNull (eventDispatcher ).dispatchEvent (
256- TouchEvent .obtain (
257- mTargetTag ,
258- SystemClock .nanoTime (),
259- TouchEventType .CANCEL ,
260- androidEvent ,
261- mTargetCoordinates [0 ],
262- mTargetCoordinates [1 ]));
122+ .getEventDispatcher ();
123+ mJSTouchDispatcher .onChildStartedNativeGesture (androidEvent , eventDispatcher );
263124 }
264125
265126 @ Override
266127 public boolean onInterceptTouchEvent (MotionEvent ev ) {
267- handleTouchEvent (ev );
128+ dispatchJSTouchEvent (ev );
268129 return super .onInterceptTouchEvent (ev );
269130 }
270131
271132 @ Override
272133 public boolean onTouchEvent (MotionEvent ev ) {
273- handleTouchEvent (ev );
134+ dispatchJSTouchEvent (ev );
274135 super .onTouchEvent (ev );
275136 // In case when there is no children interested in handling touch event, we return true from
276137 // the root view in order to receive subsequent events related to that gesture
277138 return true ;
278139 }
279140
141+ private void dispatchJSTouchEvent (MotionEvent event ) {
142+ if (mReactInstanceManager == null || !mIsAttachedToInstance ||
143+ mReactInstanceManager .getCurrentReactContext () == null ) {
144+ FLog .w (
145+ ReactConstants .TAG ,
146+ "Unable to dispatch touch to JS as the catalyst instance has not been attached" );
147+ return ;
148+ }
149+ ReactContext reactContext = mReactInstanceManager .getCurrentReactContext ();
150+ EventDispatcher eventDispatcher = reactContext .getNativeModule (UIManagerModule .class )
151+ .getEventDispatcher ();
152+ mJSTouchDispatcher .handleTouchEvent (event , eventDispatcher );
153+ }
154+
280155 @ Override
281156 public void requestDisallowInterceptTouchEvent (boolean disallowIntercept ) {
282157 // No-op - override in order to still receive events to onInterceptTouchEvent
0 commit comments