Skip to content

Commit 39fdce2

Browse files
Dave MillerFacebook Github Bot 4
authored andcommitted
Refactor JS touch dispatch from ReactRootView
Reviewed By: andreicoman11 Differential Revision: D3035589 fb-gh-sync-id: a4af7b0be89e5ea13367481697ff4b3dda8536b1 shipit-source-id: a4af7b0be89e5ea13367481697ff4b3dda8536b1
1 parent e674e45 commit 39fdce2

File tree

2 files changed

+205
-157
lines changed

2 files changed

+205
-157
lines changed

ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

Lines changed: 32 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,26 @@
2727
import com.facebook.react.bridge.UiThreadUtil;
2828
import com.facebook.react.bridge.WritableMap;
2929
import com.facebook.react.common.ReactConstants;
30-
import com.facebook.react.common.SystemClock;
3130
import com.facebook.react.common.annotations.VisibleForTesting;
3231
import com.facebook.react.modules.core.DeviceEventManagerModule;
3332
import com.facebook.react.uimanager.DisplayMetricsHolder;
3433
import com.facebook.react.uimanager.PixelUtil;
3534
import com.facebook.react.uimanager.RootView;
3635
import com.facebook.react.uimanager.SizeMonitoringFrameLayout;
37-
import com.facebook.react.uimanager.TouchTargetHelper;
36+
import com.facebook.react.uimanager.JSTouchDispatcher;
3837
import com.facebook.react.uimanager.UIManagerModule;
3938
import 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

Comments
 (0)