mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-07 20:21:25 +00:00
@@ -1,394 +1,2 @@
|
|||||||
/**
|
import PanResponder from '../../vendor/PanResponder';
|
||||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @providesModule PanResponder
|
|
||||||
* @noflow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import TouchHistoryMath from '../../vendor/TouchHistoryMath';
|
|
||||||
|
|
||||||
const currentCentroidXOfTouchesChangedAfter =
|
|
||||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
|
||||||
const currentCentroidYOfTouchesChangedAfter =
|
|
||||||
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
|
||||||
const previousCentroidXOfTouchesChangedAfter =
|
|
||||||
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
|
|
||||||
const previousCentroidYOfTouchesChangedAfter =
|
|
||||||
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
|
|
||||||
const currentCentroidX = TouchHistoryMath.currentCentroidX;
|
|
||||||
const currentCentroidY = TouchHistoryMath.currentCentroidY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `PanResponder` reconciles several touches into a single gesture. It makes
|
|
||||||
* single-touch gestures resilient to extra touches, and can be used to
|
|
||||||
* recognize simple multi-touch gestures.
|
|
||||||
*
|
|
||||||
* It provides a predictable wrapper of the responder handlers provided by the
|
|
||||||
* [gesture responder system](docs/gesture-responder-system.html).
|
|
||||||
* For each handler, it provides a new `gestureState` object alongside the
|
|
||||||
* native event object:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* onPanResponderMove: (event, gestureState) => {}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* A native event is a synthetic touch event with the following form:
|
|
||||||
*
|
|
||||||
* - `nativeEvent`
|
|
||||||
* + `changedTouches` - Array of all touch events that have changed since the last event
|
|
||||||
* + `identifier` - The ID of the touch
|
|
||||||
* + `locationX` - The X position of the touch, relative to the element
|
|
||||||
* + `locationY` - The Y position of the touch, relative to the element
|
|
||||||
* + `pageX` - The X position of the touch, relative to the root element
|
|
||||||
* + `pageY` - The Y position of the touch, relative to the root element
|
|
||||||
* + `target` - The node id of the element receiving the touch event
|
|
||||||
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
|
|
||||||
* + `touches` - Array of all current touches on the screen
|
|
||||||
*
|
|
||||||
* A `gestureState` object has the following:
|
|
||||||
*
|
|
||||||
* - `stateID` - ID of the gestureState- persisted as long as there at least
|
|
||||||
* one touch on screen
|
|
||||||
* - `moveX` - the latest screen coordinates of the recently-moved touch
|
|
||||||
* - `moveY` - the latest screen coordinates of the recently-moved touch
|
|
||||||
* - `x0` - the screen coordinates of the responder grant
|
|
||||||
* - `y0` - the screen coordinates of the responder grant
|
|
||||||
* - `dx` - accumulated distance of the gesture since the touch started
|
|
||||||
* - `dy` - accumulated distance of the gesture since the touch started
|
|
||||||
* - `vx` - current velocity of the gesture
|
|
||||||
* - `vy` - current velocity of the gesture
|
|
||||||
* - `numberActiveTouches` - Number of touches currently on screen
|
|
||||||
*
|
|
||||||
* ### Basic Usage
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* componentWillMount: function() {
|
|
||||||
* this._panResponder = PanResponder.create({
|
|
||||||
* // Ask to be the responder:
|
|
||||||
* onStartShouldSetPanResponder: (evt, gestureState) => true,
|
|
||||||
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
|
|
||||||
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
|
||||||
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
|
|
||||||
*
|
|
||||||
* onPanResponderGrant: (evt, gestureState) => {
|
|
||||||
* // The guesture has started. Show visual feedback so the user knows
|
|
||||||
* // what is happening!
|
|
||||||
*
|
|
||||||
* // gestureState.{x,y}0 will be set to zero now
|
|
||||||
* },
|
|
||||||
* onPanResponderMove: (evt, gestureState) => {
|
|
||||||
* // The most recent move distance is gestureState.move{X,Y}
|
|
||||||
*
|
|
||||||
* // The accumulated gesture distance since becoming responder is
|
|
||||||
* // gestureState.d{x,y}
|
|
||||||
* },
|
|
||||||
* onPanResponderTerminationRequest: (evt, gestureState) => true,
|
|
||||||
* onPanResponderRelease: (evt, gestureState) => {
|
|
||||||
* // The user has released all touches while this view is the
|
|
||||||
* // responder. This typically means a gesture has succeeded
|
|
||||||
* },
|
|
||||||
* onPanResponderTerminate: (evt, gestureState) => {
|
|
||||||
* // Another component has become the responder, so this gesture
|
|
||||||
* // should be cancelled
|
|
||||||
* },
|
|
||||||
* onShouldBlockNativeResponder: (evt, gestureState) => {
|
|
||||||
* // Returns whether this component should block native components from becoming the JS
|
|
||||||
* // responder. Returns true by default. Is currently only supported on android.
|
|
||||||
* return true;
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
* },
|
|
||||||
*
|
|
||||||
* render: function() {
|
|
||||||
* return (
|
|
||||||
* <View {...this._panResponder.panHandlers} />
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* ### Working Example
|
|
||||||
*
|
|
||||||
* To see it in action, try the
|
|
||||||
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const PanResponder = {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* A graphical explanation of the touch data flow:
|
|
||||||
*
|
|
||||||
* +----------------------------+ +--------------------------------+
|
|
||||||
* | ResponderTouchHistoryStore | |TouchHistoryMath |
|
|
||||||
* +----------------------------+ +----------+---------------------+
|
|
||||||
* |Global store of touchHistory| |Allocation-less math util |
|
|
||||||
* |including activeness, start | |on touch history (centroids |
|
|
||||||
* |position, prev/cur position.| |and multitouch movement etc) |
|
|
||||||
* | | | |
|
|
||||||
* +----^-----------------------+ +----^---------------------------+
|
|
||||||
* | |
|
|
||||||
* | (records relevant history |
|
|
||||||
* | of touches relevant for |
|
|
||||||
* | implementing higher level |
|
|
||||||
* | gestures) |
|
|
||||||
* | |
|
|
||||||
* +----+-----------------------+ +----|---------------------------+
|
|
||||||
* | ResponderEventPlugin | | | Your App/Component |
|
|
||||||
* +----------------------------+ +----|---------------------------+
|
|
||||||
* |Negotiates which view gets | Low level | | High level |
|
|
||||||
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
|
|
||||||
* |Also records history into | touchHistory| | Pan | multitouch + |
|
|
||||||
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
|
|
||||||
* +----------------------------+ attached to | | | distance and |
|
|
||||||
* each event | +---------+ velocity. |
|
|
||||||
* | |
|
|
||||||
* | |
|
|
||||||
* +--------------------------------+
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Gesture that calculates cumulative movement over time in a way that just
|
|
||||||
* "does the right thing" for multiple touches. The "right thing" is very
|
|
||||||
* nuanced. When moving two touches in opposite directions, the cumulative
|
|
||||||
* distance is zero in each dimension. When two touches move in parallel five
|
|
||||||
* pixels in the same direction, the cumulative distance is five, not ten. If
|
|
||||||
* two touches start, one moves five in a direction, then stops and the other
|
|
||||||
* touch moves fives in the same direction, the cumulative distance is ten.
|
|
||||||
*
|
|
||||||
* This logic requires a kind of processing of time "clusters" of touch events
|
|
||||||
* so that two touch moves that essentially occur in parallel but move every
|
|
||||||
* other frame respectively, are considered part of the same movement.
|
|
||||||
*
|
|
||||||
* Explanation of some of the non-obvious fields:
|
|
||||||
*
|
|
||||||
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
|
|
||||||
* invalid. If a move event has been observed, `(moveX, moveY)` is the
|
|
||||||
* centroid of the most recently moved "cluster" of active touches.
|
|
||||||
* (Currently all move have the same timeStamp, but later we should add some
|
|
||||||
* threshold for what is considered to be "moving"). If a palm is
|
|
||||||
* accidentally counted as a touch, but a finger is moving greatly, the palm
|
|
||||||
* will move slightly, but we only want to count the single moving touch.
|
|
||||||
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
|
|
||||||
* responder.
|
|
||||||
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
|
|
||||||
* distance. Accounts for touch moves that are clustered together in time,
|
|
||||||
* moving the same direction. Only valid when currently responder (otherwise,
|
|
||||||
* it only represents the drag distance below the threshold).
|
|
||||||
* - vx/vy: Velocity.
|
|
||||||
*/
|
|
||||||
|
|
||||||
_initializeGestureState: function(gestureState) {
|
|
||||||
gestureState.moveX = 0;
|
|
||||||
gestureState.moveY = 0;
|
|
||||||
gestureState.x0 = 0;
|
|
||||||
gestureState.y0 = 0;
|
|
||||||
gestureState.dx = 0;
|
|
||||||
gestureState.dy = 0;
|
|
||||||
gestureState.vx = 0;
|
|
||||||
gestureState.vy = 0;
|
|
||||||
gestureState.numberActiveTouches = 0;
|
|
||||||
// All `gestureState` accounts for timeStamps up until:
|
|
||||||
gestureState._accountsForMovesUpTo = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is nuanced and is necessary. It is incorrect to continuously take all
|
|
||||||
* active *and* recently moved touches, find the centroid, and track how that
|
|
||||||
* result changes over time. Instead, we must take all recently moved
|
|
||||||
* touches, and calculate how the centroid has changed just for those
|
|
||||||
* recently moved touches, and append that change to an accumulator. This is
|
|
||||||
* to (at least) handle the case where the user is moving three fingers, and
|
|
||||||
* then one of the fingers stops but the other two continue.
|
|
||||||
*
|
|
||||||
* This is very different than taking all of the recently moved touches and
|
|
||||||
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
|
|
||||||
* changes* in the centroid of recently moved touches.
|
|
||||||
*
|
|
||||||
* There is also some nuance with how we handle multiple moved touches in a
|
|
||||||
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
|
|
||||||
* individual events, multiple touches generate two 'move' events, each of
|
|
||||||
* them triggering `onResponderMove`. But with the way `PanResponder` works,
|
|
||||||
* all of the gesture inference is performed on the first dispatch, since it
|
|
||||||
* looks at all of the touches (even the ones for which there hasn't been a
|
|
||||||
* native dispatch yet). Therefore, `PanResponder` does not call
|
|
||||||
* `onResponderMove` passed the first dispatch. This diverges from the
|
|
||||||
* typical responder callback pattern (without using `PanResponder`), but
|
|
||||||
* avoids more dispatches than necessary.
|
|
||||||
*/
|
|
||||||
_updateGestureStateOnMove: function(gestureState, touchHistory) {
|
|
||||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
|
||||||
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
|
|
||||||
touchHistory,
|
|
||||||
gestureState._accountsForMovesUpTo
|
|
||||||
);
|
|
||||||
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
|
|
||||||
touchHistory,
|
|
||||||
gestureState._accountsForMovesUpTo
|
|
||||||
);
|
|
||||||
const movedAfter = gestureState._accountsForMovesUpTo;
|
|
||||||
const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
|
||||||
const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
|
||||||
const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
|
||||||
const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
|
||||||
const nextDX = gestureState.dx + (x - prevX);
|
|
||||||
const nextDY = gestureState.dy + (y - prevY);
|
|
||||||
|
|
||||||
// TODO: This must be filtered intelligently.
|
|
||||||
const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo;
|
|
||||||
gestureState.vx = (nextDX - gestureState.dx) / dt;
|
|
||||||
gestureState.vy = (nextDY - gestureState.dy) / dt;
|
|
||||||
|
|
||||||
gestureState.dx = nextDX;
|
|
||||||
gestureState.dy = nextDY;
|
|
||||||
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} config Enhanced versions of all of the responder callbacks
|
|
||||||
* that provide not only the typical `ResponderSyntheticEvent`, but also the
|
|
||||||
* `PanResponder` gesture state. Simply replace the word `Responder` with
|
|
||||||
* `PanResponder` in each of the typical `onResponder*` callbacks. For
|
|
||||||
* example, the `config` object would look like:
|
|
||||||
*
|
|
||||||
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
|
|
||||||
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
|
||||||
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
|
|
||||||
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderReject: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderGrant: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderStart: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderEnd: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderRelease: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderMove: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderTerminate: (e, gestureState) => {...}`
|
|
||||||
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
|
|
||||||
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
|
|
||||||
*
|
|
||||||
* In general, for events that have capture equivalents, we update the
|
|
||||||
* gestureState once in the capture phase and can use it in the bubble phase
|
|
||||||
* as well.
|
|
||||||
*
|
|
||||||
* Be careful with onStartShould* callbacks. They only reflect updated
|
|
||||||
* `gestureState` for start/end events that bubble/capture to the Node.
|
|
||||||
* Once the node is the responder, you can rely on every start/end event
|
|
||||||
* being processed by the gesture and `gestureState` being updated
|
|
||||||
* accordingly. (numberActiveTouches) may not be totally accurate unless you
|
|
||||||
* are the responder.
|
|
||||||
*/
|
|
||||||
create: function(config) {
|
|
||||||
const gestureState = {
|
|
||||||
// Useful for debugging
|
|
||||||
stateID: Math.random()
|
|
||||||
};
|
|
||||||
PanResponder._initializeGestureState(gestureState);
|
|
||||||
const panHandlers = {
|
|
||||||
onStartShouldSetResponder: function(e) {
|
|
||||||
return config.onStartShouldSetPanResponder === undefined
|
|
||||||
? false
|
|
||||||
: config.onStartShouldSetPanResponder(e, gestureState);
|
|
||||||
},
|
|
||||||
onMoveShouldSetResponder: function(e) {
|
|
||||||
return config.onMoveShouldSetPanResponder === undefined
|
|
||||||
? false
|
|
||||||
: config.onMoveShouldSetPanResponder(e, gestureState);
|
|
||||||
},
|
|
||||||
onStartShouldSetResponderCapture: function(e) {
|
|
||||||
// TODO: Actually, we should reinitialize the state any time
|
|
||||||
// touches.length increases from 0 active to > 0 active.
|
|
||||||
if (e.nativeEvent.touches) {
|
|
||||||
if (e.nativeEvent.touches.length === 1) {
|
|
||||||
PanResponder._initializeGestureState(gestureState);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
e.nativeEvent.originalEvent &&
|
|
||||||
e.nativeEvent.originalEvent.type === 'mousedown'
|
|
||||||
) {
|
|
||||||
PanResponder._initializeGestureState(gestureState);
|
|
||||||
}
|
|
||||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
|
||||||
return config.onStartShouldSetPanResponderCapture !== undefined
|
|
||||||
? config.onStartShouldSetPanResponderCapture(e, gestureState)
|
|
||||||
: false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onMoveShouldSetResponderCapture: function(e) {
|
|
||||||
const touchHistory = e.touchHistory;
|
|
||||||
// Responder system incorrectly dispatches should* to current responder
|
|
||||||
// Filter out any touch moves past the first one - we would have
|
|
||||||
// already processed multi-touch geometry during the first event.
|
|
||||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
|
||||||
return config.onMoveShouldSetPanResponderCapture
|
|
||||||
? config.onMoveShouldSetPanResponderCapture(e, gestureState)
|
|
||||||
: false;
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderGrant: function(e) {
|
|
||||||
gestureState.x0 = currentCentroidX(e.touchHistory);
|
|
||||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
|
||||||
gestureState.dx = 0;
|
|
||||||
gestureState.dy = 0;
|
|
||||||
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
|
|
||||||
// TODO: t7467124 investigate if this can be removed
|
|
||||||
return config.onShouldBlockNativeResponder === undefined
|
|
||||||
? true
|
|
||||||
: config.onShouldBlockNativeResponder();
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderReject: function(e) {
|
|
||||||
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderRelease: function(e) {
|
|
||||||
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
|
|
||||||
PanResponder._initializeGestureState(gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderStart: function(e) {
|
|
||||||
const touchHistory = e.touchHistory;
|
|
||||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
|
||||||
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderMove: function(e) {
|
|
||||||
const touchHistory = e.touchHistory;
|
|
||||||
// Guard against the dispatch of two touch moves when there are two
|
|
||||||
// simultaneously changed touches.
|
|
||||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Filter out any touch moves past the first one - we would have
|
|
||||||
// already processed multi-touch geometry during the first event.
|
|
||||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
|
||||||
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderEnd: function(e) {
|
|
||||||
const touchHistory = e.touchHistory;
|
|
||||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
|
||||||
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderTerminate: function(e) {
|
|
||||||
config.onPanResponderTerminate && config.onPanResponderTerminate(e, gestureState);
|
|
||||||
PanResponder._initializeGestureState(gestureState);
|
|
||||||
},
|
|
||||||
|
|
||||||
onResponderTerminationRequest: function(e) {
|
|
||||||
return config.onPanResponderTerminationRequest === undefined
|
|
||||||
? true
|
|
||||||
: config.onPanResponderTerminationRequest(e, gestureState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return { panHandlers: panHandlers };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PanResponder;
|
export default PanResponder;
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c
|
||||||
Vendored
+415
@@ -0,0 +1,415 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @providesModule PanResponder
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const InteractionManager = require('../../apis/InteractionManager').default;
|
||||||
|
const TouchHistoryMath = require('../TouchHistoryMath');
|
||||||
|
|
||||||
|
const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||||
|
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
||||||
|
const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
|
||||||
|
const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
|
||||||
|
const currentCentroidX = TouchHistoryMath.currentCentroidX;
|
||||||
|
const currentCentroidY = TouchHistoryMath.currentCentroidY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `PanResponder` reconciles several touches into a single gesture. It makes
|
||||||
|
* single-touch gestures resilient to extra touches, and can be used to
|
||||||
|
* recognize simple multi-touch gestures.
|
||||||
|
*
|
||||||
|
* By default, `PanResponder` holds an `InteractionManager` handle to block
|
||||||
|
* long-running JS events from interrupting active gestures.
|
||||||
|
*
|
||||||
|
* It provides a predictable wrapper of the responder handlers provided by the
|
||||||
|
* [gesture responder system](docs/gesture-responder-system.html).
|
||||||
|
* For each handler, it provides a new `gestureState` object alongside the
|
||||||
|
* native event object:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* onPanResponderMove: (event, gestureState) => {}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* A native event is a synthetic touch event with the following form:
|
||||||
|
*
|
||||||
|
* - `nativeEvent`
|
||||||
|
* + `changedTouches` - Array of all touch events that have changed since the last event
|
||||||
|
* + `identifier` - The ID of the touch
|
||||||
|
* + `locationX` - The X position of the touch, relative to the element
|
||||||
|
* + `locationY` - The Y position of the touch, relative to the element
|
||||||
|
* + `pageX` - The X position of the touch, relative to the root element
|
||||||
|
* + `pageY` - The Y position of the touch, relative to the root element
|
||||||
|
* + `target` - The node id of the element receiving the touch event
|
||||||
|
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
|
||||||
|
* + `touches` - Array of all current touches on the screen
|
||||||
|
*
|
||||||
|
* A `gestureState` object has the following:
|
||||||
|
*
|
||||||
|
* - `stateID` - ID of the gestureState- persisted as long as there at least
|
||||||
|
* one touch on screen
|
||||||
|
* - `moveX` - the latest screen coordinates of the recently-moved touch
|
||||||
|
* - `moveY` - the latest screen coordinates of the recently-moved touch
|
||||||
|
* - `x0` - the screen coordinates of the responder grant
|
||||||
|
* - `y0` - the screen coordinates of the responder grant
|
||||||
|
* - `dx` - accumulated distance of the gesture since the touch started
|
||||||
|
* - `dy` - accumulated distance of the gesture since the touch started
|
||||||
|
* - `vx` - current velocity of the gesture
|
||||||
|
* - `vy` - current velocity of the gesture
|
||||||
|
* - `numberActiveTouches` - Number of touches currently on screen
|
||||||
|
*
|
||||||
|
* ### Basic Usage
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* componentWillMount: function() {
|
||||||
|
* this._panResponder = PanResponder.create({
|
||||||
|
* // Ask to be the responder:
|
||||||
|
* onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||||
|
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||||
|
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
||||||
|
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||||
|
*
|
||||||
|
* onPanResponderGrant: (evt, gestureState) => {
|
||||||
|
* // The gesture has started. Show visual feedback so the user knows
|
||||||
|
* // what is happening!
|
||||||
|
*
|
||||||
|
* // gestureState.d{x,y} will be set to zero now
|
||||||
|
* },
|
||||||
|
* onPanResponderMove: (evt, gestureState) => {
|
||||||
|
* // The most recent move distance is gestureState.move{X,Y}
|
||||||
|
*
|
||||||
|
* // The accumulated gesture distance since becoming responder is
|
||||||
|
* // gestureState.d{x,y}
|
||||||
|
* },
|
||||||
|
* onPanResponderTerminationRequest: (evt, gestureState) => true,
|
||||||
|
* onPanResponderRelease: (evt, gestureState) => {
|
||||||
|
* // The user has released all touches while this view is the
|
||||||
|
* // responder. This typically means a gesture has succeeded
|
||||||
|
* },
|
||||||
|
* onPanResponderTerminate: (evt, gestureState) => {
|
||||||
|
* // Another component has become the responder, so this gesture
|
||||||
|
* // should be cancelled
|
||||||
|
* },
|
||||||
|
* onShouldBlockNativeResponder: (evt, gestureState) => {
|
||||||
|
* // Returns whether this component should block native components from becoming the JS
|
||||||
|
* // responder. Returns true by default. Is currently only supported on android.
|
||||||
|
* return true;
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* },
|
||||||
|
*
|
||||||
|
* render: function() {
|
||||||
|
* return (
|
||||||
|
* <View {...this._panResponder.panHandlers} />
|
||||||
|
* );
|
||||||
|
* },
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ### Working Example
|
||||||
|
*
|
||||||
|
* To see it in action, try the
|
||||||
|
* [PanResponder example in RNTester](https://github.com/facebook/react-native/blob/master/RNTester/js/PanResponderExample.js)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PanResponder = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* A graphical explanation of the touch data flow:
|
||||||
|
*
|
||||||
|
* +----------------------------+ +--------------------------------+
|
||||||
|
* | ResponderTouchHistoryStore | |TouchHistoryMath |
|
||||||
|
* +----------------------------+ +----------+---------------------+
|
||||||
|
* |Global store of touchHistory| |Allocation-less math util |
|
||||||
|
* |including activeness, start | |on touch history (centroids |
|
||||||
|
* |position, prev/cur position.| |and multitouch movement etc) |
|
||||||
|
* | | | |
|
||||||
|
* +----^-----------------------+ +----^---------------------------+
|
||||||
|
* | |
|
||||||
|
* | (records relevant history |
|
||||||
|
* | of touches relevant for |
|
||||||
|
* | implementing higher level |
|
||||||
|
* | gestures) |
|
||||||
|
* | |
|
||||||
|
* +----+-----------------------+ +----|---------------------------+
|
||||||
|
* | ResponderEventPlugin | | | Your App/Component |
|
||||||
|
* +----------------------------+ +----|---------------------------+
|
||||||
|
* |Negotiates which view gets | Low level | | High level |
|
||||||
|
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
|
||||||
|
* |Also records history into | touchHistory| | Pan | multitouch + |
|
||||||
|
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
|
||||||
|
* +----------------------------+ attached to | | | distance and |
|
||||||
|
* each event | +---------+ velocity. |
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* +--------------------------------+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Gesture that calculates cumulative movement over time in a way that just
|
||||||
|
* "does the right thing" for multiple touches. The "right thing" is very
|
||||||
|
* nuanced. When moving two touches in opposite directions, the cumulative
|
||||||
|
* distance is zero in each dimension. When two touches move in parallel five
|
||||||
|
* pixels in the same direction, the cumulative distance is five, not ten. If
|
||||||
|
* two touches start, one moves five in a direction, then stops and the other
|
||||||
|
* touch moves fives in the same direction, the cumulative distance is ten.
|
||||||
|
*
|
||||||
|
* This logic requires a kind of processing of time "clusters" of touch events
|
||||||
|
* so that two touch moves that essentially occur in parallel but move every
|
||||||
|
* other frame respectively, are considered part of the same movement.
|
||||||
|
*
|
||||||
|
* Explanation of some of the non-obvious fields:
|
||||||
|
*
|
||||||
|
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
|
||||||
|
* invalid. If a move event has been observed, `(moveX, moveY)` is the
|
||||||
|
* centroid of the most recently moved "cluster" of active touches.
|
||||||
|
* (Currently all move have the same timeStamp, but later we should add some
|
||||||
|
* threshold for what is considered to be "moving"). If a palm is
|
||||||
|
* accidentally counted as a touch, but a finger is moving greatly, the palm
|
||||||
|
* will move slightly, but we only want to count the single moving touch.
|
||||||
|
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
|
||||||
|
* responder.
|
||||||
|
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
|
||||||
|
* distance. Accounts for touch moves that are clustered together in time,
|
||||||
|
* moving the same direction. Only valid when currently responder (otherwise,
|
||||||
|
* it only represents the drag distance below the threshold).
|
||||||
|
* - vx/vy: Velocity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_initializeGestureState: function (gestureState) {
|
||||||
|
gestureState.moveX = 0;
|
||||||
|
gestureState.moveY = 0;
|
||||||
|
gestureState.x0 = 0;
|
||||||
|
gestureState.y0 = 0;
|
||||||
|
gestureState.dx = 0;
|
||||||
|
gestureState.dy = 0;
|
||||||
|
gestureState.vx = 0;
|
||||||
|
gestureState.vy = 0;
|
||||||
|
gestureState.numberActiveTouches = 0;
|
||||||
|
// All `gestureState` accounts for timeStamps up until:
|
||||||
|
gestureState._accountsForMovesUpTo = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is nuanced and is necessary. It is incorrect to continuously take all
|
||||||
|
* active *and* recently moved touches, find the centroid, and track how that
|
||||||
|
* result changes over time. Instead, we must take all recently moved
|
||||||
|
* touches, and calculate how the centroid has changed just for those
|
||||||
|
* recently moved touches, and append that change to an accumulator. This is
|
||||||
|
* to (at least) handle the case where the user is moving three fingers, and
|
||||||
|
* then one of the fingers stops but the other two continue.
|
||||||
|
*
|
||||||
|
* This is very different than taking all of the recently moved touches and
|
||||||
|
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
|
||||||
|
* changes* in the centroid of recently moved touches.
|
||||||
|
*
|
||||||
|
* There is also some nuance with how we handle multiple moved touches in a
|
||||||
|
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
|
||||||
|
* individual events, multiple touches generate two 'move' events, each of
|
||||||
|
* them triggering `onResponderMove`. But with the way `PanResponder` works,
|
||||||
|
* all of the gesture inference is performed on the first dispatch, since it
|
||||||
|
* looks at all of the touches (even the ones for which there hasn't been a
|
||||||
|
* native dispatch yet). Therefore, `PanResponder` does not call
|
||||||
|
* `onResponderMove` passed the first dispatch. This diverges from the
|
||||||
|
* typical responder callback pattern (without using `PanResponder`), but
|
||||||
|
* avoids more dispatches than necessary.
|
||||||
|
*/
|
||||||
|
_updateGestureStateOnMove: function (gestureState, touchHistory) {
|
||||||
|
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||||
|
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
|
||||||
|
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo);
|
||||||
|
const movedAfter = gestureState._accountsForMovesUpTo;
|
||||||
|
const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||||
|
const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||||
|
const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||||
|
const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||||
|
const nextDX = gestureState.dx + (x - prevX);
|
||||||
|
const nextDY = gestureState.dy + (y - prevY);
|
||||||
|
|
||||||
|
// TODO: This must be filtered intelligently.
|
||||||
|
const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo;
|
||||||
|
gestureState.vx = (nextDX - gestureState.dx) / dt;
|
||||||
|
gestureState.vy = (nextDY - gestureState.dy) / dt;
|
||||||
|
|
||||||
|
gestureState.dx = nextDX;
|
||||||
|
gestureState.dy = nextDY;
|
||||||
|
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} config Enhanced versions of all of the responder callbacks
|
||||||
|
* that provide not only the typical `ResponderSyntheticEvent`, but also the
|
||||||
|
* `PanResponder` gesture state. Simply replace the word `Responder` with
|
||||||
|
* `PanResponder` in each of the typical `onResponder*` callbacks. For
|
||||||
|
* example, the `config` object would look like:
|
||||||
|
*
|
||||||
|
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
|
||||||
|
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||||
|
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
|
||||||
|
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderReject: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderGrant: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderStart: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderEnd: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderRelease: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderMove: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderTerminate: (e, gestureState) => {...}`
|
||||||
|
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
|
||||||
|
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
|
||||||
|
*
|
||||||
|
* In general, for events that have capture equivalents, we update the
|
||||||
|
* gestureState once in the capture phase and can use it in the bubble phase
|
||||||
|
* as well.
|
||||||
|
*
|
||||||
|
* Be careful with onStartShould* callbacks. They only reflect updated
|
||||||
|
* `gestureState` for start/end events that bubble/capture to the Node.
|
||||||
|
* Once the node is the responder, you can rely on every start/end event
|
||||||
|
* being processed by the gesture and `gestureState` being updated
|
||||||
|
* accordingly. (numberActiveTouches) may not be totally accurate unless you
|
||||||
|
* are the responder.
|
||||||
|
*/
|
||||||
|
create: function (config) {
|
||||||
|
const interactionState = {
|
||||||
|
handle: (null: ?number),
|
||||||
|
};
|
||||||
|
const gestureState = {
|
||||||
|
// Useful for debugging
|
||||||
|
stateID: Math.random(),
|
||||||
|
};
|
||||||
|
PanResponder._initializeGestureState(gestureState);
|
||||||
|
const panHandlers = {
|
||||||
|
onStartShouldSetResponder: function (e) {
|
||||||
|
return config.onStartShouldSetPanResponder === undefined ?
|
||||||
|
false :
|
||||||
|
config.onStartShouldSetPanResponder(e, gestureState);
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponder: function (e) {
|
||||||
|
return config.onMoveShouldSetPanResponder === undefined ?
|
||||||
|
false :
|
||||||
|
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||||
|
},
|
||||||
|
onStartShouldSetResponderCapture: function (e) {
|
||||||
|
// TODO: Actually, we should reinitialize the state any time
|
||||||
|
// touches.length increases from 0 active to > 0 active.
|
||||||
|
if (e.nativeEvent.touches.length === 1) {
|
||||||
|
PanResponder._initializeGestureState(gestureState);
|
||||||
|
}
|
||||||
|
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||||
|
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||||
|
config.onStartShouldSetPanResponderCapture(e, gestureState) :
|
||||||
|
false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMoveShouldSetResponderCapture: function (e) {
|
||||||
|
const touchHistory = e.touchHistory;
|
||||||
|
// Responder system incorrectly dispatches should* to current responder
|
||||||
|
// Filter out any touch moves past the first one - we would have
|
||||||
|
// already processed multi-touch geometry during the first event.
|
||||||
|
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||||
|
return config.onMoveShouldSetPanResponderCapture ?
|
||||||
|
config.onMoveShouldSetPanResponderCapture(e, gestureState) :
|
||||||
|
false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderGrant: function (e) {
|
||||||
|
if (!interactionState.handle) {
|
||||||
|
interactionState.handle = InteractionManager.createInteractionHandle();
|
||||||
|
}
|
||||||
|
gestureState.x0 = currentCentroidX(e.touchHistory);
|
||||||
|
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||||
|
gestureState.dx = 0;
|
||||||
|
gestureState.dy = 0;
|
||||||
|
if (config.onPanResponderGrant) {
|
||||||
|
config.onPanResponderGrant(e, gestureState);
|
||||||
|
}
|
||||||
|
// TODO: t7467124 investigate if this can be removed
|
||||||
|
return config.onShouldBlockNativeResponder === undefined ?
|
||||||
|
true :
|
||||||
|
config.onShouldBlockNativeResponder();
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderReject: function (e) {
|
||||||
|
clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState);
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderRelease: function (e) {
|
||||||
|
clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState);
|
||||||
|
PanResponder._initializeGestureState(gestureState);
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderStart: function (e) {
|
||||||
|
const touchHistory = e.touchHistory;
|
||||||
|
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||||
|
if (config.onPanResponderStart) {
|
||||||
|
config.onPanResponderStart(e, gestureState);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderMove: function (e) {
|
||||||
|
const touchHistory = e.touchHistory;
|
||||||
|
// Guard against the dispatch of two touch moves when there are two
|
||||||
|
// simultaneously changed touches.
|
||||||
|
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Filter out any touch moves past the first one - we would have
|
||||||
|
// already processed multi-touch geometry during the first event.
|
||||||
|
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||||
|
if (config.onPanResponderMove) {
|
||||||
|
config.onPanResponderMove(e, gestureState);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderEnd: function (e) {
|
||||||
|
const touchHistory = e.touchHistory;
|
||||||
|
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||||
|
clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState);
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderTerminate: function (e) {
|
||||||
|
clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState);
|
||||||
|
PanResponder._initializeGestureState(gestureState);
|
||||||
|
},
|
||||||
|
|
||||||
|
onResponderTerminationRequest: function (e) {
|
||||||
|
return config.onPanResponderTerminationRequest === undefined ?
|
||||||
|
true :
|
||||||
|
config.onPanResponderTerminationRequest(e, gestureState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
panHandlers,
|
||||||
|
getInteractionHandle(): ?number {
|
||||||
|
return interactionState.handle;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function clearInteractionHandle(
|
||||||
|
interactionState: {handle: ?number},
|
||||||
|
callback: Function,
|
||||||
|
event: Object,
|
||||||
|
gestureState: Object
|
||||||
|
) {
|
||||||
|
if (interactionState.handle) {
|
||||||
|
InteractionManager.clearInteractionHandle(interactionState.handle);
|
||||||
|
interactionState.handle = null;
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(event, gestureState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PanResponder;
|
||||||
Vendored
+1
-1
@@ -121,4 +121,4 @@ var TouchHistoryMath = {
|
|||||||
noCentroid: -1
|
noCentroid: -1
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TouchHistoryMath;
|
module.exports = TouchHistoryMath;
|
||||||
|
|||||||
Reference in New Issue
Block a user