Only grab InteractionManager handle in PanResponder, not all touches

Summary:
Previously, `InteractionManager` was baked in at the lowest level to all touches via `ResponderEventPlugin`,
which meant that any time a finger was touching the screen, `InteractionManager` would be locked. This included while
doing 100% native scrolls, and thus would block progress from Relay, Incremental, or anything else scheduling events
through `InteractionManager`.

This diff switches to only bake it into `PanResponder` (and it remains hooked into `Animated` as before) which are the
main two cases where we need 60fps JS execution and want to queue up slower tasks.

This is done with a reusable higher-order-responder `InteractionManager.createResponderClass`.

Depends on FYI https://github.com/facebook/react/pull/6587, https://github.com/facebook/react/pull/6584

Reviewed By: sebmarkbage

Differential Revision: D3210951

fb-gh-sync-id: 682d21ac5cff704673b63d5942a903a3d8912835
fbshipit-source-id: 682d21ac5cff704673b63d5942a903a3d8912835
This commit is contained in:
Spencer Ahrens 2016-04-26 00:22:28 -07:00 committed by Facebook Github Bot 2
parent c05169d8b7
commit 667d278119
2 changed files with 92 additions and 14 deletions

View File

@ -119,6 +119,56 @@ var InteractionManager = {
_deleteInteractionSet.add(handle);
},
/**
* Can be used to turn a regular repsonder into one that holds interaction handles
* when the responder is granted. This makes it easier to acheive 60fps responder
* interactions in JS, e.g. for drag-and-drop gestures with PanResponder.
*/
createResponderFactory(baseResponderFactory: {create: (config: Object) => Object}) {
function clearInteractionHandle(
interactionState: {handle: ?Handle},
callback: Function,
event: Object,
gestureState: Object
) {
if (interactionState.handle) {
InteractionManager.clearInteractionHandle(interactionState.handle);
interactionState.handle = null;
}
if (callback) {
callback(event, gestureState);
}
}
return {
create: function(config: Object) {
const interactionState = {
handle: (null: ?Handle),
};
const newConfig = {
...config,
onPanResponderGrant: function (e, gestureState) {
if (!interactionState.handle) {
interactionState.handle = InteractionManager.createInteractionHandle();
}
if (config.onPanResponderGrant) {
config.onPanResponderGrant(e, gestureState);
}
},
onPanResponderReject: function (e, gestureState) {
clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState);
},
onPanResponderRelease: function (e, gestureState) {
clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState);
},
onPanResponderTerminate: function (e, gestureState) {
clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState);
},
};
return baseResponderFactory.create(newConfig);
}
};
},
addListener: _emitter.addListener.bind(_emitter),
/**

View File

@ -11,6 +11,7 @@
'use strict';
var InteractionManager = require('./InteractionManager');
var TouchHistoryMath = require('TouchHistoryMath');
var currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
@ -25,6 +26,9 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY;
* 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
@ -275,15 +279,19 @@ var PanResponder = {
create: function (config) {
var gestureState = {
// Useful for debugging
stateID: Math.random()
stateID: Math.random(),
};
PanResponder._initializeGestureState(gestureState);
var panHandlers = {
onStartShouldSetResponder: function (e) {
return config.onStartShouldSetPanResponder === undefined ? false : config.onStartShouldSetPanResponder(e, gestureState);
return config.onStartShouldSetPanResponder === undefined ?
false :
config.onStartShouldSetPanResponder(e, gestureState);
},
onMoveShouldSetResponder: function (e) {
return config.onMoveShouldSetPanResponder === undefined ? false : config.onMoveShouldSetPanResponder(e, gestureState);
return config.onMoveShouldSetPanResponder === undefined ?
false :
config.onMoveShouldSetPanResponder(e, gestureState);
},
onStartShouldSetResponderCapture: function (e) {
// TODO: Actually, we should reinitialize the state any time
@ -292,7 +300,9 @@ var PanResponder = {
PanResponder._initializeGestureState(gestureState);
}
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
return config.onStartShouldSetPanResponderCapture !== undefined ? config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
return config.onStartShouldSetPanResponderCapture !== undefined ?
config.onStartShouldSetPanResponderCapture(e, gestureState) :
false;
},
onMoveShouldSetResponderCapture: function (e) {
@ -304,7 +314,9 @@ var PanResponder = {
return false;
}
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
return config.onMoveShouldSetPanResponderCapture ? config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
return config.onMoveShouldSetPanResponderCapture ?
config.onMoveShouldSetPanResponderCapture(e, gestureState) :
false;
},
onResponderGrant: function (e) {
@ -312,24 +324,34 @@ var PanResponder = {
gestureState.y0 = currentCentroidY(e.touchHistory);
gestureState.dx = 0;
gestureState.dy = 0;
if (config.onPanResponderGrant) config.onPanResponderGrant(e, gestureState);
if (config.onPanResponderGrant) {
config.onPanResponderGrant(e, gestureState);
}
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined ? true : config.onShouldBlockNativeResponder();
return config.onShouldBlockNativeResponder === undefined ?
true :
config.onShouldBlockNativeResponder();
},
onResponderReject: function (e) {
if (config.onPanResponderReject) config.onPanResponderReject(e, gestureState);
if (config.onPanResponderReject) {
config.onPanResponderReject(e, gestureState);
}
},
onResponderRelease: function (e) {
if (config.onPanResponderRelease) config.onPanResponderRelease(e, gestureState);
if (config.onPanResponderRelease) {
config.onPanResponderRelease(e, gestureState);
}
PanResponder._initializeGestureState(gestureState);
},
onResponderStart: function (e) {
var touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
if (config.onPanResponderStart) config.onPanResponderStart(e, gestureState);
if (config.onPanResponderStart) {
config.onPanResponderStart(e, gestureState);
}
},
onResponderMove: function (e) {
@ -342,13 +364,17 @@ var PanResponder = {
// 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);
if (config.onPanResponderMove) {
config.onPanResponderMove(e, gestureState);
}
},
onResponderEnd: function (e) {
var touchHistory = e.touchHistory;
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
if (config.onPanResponderEnd) config.onPanResponderEnd(e, gestureState);
if (config.onPanResponderEnd) {
config.onPanResponderEnd(e, gestureState);
}
},
onResponderTerminate: function (e) {
@ -359,11 +385,13 @@ var PanResponder = {
},
onResponderTerminationRequest: function (e) {
return config.onPanResponderTerminationRequest === undefined ? true : config.onPanResponderTerminationRequest(e, gestureState);
return config.onPanResponderTerminationRequest === undefined ?
true :
config.onPanResponderTerminationRequest(e, gestureState);
}
};
return { panHandlers: panHandlers };
}
};
module.exports = PanResponder;
module.exports = InteractionManager.createResponderFactory(PanResponder);