[ReactNative] Start Navigator gesture config, disable gesture in AdsManager

This commit is contained in:
Eric Vicenti 2015-04-03 14:33:27 -07:00
parent 6e6bd28f3a
commit 34ed3a0cec
3 changed files with 236 additions and 122 deletions

View File

@ -52,26 +52,45 @@ var ROUTE_STACK = [
var INIT_ROUTE_INDEX = 1; var INIT_ROUTE_INDEX = 1;
class JumpingNavBar extends React.Component { class JumpingNavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
tabIndex: props.initTabIndex,
};
}
handleWillFocus(route) {
var tabIndex = ROUTE_STACK.indexOf(route);
this.setState({ tabIndex, });
}
render() { render() {
return ( return (
<View style={styles.tabs}> <View style={styles.tabs}>
<TabBarIOS> <TabBarIOS>
<TabBarIOS.Item <TabBarIOS.Item
icon={require('image!tabnav_notification')} icon={require('image!tabnav_notification')}
selected={this.props.tabIndex === 0} selected={this.state.tabIndex === 0}
onPress={() => { this.props.onTabIndex(0); }}> onPress={() => {
this.props.onTabIndex(0);
this.setState({ tabIndex: 0, });
}}>
<View /> <View />
</TabBarIOS.Item> </TabBarIOS.Item>
<TabBarIOS.Item <TabBarIOS.Item
icon={require('image!tabnav_list')} icon={require('image!tabnav_list')}
selected={this.props.tabIndex === 1} selected={this.state.tabIndex === 1}
onPress={() => { this.props.onTabIndex(1); }}> onPress={() => {
this.props.onTabIndex(1);
this.setState({ tabIndex: 1, });
}}>
<View /> <View />
</TabBarIOS.Item> </TabBarIOS.Item>
<TabBarIOS.Item <TabBarIOS.Item
icon={require('image!tabnav_settings')} icon={require('image!tabnav_settings')}
selected={this.props.tabIndex === 2} selected={this.state.tabIndex === 2}
onPress={() => { this.props.onTabIndex(2); }}> onPress={() => {
this.props.onTabIndex(2);
this.setState({ tabIndex: 2, });
}}>
<View /> <View />
</TabBarIOS.Item> </TabBarIOS.Item>
</TabBarIOS> </TabBarIOS>
@ -81,12 +100,6 @@ class JumpingNavBar extends React.Component {
} }
var JumpingNavSample = React.createClass({ var JumpingNavSample = React.createClass({
getInitialState: function() {
return {
tabIndex: INIT_ROUTE_INDEX,
};
},
render: function() { render: function() {
return ( return (
<Navigator <Navigator
@ -98,23 +111,19 @@ var JumpingNavSample = React.createClass({
initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]} initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]}
initialRouteStack={ROUTE_STACK} initialRouteStack={ROUTE_STACK}
renderScene={this.renderScene} renderScene={this.renderScene}
configureScene={() => ({
...Navigator.SceneConfigs.HorizontalSwipeJump,
})}
navigationBar={ navigationBar={
<JumpingNavBar <JumpingNavBar
ref={(navBar) => { this.navBar = navBar; }}
initTabIndex={INIT_ROUTE_INDEX}
routeStack={ROUTE_STACK} routeStack={ROUTE_STACK}
tabIndex={this.state.tabIndex}
onTabIndex={(index) => { onTabIndex={(index) => {
this.setState({ tabIndex: index }, () => { this._navigator.jumpTo(ROUTE_STACK[index]);
this._navigator.jumpTo(ROUTE_STACK[index]);
});
}} }}
/> />
} }
onWillFocus={(route) => {
this.setState({
tabIndex: ROUTE_STACK.indexOf(route),
});
}}
shouldJumpOnBackstackPop={true}
/> />
); );
}, },

View File

@ -54,8 +54,6 @@ var SCREEN_HEIGHT = Dimensions.get('window').height;
var OFF_SCREEN = {style: {opacity: 0}}; var OFF_SCREEN = {style: {opacity: 0}};
var NAVIGATION_BAR_REF = 'navigationBar_ref';
var __uid = 0; var __uid = 0;
function getuid() { function getuid() {
return __uid++; return __uid++;
@ -94,6 +92,12 @@ var styles = StyleSheet.create({
} }
}); });
var GESTURE_ACTIONS = [
'pop',
'jumpBack',
'jumpForward',
];
/** /**
* Use `Navigator` to transition between different scenes in your app. To * Use `Navigator` to transition between different scenes in your app. To
* accomplish this, provide route objects to the navigator to identify each * accomplish this, provide route objects to the navigator to identify each
@ -457,15 +461,18 @@ var Navigator = React.createClass({
_completeTransition: function() { _completeTransition: function() {
if (this.spring.getCurrentValue() === 1) { if (this.spring.getCurrentValue() === 1) {
var presentedIndex = this.state.toIndex; var presentedIndex = this.state.toIndex;
this.state.fromIndex = presentedIndex;
this.state.presentedIndex = presentedIndex; this.state.presentedIndex = presentedIndex;
this.state.fromIndex = presentedIndex;
this._emitDidFocus(presentedIndex); this._emitDidFocus(presentedIndex);
this._removePoppedRoutes(); this._removePoppedRoutes();
if (AnimationsDebugModule) { if (AnimationsDebugModule) {
AnimationsDebugModule.stopRecordingFps(Date.now()); AnimationsDebugModule.stopRecordingFps(Date.now());
} }
this._hideOtherScenes(presentedIndex); } else {
this.state.fromIndex = this.state.presentedIndex;
this.state.toIndex = this.state.presentedIndex;
} }
this._hideOtherScenes(presentedIndex);
}, },
_transitionToToIndexWithVelocity: function(v) { _transitionToToIndexWithVelocity: function(v) {
@ -499,6 +506,10 @@ var Navigator = React.createClass({
_emitWillFocus: function(index) { _emitWillFocus: function(index) {
var route = this.state.routeStack[index]; var route = this.state.routeStack[index];
var navBar = this._navBar;
if (navBar && navBar.handleWillFocus) {
navBar.handleWillFocus(route);
}
if (this.props.onWillFocus) { if (this.props.onWillFocus) {
this.props.onWillFocus(route); this.props.onWillFocus(route);
} else if (this.props.navigator && this.props.navigator.onWillFocus) { } else if (this.props.navigator && this.props.navigator.onWillFocus) {
@ -532,95 +543,170 @@ var Navigator = React.createClass({
_handleMoveShouldSetPanResponder: function(e, gestureState) { _handleMoveShouldSetPanResponder: function(e, gestureState) {
var currentRoute = this.state.routeStack[this.state.presentedIndex]; var currentRoute = this.state.routeStack[this.state.presentedIndex];
var animationConfig = this.state.sceneConfigStack[this.state.presentedIndex]; var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
if (!animationConfig.enableGestures) { this._expectingGestureGrant = this._matchGestureAction(sceneConfig.gestures, gestureState);
return false; return !! this._expectingGestureGrant;
} },
var currentLoc = animationConfig.isVertical ? gestureState.moveY : gestureState.moveX;
var travelDist = animationConfig.isVertical ? gestureState.dy : gestureState.dx; _doesGestureOverswipe: function(gestureName) {
var oppositeAxisTravelDist = var wouldOverswipeBack = this.state.presentedIndex <= 0 &&
animationConfig.isVertical ? gestureState.dx : gestureState.dy; (gestureName === 'pop' || gestureName === 'jumpBack');
var moveStartedInRegion = currentLoc < animationConfig.edgeHitWidth; var wouldOverswipeForward = this.state.presentedIndex >= this.state.routeStack.length - 1 &&
var moveTravelledFarEnough = gestureName === 'jumpForward';
travelDist >= animationConfig.gestureDetectMovement && return wouldOverswipeForward || wouldOverswipeBack;
travelDist > oppositeAxisTravelDist * animationConfig.directionRatio;
return (
!this.state.isResponderOnlyToBlockTouches &&
moveStartedInRegion &&
!this.state.isAnimating &&
this.state.presentedIndex > 0 &&
moveTravelledFarEnough
);
}, },
_handlePanResponderGrant: function(e, gestureState) { _handlePanResponderGrant: function(e, gestureState) {
invariant(
this._expectingGestureGrant,
'Responder granted unexpectedly.'
);
this._activeGestureAction = this._expectingGestureGrant;
this._expectingGestureGrant = null;
this.state.isResponderOnlyToBlockTouches = this.state.isAnimating; this.state.isResponderOnlyToBlockTouches = this.state.isAnimating;
if (!this.state.isAnimating) { if (!this.state.isAnimating) {
this.state.fromIndex = this.state.presentedIndex; this.state.fromIndex = this.state.presentedIndex;
this.state.toIndex = this.state.presentedIndex - 1; var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction);
this.state.toIndex = this.state.presentedIndex + gestureSceneDelta;
}
},
_deltaForGestureAction: function(gestureAction) {
switch (gestureAction) {
case 'pop':
case 'jumpBack':
return -1;
case 'jumpForward':
return 1;
default:
invariant(false, 'Unsupported gesture action ' + gestureAction);
return;
} }
}, },
_handlePanResponderRelease: function(e, gestureState) { _handlePanResponderRelease: function(e, gestureState) {
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
var releaseGestureAction = this._activeGestureAction;
this._activeGestureAction = null;
if (this.state.isResponderOnlyToBlockTouches) { if (this.state.isResponderOnlyToBlockTouches) {
this.state.isResponderOnlyToBlockTouches = false; this.state.isResponderOnlyToBlockTouches = false;
return; return;
} }
var animationConfig = this.state.sceneConfigStack[this.state.presentedIndex]; var releaseGesture = sceneConfig.gestures[releaseGestureAction];
var velocity = animationConfig.isVertical ? gestureState.vy : gestureState.vx;
// It's not the real location. There is no *real* location - that's the
// point of the pan gesture.
var pseudoLocation = animationConfig.isVertical ?
gestureState.y0 + gestureState.dy :
gestureState.x0 + gestureState.dx;
var still = Math.abs(velocity) < animationConfig.notMoving;
if (this.spring.getCurrentValue() === 0) { if (this.spring.getCurrentValue() === 0) {
// The spring is at zero, so the gesture is already complete
this.spring.setCurrentValue(0).setAtRest(); this.spring.setCurrentValue(0).setAtRest();
this._completeTransition(); this._completeTransition();
return; return;
} }
var transitionVelocity = var isTravelVertical = releaseGesture.direction === 'top-to-bottom' || releaseGesture.direction === 'bottom-to-top';
still && animationConfig.pastPointOfNoReturn(pseudoLocation) ? animationConfig.snapVelocity : var isTravelInverted = releaseGesture.direction === 'right-to-left' || releaseGesture.direction === 'bottom-to-top';
still && !animationConfig.pastPointOfNoReturn(pseudoLocation) ? -animationConfig.snapVelocity : var velocity, gestureDistance;
clamp(-10, velocity, 10); // What are Rebound UoM? if (isTravelVertical) {
velocity = isTravelInverted ? -gestureState.vy : gestureState.vy;
gestureDistance = isTravelInverted ? -gestureState.dy : gestureState.dy;
} else {
velocity = isTravelInverted ? -gestureState.vx : gestureState.vx;
gestureDistance = isTravelInverted ? -gestureState.dx : gestureState.dx;
}
var transitionVelocity = clamp(-10, velocity, 10);
if (Math.abs(velocity) < releaseGesture.notMoving) {
// The gesture velocity is so slow, is "not moving"
var hasGesturedEnoughToComplete = gestureDistance > releaseGesture.fullDistance * releaseGesture.stillCompletionRatio;
transitionVelocity = hasGesturedEnoughToComplete ? releaseGesture.snapVelocity : -releaseGesture.snapVelocity;
}
this.spring.setOvershootClampingEnabled(true); this.spring.setOvershootClampingEnabled(true);
if (transitionVelocity < 0) { if (transitionVelocity < 0 || this._doesGestureOverswipe(releaseGestureAction)) {
this._transitionToFromIndexWithVelocity(transitionVelocity); this._transitionToFromIndexWithVelocity(transitionVelocity);
} else { } else {
this._manuallyPopBackstack(1);
this._transitionToToIndexWithVelocity(transitionVelocity); this._transitionToToIndexWithVelocity(transitionVelocity);
} }
}, },
_handlePanResponderTerminate: function(e, gestureState) { _handlePanResponderTerminate: function(e, gestureState) {
this._activeGestureAction = null;
this.state.isResponderOnlyToBlockTouches = false; this.state.isResponderOnlyToBlockTouches = false;
this._transitionToFromIndexWithVelocity(0); this._transitionToFromIndexWithVelocity(0);
}, },
_handlePanResponderMove: function(e, gestureState) { _handlePanResponderMove: function(e, gestureState) {
if (!this.state.isResponderOnlyToBlockTouches) { if (!this.state.isResponderOnlyToBlockTouches) {
var animationConfig = this.state.sceneConfigStack[this.state.presentedIndex]; var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
var distance = animationConfig.isVertical ? gestureState.dy : gestureState.dx; var gesture = sceneConfig.gestures[this._activeGestureAction];
var gestureDetectMovement = animationConfig.gestureDetectMovement; var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
distance = isTravelInverted ? - distance : distance;
var gestureDetectMovement = gesture.gestureDetectMovement;
var nextProgress = (distance - gestureDetectMovement) / var nextProgress = (distance - gestureDetectMovement) /
(animationConfig.screenDimension - gestureDetectMovement); (gesture.fullDistance - gestureDetectMovement);
if (this._doesGestureOverswipe(this._activeGestureAction)) {
var frictionConstant = gesture.overswipe.frictionConstant;
var frictionByDistance = gesture.overswipe.frictionByDistance;
var frictionRatio = 1 / ((frictionConstant) + (Math.abs(nextProgress) * frictionByDistance));
nextProgress *= frictionRatio;
}
this.spring.setCurrentValue(clamp(0, nextProgress, 1)); this.spring.setCurrentValue(clamp(0, nextProgress, 1));
} }
}, },
_matchGestureAction: function(gestures, gestureState) {
if (!gestures) {
return null;
}
if (this.state.isResponderOnlyToBlockTouches || this.state.isAnimating) {
return null;
}
var matchedGesture = null;
GESTURE_ACTIONS.some((gestureName) => {
var gesture = gestures[gestureName];
if (!gesture) {
return;
}
if (gesture.overswipe == null && this._doesGestureOverswipe(gestureName)) {
// cannot swipe past first or last scene without overswiping
return false;
}
var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
var currentLoc = isTravelVertical ? gestureState.moveY : gestureState.moveX;
var travelDist = isTravelVertical ? gestureState.dy : gestureState.dx;
var oppositeAxisTravelDist =
isTravelVertical ? gestureState.dx : gestureState.dy;
if (isTravelInverted) {
currentLoc = -currentLoc;
travelDist = -travelDist;
oppositeAxisTravelDist = -oppositeAxisTravelDist;
}
var moveStartedInRegion = gesture.edgeHitWidth == null ||
currentLoc < gesture.edgeHitWidth;
var moveTravelledFarEnough =
travelDist >= gesture.gestureDetectMovement &&
travelDist > oppositeAxisTravelDist * gesture.directionRatio;
if (moveStartedInRegion && moveTravelledFarEnough) {
matchedGesture = gestureName;
return true;
}
});
return matchedGesture;
},
_transitionSceneStyle: function(fromIndex, toIndex, progress, index) { _transitionSceneStyle: function(fromIndex, toIndex, progress, index) {
var viewAtIndex = this.refs['scene_' + index]; var viewAtIndex = this.refs['scene_' + index];
if (viewAtIndex === null || viewAtIndex === undefined) { if (viewAtIndex === null || viewAtIndex === undefined) {
return; return;
} }
// Use toIndex animation when we move forwards. Use fromIndex when we move back // Use toIndex animation when we move forwards. Use fromIndex when we move back
var animationIndex = this.state.presentedIndex < toIndex ? toIndex : fromIndex; var sceneConfigIndex = this.state.presentedIndex < toIndex ? toIndex : fromIndex;
var animationConfig = this.state.sceneConfigStack[animationIndex]; var sceneConfig = this.state.sceneConfigStack[sceneConfigIndex];
// this happens for overswiping when there is no scene at toIndex
if (!sceneConfig) {
sceneConfig = this.state.sceneConfigStack[sceneConfigIndex - 1];
}
var styleToUse = {}; var styleToUse = {};
var useFn = index < fromIndex || index < toIndex ? var useFn = index < fromIndex || index < toIndex ?
animationConfig.interpolators.out : sceneConfig.animationInterpolators.out :
animationConfig.interpolators.into; sceneConfig.animationInterpolators.into;
var directionAdjustedProgress = fromIndex < toIndex ? progress : 1 - progress; var directionAdjustedProgress = fromIndex < toIndex ? progress : 1 - progress;
var didChange = useFn(styleToUse, directionAdjustedProgress); var didChange = useFn(styleToUse, directionAdjustedProgress);
if (didChange) { if (didChange) {
@ -631,7 +717,7 @@ var Navigator = React.createClass({
_transitionBetween: function(fromIndex, toIndex, progress) { _transitionBetween: function(fromIndex, toIndex, progress) {
this._transitionSceneStyle(fromIndex, toIndex, progress, fromIndex); this._transitionSceneStyle(fromIndex, toIndex, progress, fromIndex);
this._transitionSceneStyle(fromIndex, toIndex, progress, toIndex); this._transitionSceneStyle(fromIndex, toIndex, progress, toIndex);
var navBar = this.refs[NAVIGATION_BAR_REF]; var navBar = this._navBar;
if (navBar && navBar.updateProgress) { if (navBar && navBar.updateProgress) {
navBar.updateProgress(progress, fromIndex, toIndex); navBar.updateProgress(progress, fromIndex, toIndex);
} }
@ -989,7 +1075,7 @@ var Navigator = React.createClass({
return null; return null;
} }
return React.cloneElement(this.props.navigationBar, { return React.cloneElement(this.props.navigationBar, {
ref: NAVIGATION_BAR_REF, ref: (navBar) => { this._navBar = navBar; },
navigator: this.navigatorActions, navigator: this.navigatorActions,
navState: this.state, navState: this.state,
}); });

View File

@ -30,7 +30,6 @@ var Dimensions = require('Dimensions');
var PixelRatio = require('PixelRatio'); var PixelRatio = require('PixelRatio');
var buildStyleInterpolator = require('buildStyleInterpolator'); var buildStyleInterpolator = require('buildStyleInterpolator');
var merge = require('merge');
var SCREEN_WIDTH = Dimensions.get('window').width; var SCREEN_WIDTH = Dimensions.get('window').width;
var SCREEN_HEIGHT = Dimensions.get('window').height; var SCREEN_HEIGHT = Dimensions.get('window').height;
@ -220,32 +219,12 @@ var FromTheFront = {
}, },
}; };
var BaseOverswipeConfig = {
var Interpolators = { frictionConstant: 1,
Vertical: { frictionByDistance: 1.5,
into: buildStyleInterpolator(FromTheFront),
out: buildStyleInterpolator(ToTheBack),
},
Horizontal: {
into: buildStyleInterpolator(FromTheRight),
out: buildStyleInterpolator(ToTheLeft),
},
}; };
var BaseLeftToRightGesture = {
// These are meant to mimic iOS default behavior
var PastPointOfNoReturn = {
horizontal: function(location) {
return location > SCREEN_WIDTH * 3 / 5;
},
vertical: function(location) {
return location > SCREEN_HEIGHT * 3 / 5;
},
};
var BaseConfig = {
// When false, all gestures are ignored for this scene
enableGestures: true,
// How far the swipe must drag to start transitioning // How far the swipe must drag to start transitioning
gestureDetectMovement: 2, gestureDetectMovement: 2,
@ -253,48 +232,88 @@ var BaseConfig = {
// Amplitude of release velocity that is considered still // Amplitude of release velocity that is considered still
notMoving: 0.3, notMoving: 0.3,
// Velocity to start at when transitioning without gesture
defaultTransitionVelocity: 1.5,
// Fraction of directional move required. // Fraction of directional move required.
directionRatio: 0.66, directionRatio: 0.66,
// Velocity to transition with when the gesture release was "not moving" // Velocity to transition with when the gesture release was "not moving"
snapVelocity: 2, snapVelocity: 2,
// Region that can trigger swipe. iOS default is 30px from the left edge
edgeHitWidth: 30,
// Ratio of gesture completion when non-velocity release will cause action
stillCompletionRatio: 3 / 5,
fullDistance: SCREEN_WIDTH,
direction: 'left-to-right',
};
var BaseRightToLeftGesture = {
...BaseLeftToRightGesture,
direction: 'right-to-left',
};
var BaseConfig = {
// A list of all gestures that are enabled on this scene
gestures: {
pop: BaseLeftToRightGesture,
},
// Rebound spring parameters when transitioning FROM this scene // Rebound spring parameters when transitioning FROM this scene
springFriction: 26, springFriction: 26,
springTension: 200, springTension: 200,
// Defaults for horizontal transitioning: // Velocity to start at when transitioning without gesture
defaultTransitionVelocity: 1.5,
isVertical: false, // Animation interpolators for horizontal transitioning:
screenDimension: SCREEN_WIDTH, animationInterpolators: {
into: buildStyleInterpolator(FromTheRight),
// Region that can trigger swipe. iOS default is 30px from the left edge out: buildStyleInterpolator(ToTheLeft),
edgeHitWidth: 30, },
// Point at which a non-velocity release will cause nav pop
pastPointOfNoReturn: PastPointOfNoReturn.horizontal,
// Animation interpolators for this transition
interpolators: Interpolators.Horizontal,
}; };
var NavigatorSceneConfigs = { var NavigatorSceneConfigs = {
PushFromRight: merge(BaseConfig, { PushFromRight: {
...BaseConfig,
// We will want to customize this soon // We will want to customize this soon
}), },
FloatFromRight: merge(BaseConfig, { FloatFromRight: {
...BaseConfig,
// We will want to customize this soon // We will want to customize this soon
}), },
FloatFromBottom: merge(BaseConfig, { FloatFromBottom: {
edgeHitWidth: 150, ...BaseConfig,
interpolators: Interpolators.Vertical, gestures: {
isVertical: true, pop: {
pastPointOfNoReturn: PastPointOfNoReturn.vertical, ...BaseLeftToRightGesture,
screenDimension: SCREEN_HEIGHT, edgeHitWidth: 150,
}), direction: 'top-to-bottom',
fullDistance: SCREEN_HEIGHT,
}
},
animationInterpolators: {
into: buildStyleInterpolator(FromTheFront),
out: buildStyleInterpolator(ToTheBack),
},
},
HorizontalSwipeJump: {
...BaseConfig,
gestures: {
jumpBack: {
...BaseLeftToRightGesture,
overswipe: BaseOverswipeConfig,
edgeHitWidth: null,
},
jumpForward: {
...BaseRightToLeftGesture,
overswipe: BaseOverswipeConfig,
edgeHitWidth: null,
},
}
}
}; };
module.exports = NavigatorSceneConfigs; module.exports = NavigatorSceneConfigs;