Skip to content

Commit f96d7b8

Browse files
committed
[change] update PanResponder implementation
Fix #171
1 parent 0dfe319 commit f96d7b8

File tree

4 files changed

+418
-394
lines changed

4 files changed

+418
-394
lines changed

src/apis/PanResponder/index.js

Lines changed: 1 addition & 393 deletions
Original file line numberDiff line numberDiff line change
@@ -1,394 +1,2 @@
1-
/**
2-
* Copyright (c) 2016-present, Nicolas Gallagher.
3-
* Copyright (c) 2015-present, Facebook, Inc.
4-
* All rights reserved.
5-
*
6-
* This source code is licensed under the BSD-style license found in the
7-
* LICENSE file in the root directory of this source tree.
8-
*
9-
* @providesModule PanResponder
10-
* @noflow
11-
*/
12-
13-
import TouchHistoryMath from '../../vendor/TouchHistoryMath';
14-
15-
const currentCentroidXOfTouchesChangedAfter =
16-
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
17-
const currentCentroidYOfTouchesChangedAfter =
18-
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
19-
const previousCentroidXOfTouchesChangedAfter =
20-
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
21-
const previousCentroidYOfTouchesChangedAfter =
22-
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
23-
const currentCentroidX = TouchHistoryMath.currentCentroidX;
24-
const currentCentroidY = TouchHistoryMath.currentCentroidY;
25-
26-
/**
27-
* `PanResponder` reconciles several touches into a single gesture. It makes
28-
* single-touch gestures resilient to extra touches, and can be used to
29-
* recognize simple multi-touch gestures.
30-
*
31-
* It provides a predictable wrapper of the responder handlers provided by the
32-
* [gesture responder system](docs/gesture-responder-system.html).
33-
* For each handler, it provides a new `gestureState` object alongside the
34-
* native event object:
35-
*
36-
* ```
37-
* onPanResponderMove: (event, gestureState) => {}
38-
* ```
39-
*
40-
* A native event is a synthetic touch event with the following form:
41-
*
42-
* - `nativeEvent`
43-
* + `changedTouches` - Array of all touch events that have changed since the last event
44-
* + `identifier` - The ID of the touch
45-
* + `locationX` - The X position of the touch, relative to the element
46-
* + `locationY` - The Y position of the touch, relative to the element
47-
* + `pageX` - The X position of the touch, relative to the root element
48-
* + `pageY` - The Y position of the touch, relative to the root element
49-
* + `target` - The node id of the element receiving the touch event
50-
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
51-
* + `touches` - Array of all current touches on the screen
52-
*
53-
* A `gestureState` object has the following:
54-
*
55-
* - `stateID` - ID of the gestureState- persisted as long as there at least
56-
* one touch on screen
57-
* - `moveX` - the latest screen coordinates of the recently-moved touch
58-
* - `moveY` - the latest screen coordinates of the recently-moved touch
59-
* - `x0` - the screen coordinates of the responder grant
60-
* - `y0` - the screen coordinates of the responder grant
61-
* - `dx` - accumulated distance of the gesture since the touch started
62-
* - `dy` - accumulated distance of the gesture since the touch started
63-
* - `vx` - current velocity of the gesture
64-
* - `vy` - current velocity of the gesture
65-
* - `numberActiveTouches` - Number of touches currently on screen
66-
*
67-
* ### Basic Usage
68-
*
69-
* ```
70-
* componentWillMount: function() {
71-
* this._panResponder = PanResponder.create({
72-
* // Ask to be the responder:
73-
* onStartShouldSetPanResponder: (evt, gestureState) => true,
74-
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
75-
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
76-
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
77-
*
78-
* onPanResponderGrant: (evt, gestureState) => {
79-
* // The guesture has started. Show visual feedback so the user knows
80-
* // what is happening!
81-
*
82-
* // gestureState.{x,y}0 will be set to zero now
83-
* },
84-
* onPanResponderMove: (evt, gestureState) => {
85-
* // The most recent move distance is gestureState.move{X,Y}
86-
*
87-
* // The accumulated gesture distance since becoming responder is
88-
* // gestureState.d{x,y}
89-
* },
90-
* onPanResponderTerminationRequest: (evt, gestureState) => true,
91-
* onPanResponderRelease: (evt, gestureState) => {
92-
* // The user has released all touches while this view is the
93-
* // responder. This typically means a gesture has succeeded
94-
* },
95-
* onPanResponderTerminate: (evt, gestureState) => {
96-
* // Another component has become the responder, so this gesture
97-
* // should be cancelled
98-
* },
99-
* onShouldBlockNativeResponder: (evt, gestureState) => {
100-
* // Returns whether this component should block native components from becoming the JS
101-
* // responder. Returns true by default. Is currently only supported on android.
102-
* return true;
103-
* },
104-
* });
105-
* },
106-
*
107-
* render: function() {
108-
* return (
109-
* <View {...this._panResponder.panHandlers} />
110-
* );
111-
* },
112-
*
113-
* ```
114-
*
115-
* ### Working Example
116-
*
117-
* To see it in action, try the
118-
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
119-
*/
120-
121-
const PanResponder = {
122-
/**
123-
*
124-
* A graphical explanation of the touch data flow:
125-
*
126-
* +----------------------------+ +--------------------------------+
127-
* | ResponderTouchHistoryStore | |TouchHistoryMath |
128-
* +----------------------------+ +----------+---------------------+
129-
* |Global store of touchHistory| |Allocation-less math util |
130-
* |including activeness, start | |on touch history (centroids |
131-
* |position, prev/cur position.| |and multitouch movement etc) |
132-
* | | | |
133-
* +----^-----------------------+ +----^---------------------------+
134-
* | |
135-
* | (records relevant history |
136-
* | of touches relevant for |
137-
* | implementing higher level |
138-
* | gestures) |
139-
* | |
140-
* +----+-----------------------+ +----|---------------------------+
141-
* | ResponderEventPlugin | | | Your App/Component |
142-
* +----------------------------+ +----|---------------------------+
143-
* |Negotiates which view gets | Low level | | High level |
144-
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
145-
* |Also records history into | touchHistory| | Pan | multitouch + |
146-
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
147-
* +----------------------------+ attached to | | | distance and |
148-
* each event | +---------+ velocity. |
149-
* | |
150-
* | |
151-
* +--------------------------------+
152-
*
153-
*
154-
*
155-
* Gesture that calculates cumulative movement over time in a way that just
156-
* "does the right thing" for multiple touches. The "right thing" is very
157-
* nuanced. When moving two touches in opposite directions, the cumulative
158-
* distance is zero in each dimension. When two touches move in parallel five
159-
* pixels in the same direction, the cumulative distance is five, not ten. If
160-
* two touches start, one moves five in a direction, then stops and the other
161-
* touch moves fives in the same direction, the cumulative distance is ten.
162-
*
163-
* This logic requires a kind of processing of time "clusters" of touch events
164-
* so that two touch moves that essentially occur in parallel but move every
165-
* other frame respectively, are considered part of the same movement.
166-
*
167-
* Explanation of some of the non-obvious fields:
168-
*
169-
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
170-
* invalid. If a move event has been observed, `(moveX, moveY)` is the
171-
* centroid of the most recently moved "cluster" of active touches.
172-
* (Currently all move have the same timeStamp, but later we should add some
173-
* threshold for what is considered to be "moving"). If a palm is
174-
* accidentally counted as a touch, but a finger is moving greatly, the palm
175-
* will move slightly, but we only want to count the single moving touch.
176-
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
177-
* responder.
178-
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
179-
* distance. Accounts for touch moves that are clustered together in time,
180-
* moving the same direction. Only valid when currently responder (otherwise,
181-
* it only represents the drag distance below the threshold).
182-
* - vx/vy: Velocity.
183-
*/
184-
185-
_initializeGestureState: function(gestureState) {
186-
gestureState.moveX = 0;
187-
gestureState.moveY = 0;
188-
gestureState.x0 = 0;
189-
gestureState.y0 = 0;
190-
gestureState.dx = 0;
191-
gestureState.dy = 0;
192-
gestureState.vx = 0;
193-
gestureState.vy = 0;
194-
gestureState.numberActiveTouches = 0;
195-
// All `gestureState` accounts for timeStamps up until:
196-
gestureState._accountsForMovesUpTo = 0;
197-
},
198-
199-
/**
200-
* This is nuanced and is necessary. It is incorrect to continuously take all
201-
* active *and* recently moved touches, find the centroid, and track how that
202-
* result changes over time. Instead, we must take all recently moved
203-
* touches, and calculate how the centroid has changed just for those
204-
* recently moved touches, and append that change to an accumulator. This is
205-
* to (at least) handle the case where the user is moving three fingers, and
206-
* then one of the fingers stops but the other two continue.
207-
*
208-
* This is very different than taking all of the recently moved touches and
209-
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
210-
* changes* in the centroid of recently moved touches.
211-
*
212-
* There is also some nuance with how we handle multiple moved touches in a
213-
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
214-
* individual events, multiple touches generate two 'move' events, each of
215-
* them triggering `onResponderMove`. But with the way `PanResponder` works,
216-
* all of the gesture inference is performed on the first dispatch, since it
217-
* looks at all of the touches (even the ones for which there hasn't been a
218-
* native dispatch yet). Therefore, `PanResponder` does not call
219-
* `onResponderMove` passed the first dispatch. This diverges from the
220-
* typical responder callback pattern (without using `PanResponder`), but
221-
* avoids more dispatches than necessary.
222-
*/
223-
_updateGestureStateOnMove: function(gestureState, touchHistory) {
224-
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
225-
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
226-
touchHistory,
227-
gestureState._accountsForMovesUpTo
228-
);
229-
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
230-
touchHistory,
231-
gestureState._accountsForMovesUpTo
232-
);
233-
const movedAfter = gestureState._accountsForMovesUpTo;
234-
const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
235-
const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
236-
const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
237-
const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
238-
const nextDX = gestureState.dx + (x - prevX);
239-
const nextDY = gestureState.dy + (y - prevY);
240-
241-
// TODO: This must be filtered intelligently.
242-
const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo;
243-
gestureState.vx = (nextDX - gestureState.dx) / dt;
244-
gestureState.vy = (nextDY - gestureState.dy) / dt;
245-
246-
gestureState.dx = nextDX;
247-
gestureState.dy = nextDY;
248-
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
249-
},
250-
251-
/**
252-
* @param {object} config Enhanced versions of all of the responder callbacks
253-
* that provide not only the typical `ResponderSyntheticEvent`, but also the
254-
* `PanResponder` gesture state. Simply replace the word `Responder` with
255-
* `PanResponder` in each of the typical `onResponder*` callbacks. For
256-
* example, the `config` object would look like:
257-
*
258-
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
259-
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
260-
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
261-
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
262-
* - `onPanResponderReject: (e, gestureState) => {...}`
263-
* - `onPanResponderGrant: (e, gestureState) => {...}`
264-
* - `onPanResponderStart: (e, gestureState) => {...}`
265-
* - `onPanResponderEnd: (e, gestureState) => {...}`
266-
* - `onPanResponderRelease: (e, gestureState) => {...}`
267-
* - `onPanResponderMove: (e, gestureState) => {...}`
268-
* - `onPanResponderTerminate: (e, gestureState) => {...}`
269-
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
270-
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
271-
*
272-
* In general, for events that have capture equivalents, we update the
273-
* gestureState once in the capture phase and can use it in the bubble phase
274-
* as well.
275-
*
276-
* Be careful with onStartShould* callbacks. They only reflect updated
277-
* `gestureState` for start/end events that bubble/capture to the Node.
278-
* Once the node is the responder, you can rely on every start/end event
279-
* being processed by the gesture and `gestureState` being updated
280-
* accordingly. (numberActiveTouches) may not be totally accurate unless you
281-
* are the responder.
282-
*/
283-
create: function(config) {
284-
const gestureState = {
285-
// Useful for debugging
286-
stateID: Math.random()
287-
};
288-
PanResponder._initializeGestureState(gestureState);
289-
const panHandlers = {
290-
onStartShouldSetResponder: function(e) {
291-
return config.onStartShouldSetPanResponder === undefined
292-
? false
293-
: config.onStartShouldSetPanResponder(e, gestureState);
294-
},
295-
onMoveShouldSetResponder: function(e) {
296-
return config.onMoveShouldSetPanResponder === undefined
297-
? false
298-
: config.onMoveShouldSetPanResponder(e, gestureState);
299-
},
300-
onStartShouldSetResponderCapture: function(e) {
301-
// TODO: Actually, we should reinitialize the state any time
302-
// touches.length increases from 0 active to > 0 active.
303-
if (e.nativeEvent.touches) {
304-
if (e.nativeEvent.touches.length === 1) {
305-
PanResponder._initializeGestureState(gestureState);
306-
}
307-
} else if (
308-
e.nativeEvent.originalEvent &&
309-
e.nativeEvent.originalEvent.type === 'mousedown'
310-
) {
311-
PanResponder._initializeGestureState(gestureState);
312-
}
313-
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
314-
return config.onStartShouldSetPanResponderCapture !== undefined
315-
? config.onStartShouldSetPanResponderCapture(e, gestureState)
316-
: false;
317-
},
318-
319-
onMoveShouldSetResponderCapture: function(e) {
320-
const touchHistory = e.touchHistory;
321-
// Responder system incorrectly dispatches should* to current responder
322-
// Filter out any touch moves past the first one - we would have
323-
// already processed multi-touch geometry during the first event.
324-
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
325-
return false;
326-
}
327-
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
328-
return config.onMoveShouldSetPanResponderCapture
329-
? config.onMoveShouldSetPanResponderCapture(e, gestureState)
330-
: false;
331-
},
332-
333-
onResponderGrant: function(e) {
334-
gestureState.x0 = currentCentroidX(e.touchHistory);
335-
gestureState.y0 = currentCentroidY(e.touchHistory);
336-
gestureState.dx = 0;
337-
gestureState.dy = 0;
338-
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
339-
// TODO: t7467124 investigate if this can be removed
340-
return config.onShouldBlockNativeResponder === undefined
341-
? true
342-
: config.onShouldBlockNativeResponder();
343-
},
344-
345-
onResponderReject: function(e) {
346-
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
347-
},
348-
349-
onResponderRelease: function(e) {
350-
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
351-
PanResponder._initializeGestureState(gestureState);
352-
},
353-
354-
onResponderStart: function(e) {
355-
const touchHistory = e.touchHistory;
356-
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
357-
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
358-
},
359-
360-
onResponderMove: function(e) {
361-
const touchHistory = e.touchHistory;
362-
// Guard against the dispatch of two touch moves when there are two
363-
// simultaneously changed touches.
364-
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
365-
return;
366-
}
367-
// Filter out any touch moves past the first one - we would have
368-
// already processed multi-touch geometry during the first event.
369-
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
370-
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
371-
},
372-
373-
onResponderEnd: function(e) {
374-
const touchHistory = e.touchHistory;
375-
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
376-
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
377-
},
378-
379-
onResponderTerminate: function(e) {
380-
config.onPanResponderTerminate && config.onPanResponderTerminate(e, gestureState);
381-
PanResponder._initializeGestureState(gestureState);
382-
},
383-
384-
onResponderTerminationRequest: function(e) {
385-
return config.onPanResponderTerminationRequest === undefined
386-
? true
387-
: config.onPanResponderTerminationRequest(e, gestureState);
388-
}
389-
};
390-
return { panHandlers: panHandlers };
391-
}
392-
};
393-
1+
import PanResponder from '../../vendor/PanResponder';
3942
export default PanResponder;

src/vendor/PanResponder/SHA

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c

0 commit comments

Comments
 (0)