Add gesture handling for the card stack.
Reviewed By: ericvicenti Differential Revision: D2995958 fb-gh-sync-id: f66759440b03072b650a572f011cadd06a0180d2 shipit-source-id: f66759440b03072b650a572f011cadd06a0180d2
This commit is contained in:
Родитель
85801ef874
Коммит
caac520952
|
@ -14,6 +14,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const NavigationExampleRow = require('./NavigationExampleRow');
|
const NavigationExampleRow = require('./NavigationExampleRow');
|
||||||
|
const NavigationRootContainer = require('NavigationRootContainer');
|
||||||
const React = require('react-native');
|
const React = require('react-native');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -25,62 +26,68 @@ const {
|
||||||
const NavigationCardStack = NavigationExperimental.CardStack;
|
const NavigationCardStack = NavigationExperimental.CardStack;
|
||||||
const NavigationStateUtils = NavigationExperimental.StateUtils;
|
const NavigationStateUtils = NavigationExperimental.StateUtils;
|
||||||
|
|
||||||
|
function reduceNavigationState(initialState) {
|
||||||
|
return (currentState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'RootContainerInitialAction':
|
||||||
|
return initialState;
|
||||||
|
|
||||||
|
case 'push':
|
||||||
|
return NavigationStateUtils.push(currentState, {key: action.key});
|
||||||
|
|
||||||
|
case 'back':
|
||||||
|
case 'pop':
|
||||||
|
return currentState.index > 0 ?
|
||||||
|
NavigationStateUtils.pop(currentState) :
|
||||||
|
currentState;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExampleReducer = reduceNavigationState({
|
||||||
|
index: 0,
|
||||||
|
children: [{key: 'First Route'}],
|
||||||
|
});
|
||||||
|
|
||||||
class NavigationCardStackExample extends React.Component {
|
class NavigationCardStackExample extends React.Component {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = this._getInitialState();
|
|
||||||
|
this._renderNavigation = this._renderNavigation.bind(this);
|
||||||
this._renderScene = this._renderScene.bind(this);
|
this._renderScene = this._renderScene.bind(this);
|
||||||
this._push = this._push.bind(this);
|
|
||||||
this._pop = this._pop.bind(this);
|
|
||||||
this._toggleDirection = this._toggleDirection.bind(this);
|
this._toggleDirection = this._toggleDirection.bind(this);
|
||||||
|
|
||||||
|
this.state = {isHorizontal: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
return (
|
||||||
|
<NavigationRootContainer
|
||||||
|
reducer={ExampleReducer}
|
||||||
|
renderNavigation={this._renderNavigation}
|
||||||
|
style={styles.main}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderNavigation(navigationState, onNavigate) {
|
||||||
return (
|
return (
|
||||||
<NavigationCardStack
|
<NavigationCardStack
|
||||||
direction={this.state.isHorizontal ? 'horizontal' : 'vertical'}
|
direction={this.state.isHorizontal ? 'horizontal' : 'vertical'}
|
||||||
navigationState={this.state.navigationState}
|
navigationState={navigationState}
|
||||||
|
onNavigate={onNavigate}
|
||||||
renderScene={this._renderScene}
|
renderScene={this._renderScene}
|
||||||
style={styles.main}
|
style={styles.main}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getInitialState() {
|
|
||||||
const navigationState = {
|
|
||||||
index: 0,
|
|
||||||
children: [{key: 'First Route'}],
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
isHorizontal: true,
|
|
||||||
navigationState,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_push() {
|
|
||||||
const state = this.state.navigationState;
|
|
||||||
const nextState = NavigationStateUtils.push(
|
|
||||||
state,
|
|
||||||
{key: 'Route ' + (state.index + 1)},
|
|
||||||
);
|
|
||||||
this.setState({
|
|
||||||
navigationState: nextState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_pop() {
|
|
||||||
const state = this.state.navigationState;
|
|
||||||
const nextState = state.index > 0 ?
|
|
||||||
NavigationStateUtils.pop(state) :
|
|
||||||
state;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
navigationState: nextState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderScene(props) {
|
_renderScene(props) {
|
||||||
|
const {navigationParentState, onNavigate} = props;
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.scrollView}>
|
<ScrollView style={styles.scrollView}>
|
||||||
<NavigationExampleRow
|
<NavigationExampleRow
|
||||||
|
@ -96,11 +103,20 @@ class NavigationCardStackExample extends React.Component {
|
||||||
/>
|
/>
|
||||||
<NavigationExampleRow
|
<NavigationExampleRow
|
||||||
text="Push Route"
|
text="Push Route"
|
||||||
onPress={this._push}
|
onPress={() => {
|
||||||
|
onNavigate({
|
||||||
|
type: 'push',
|
||||||
|
key: 'Route ' + navigationParentState.children.length,
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<NavigationExampleRow
|
<NavigationExampleRow
|
||||||
text="Pop Route"
|
text="Pop Route"
|
||||||
onPress={this._pop}
|
onPress={() => {
|
||||||
|
onNavigate({
|
||||||
|
type: 'pop',
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<NavigationExampleRow
|
<NavigationExampleRow
|
||||||
text="Exit Card Stack Example"
|
text="Exit Card Stack Example"
|
||||||
|
@ -115,6 +131,12 @@ class NavigationCardStackExample extends React.Component {
|
||||||
isHorizontal: !this.state.isHorizontal,
|
isHorizontal: !this.state.isHorizontal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onNavigate(action) {
|
||||||
|
if (action && action.type === 'back') {
|
||||||
|
this._pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
@ -31,6 +31,7 @@ const Animated = require('Animated');
|
||||||
const NavigationAnimatedView = require('NavigationAnimatedView');
|
const NavigationAnimatedView = require('NavigationAnimatedView');
|
||||||
const NavigationCardStackItem = require('NavigationCardStackItem');
|
const NavigationCardStackItem = require('NavigationCardStackItem');
|
||||||
const NavigationContainer = require('NavigationContainer');
|
const NavigationContainer = require('NavigationContainer');
|
||||||
|
const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
|
||||||
const React = require('React');
|
const React = require('React');
|
||||||
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
||||||
const StyleSheet = require('StyleSheet');
|
const StyleSheet = require('StyleSheet');
|
||||||
|
@ -38,14 +39,13 @@ const StyleSheet = require('StyleSheet');
|
||||||
const emptyFunction = require('emptyFunction');
|
const emptyFunction = require('emptyFunction');
|
||||||
|
|
||||||
const {PropTypes} = React;
|
const {PropTypes} = React;
|
||||||
const {Directions} = NavigationCardStackItem;
|
const {Directions} = NavigationLinearPanResponder;
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
NavigationParentState,
|
NavigationParentState,
|
||||||
} from 'NavigationStateUtils';
|
} from 'NavigationStateUtils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Layout,
|
|
||||||
NavigationStateRenderer,
|
NavigationStateRenderer,
|
||||||
NavigationStateRendererProps,
|
NavigationStateRendererProps,
|
||||||
Position,
|
Position,
|
||||||
|
@ -55,7 +55,7 @@ import type {
|
||||||
type Props = {
|
type Props = {
|
||||||
direction: string,
|
direction: string,
|
||||||
navigationState: NavigationParentState,
|
navigationState: NavigationParentState,
|
||||||
renderOverlay: NavigationStateRenderer,
|
renderOverlay: ?NavigationStateRenderer,
|
||||||
renderScene: NavigationStateRenderer,
|
renderScene: NavigationStateRenderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,6 +98,7 @@ class NavigationCardStack extends React.Component {
|
||||||
layout,
|
layout,
|
||||||
navigationState,
|
navigationState,
|
||||||
position,
|
position,
|
||||||
|
navigationParentState,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -106,6 +107,7 @@ class NavigationCardStack extends React.Component {
|
||||||
index={index}
|
index={index}
|
||||||
key={navigationState.key}
|
key={navigationState.key}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
|
navigationParentState={navigationParentState}
|
||||||
navigationState={navigationState}
|
navigationState={navigationState}
|
||||||
position={position}
|
position={position}
|
||||||
renderScene={this.props.renderScene}
|
renderScene={this.props.renderScene}
|
||||||
|
@ -136,8 +138,6 @@ NavigationCardStack.defaultProps = {
|
||||||
renderOverlay: emptyFunction.thatReturnsNull,
|
renderOverlay: emptyFunction.thatReturnsNull,
|
||||||
};
|
};
|
||||||
|
|
||||||
NavigationCardStack.Directions = Directions;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
animatedView: {
|
animatedView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -29,33 +29,41 @@
|
||||||
|
|
||||||
const Animated = require('Animated');
|
const Animated = require('Animated');
|
||||||
const NavigationContainer = require('NavigationContainer');
|
const NavigationContainer = require('NavigationContainer');
|
||||||
|
const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
|
||||||
const React = require('React');
|
const React = require('React');
|
||||||
|
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
||||||
const StyleSheet = require('StyleSheet');
|
const StyleSheet = require('StyleSheet');
|
||||||
const View = require('View');
|
const View = require('View');
|
||||||
const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
|
||||||
|
|
||||||
const {PropTypes} = React;
|
const {PropTypes} = React;
|
||||||
|
const {Directions} = NavigationLinearPanResponder;
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
NavigationParentState,
|
NavigationParentState,
|
||||||
} from 'NavigationStateUtils';
|
} from 'NavigationStateUtils';
|
||||||
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Layout,
|
Layout,
|
||||||
Position,
|
Position,
|
||||||
NavigationStateRenderer,
|
NavigationStateRenderer,
|
||||||
} from 'NavigationAnimatedView';
|
} from 'NavigationAnimatedView';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Direction,
|
||||||
|
OnNavigateHandler,
|
||||||
|
} from 'NavigationLinearPanResponder';
|
||||||
|
|
||||||
type AnimatedValue = Animated.Value;
|
type AnimatedValue = Animated.Value;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
direction: string,
|
direction: Direction,
|
||||||
index: number;
|
index: number;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
navigationState: NavigationParentState;
|
navigationParentState: NavigationParentState,
|
||||||
position: Position;
|
navigationState: NavigationParentState,
|
||||||
renderScene: NavigationStateRenderer;
|
position: Position,
|
||||||
|
onNavigate: ?OnNavigateHandler,
|
||||||
|
renderScene: NavigationStateRenderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -78,6 +86,39 @@ class AmimatedValueSubscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that provides the required information for the
|
||||||
|
* `NavigationLinearPanResponder`. This class must implement
|
||||||
|
* the interface `NavigationLinearPanResponderDelegate`.
|
||||||
|
*/
|
||||||
|
class PanResponderDelegate {
|
||||||
|
_props : Props;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
this._props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirection(): Direction {
|
||||||
|
return this._props.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIndex(): number {
|
||||||
|
return this._props.navigationParentState.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayout(): Layout {
|
||||||
|
return this._props.layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosition(): Position {
|
||||||
|
return this._props.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
onNavigate(action: {type: string}): void {
|
||||||
|
this._props.onNavigate && this._props.onNavigate(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders the scene as card for the <NavigationCardStack />.
|
* Component that renders the scene as card for the <NavigationCardStack />.
|
||||||
*/
|
*/
|
||||||
|
@ -119,9 +160,8 @@ class NavigationCardStackItem extends React.Component {
|
||||||
const {
|
const {
|
||||||
direction,
|
direction,
|
||||||
index,
|
index,
|
||||||
navigationState,
|
navigationParentState,
|
||||||
position,
|
position,
|
||||||
layout,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
height,
|
height,
|
||||||
|
@ -161,8 +201,16 @@ class NavigationCardStackItem extends React.Component {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let panHandlers = null;
|
||||||
|
if (navigationParentState.index === index) {
|
||||||
|
const delegate = new PanResponderDelegate(this.props);
|
||||||
|
const panResponder = new NavigationLinearPanResponder(delegate);
|
||||||
|
panHandlers = panResponder.panHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
|
{...panHandlers}
|
||||||
style={[styles.main, animatedStyle]}>
|
style={[styles.main, animatedStyle]}>
|
||||||
{this.props.renderScene(this.props)}
|
{this.props.renderScene(this.props)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
@ -200,16 +248,12 @@ class NavigationCardStackItem extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Directions = {
|
|
||||||
HORIZONTAL: 'horizontal',
|
|
||||||
VERTICAL: 'vertical',
|
|
||||||
};
|
|
||||||
|
|
||||||
NavigationCardStackItem.propTypes = {
|
NavigationCardStackItem.propTypes = {
|
||||||
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
|
direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
layout: PropTypes.object.isRequired,
|
layout: PropTypes.object.isRequired,
|
||||||
navigationState: PropTypes.object.isRequired,
|
navigationState: PropTypes.object.isRequired,
|
||||||
|
navigationParentState: PropTypes.object.isRequired,
|
||||||
position: PropTypes.object.isRequired,
|
position: PropTypes.object.isRequired,
|
||||||
renderScene: PropTypes.func.isRequired,
|
renderScene: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -218,10 +262,6 @@ NavigationCardStackItem.defaultProps = {
|
||||||
direction: Directions.HORIZONTAL,
|
direction: Directions.HORIZONTAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
NavigationCardStackItem = NavigationContainer.create(NavigationCardStackItem);
|
|
||||||
|
|
||||||
NavigationCardStackItem.Directions = Directions;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
main: {
|
main: {
|
||||||
backgroundColor: '#E9E9EF',
|
backgroundColor: '#E9E9EF',
|
||||||
|
@ -237,6 +277,4 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports = NavigationContainer.create(NavigationCardStackItem);
|
||||||
|
|
||||||
module.exports = NavigationCardStackItem;
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule NavigationAbstractPanResponder
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const PanResponder = require('PanResponder');
|
||||||
|
|
||||||
|
const invariant = require('invariant');
|
||||||
|
|
||||||
|
const EmptyPanHandlers = {
|
||||||
|
onMoveShouldSetPanResponder: null,
|
||||||
|
onPanResponderGrant: null,
|
||||||
|
onPanResponderMove: null,
|
||||||
|
onPanResponderRelease: null,
|
||||||
|
onPanResponderTerminate: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class that defines the common interface of PanResponder that handles
|
||||||
|
* the gesture actions.
|
||||||
|
*/
|
||||||
|
class NavigationAbstractPanResponder {
|
||||||
|
|
||||||
|
panHandlers: Object;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const config = {};
|
||||||
|
Object.keys(EmptyPanHandlers).forEach(name => {
|
||||||
|
const fn: any = (this: any)[name];
|
||||||
|
|
||||||
|
invariant(
|
||||||
|
typeof fn === 'function',
|
||||||
|
'subclass of `NavigationAbstractPanResponder` must implement method %s',
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
config[name] = fn.bind(this);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.panHandlers = PanResponder.create(config).panHandlers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NavigationAbstractPanResponder;
|
|
@ -87,6 +87,8 @@ type NavigationStateRendererProps = {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
// The state of the the containing navigation view.
|
// The state of the the containing navigation view.
|
||||||
navigationParentState: NavigationParentState,
|
navigationParentState: NavigationParentState,
|
||||||
|
|
||||||
|
onNavigate: (action: any) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type NavigationStateRenderer = (
|
type NavigationStateRenderer = (
|
||||||
|
@ -100,11 +102,12 @@ type TimingSetter = (
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
navigationState: NavigationParentState;
|
navigationState: NavigationParentState,
|
||||||
renderScene: NavigationStateRenderer;
|
onNavigate: (action: any) => void,
|
||||||
renderOverlay: ?NavigationStateRenderer;
|
renderScene: NavigationStateRenderer,
|
||||||
style: any;
|
renderOverlay: ?NavigationStateRenderer,
|
||||||
setTiming: ?TimingSetter;
|
style: any,
|
||||||
|
setTiming: ?TimingSetter,
|
||||||
};
|
};
|
||||||
|
|
||||||
class NavigationAnimatedView extends React.Component {
|
class NavigationAnimatedView extends React.Component {
|
||||||
|
@ -224,18 +227,23 @@ class NavigationAnimatedView extends React.Component {
|
||||||
layout: this._getLayout(),
|
layout: this._getLayout(),
|
||||||
navigationParentState: this.props.navigationState,
|
navigationParentState: this.props.navigationState,
|
||||||
navigationState: scene.state,
|
navigationState: scene.state,
|
||||||
|
onNavigate: this.props.onNavigate,
|
||||||
position: this.state.position,
|
position: this.state.position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_renderOverlay() {
|
_renderOverlay() {
|
||||||
const {renderOverlay} = this.props;
|
const {
|
||||||
|
onNavigate,
|
||||||
|
renderOverlay,
|
||||||
|
navigationState,
|
||||||
|
} = this.props;
|
||||||
if (renderOverlay) {
|
if (renderOverlay) {
|
||||||
const parentState = this.props.navigationState;
|
|
||||||
return renderOverlay({
|
return renderOverlay({
|
||||||
index: parentState.index,
|
index: navigationState.index,
|
||||||
layout: this._getLayout(),
|
layout: this._getLayout(),
|
||||||
navigationParentState: parentState,
|
navigationParentState: navigationState,
|
||||||
navigationState: parentState.children[parentState.index],
|
navigationState: navigationState.children[navigationState.index],
|
||||||
|
onNavigate: onNavigate,
|
||||||
position: this.state.position,
|
position: this.state.position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule NavigationLinearPanResponder
|
||||||
|
* @flow
|
||||||
|
* @typechecks
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Animated = require('Animated');
|
||||||
|
const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder');
|
||||||
|
|
||||||
|
const clamp = require('clamp');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration of the card animation in milliseconds.
|
||||||
|
*/
|
||||||
|
const ANIMATION_DURATION = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold to invoke the `onNavigate` action.
|
||||||
|
* For instance, `1 / 3` means that moving greater than 1 / 3 of the width of
|
||||||
|
* the view will navigate.
|
||||||
|
*/
|
||||||
|
const POSITION_THRESHOLD = 1 / 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold (in pixels) to start the gesture action.
|
||||||
|
*/
|
||||||
|
const RESPOND_THRESHOLD = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The threshold (in speed) to finish the gesture action.
|
||||||
|
*/
|
||||||
|
const VELOCITY_THRESHOLD = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive gesture directions.
|
||||||
|
*/
|
||||||
|
const Directions = {
|
||||||
|
'HORIZONTAL': 'horizontal',
|
||||||
|
'VERTICAL': 'vertical',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive gesture actions.
|
||||||
|
*/
|
||||||
|
const Actions = {
|
||||||
|
// The gesture to navigate backward.
|
||||||
|
// This is done by swiping from the left to the right or from the top to the
|
||||||
|
// bottom.
|
||||||
|
BACK: {type: 'back'},
|
||||||
|
};
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Layout,
|
||||||
|
Position,
|
||||||
|
} from 'NavigationAnimatedView';
|
||||||
|
|
||||||
|
export type OnNavigateHandler = (action: {type: string}) => void;
|
||||||
|
|
||||||
|
export type Direction = $Enum<typeof Directions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type interface of the object that provides the information required by
|
||||||
|
* NavigationLinearPanResponder.
|
||||||
|
*/
|
||||||
|
export type NavigationLinearPanResponderDelegate = {
|
||||||
|
getDirection: () => Direction;
|
||||||
|
getIndex: () => number,
|
||||||
|
getLayout: () => Layout,
|
||||||
|
getPosition: () => Position,
|
||||||
|
onNavigate: OnNavigateHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pan responder that handles the One-dimensional gesture (horizontal or
|
||||||
|
* vertical).
|
||||||
|
*/
|
||||||
|
class NavigationLinearPanResponder extends NavigationAbstractPanResponder {
|
||||||
|
static Actions: Object;
|
||||||
|
static Directions: Object;
|
||||||
|
|
||||||
|
_isResponding: boolean;
|
||||||
|
_startValue: number;
|
||||||
|
_delegate: NavigationLinearPanResponderDelegate;
|
||||||
|
|
||||||
|
constructor(delegate: NavigationLinearPanResponderDelegate) {
|
||||||
|
super();
|
||||||
|
this._isResponding = false;
|
||||||
|
this._startValue = 0;
|
||||||
|
this._delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMoveShouldSetPanResponder(event: any, gesture: any): boolean {
|
||||||
|
const delegate = this._delegate;
|
||||||
|
const layout = delegate.getLayout();
|
||||||
|
const isVertical = delegate.getDirection() === Directions.VERTICAL;
|
||||||
|
const axis = isVertical ? 'dy' : 'dx';
|
||||||
|
const index = delegate.getIndex();
|
||||||
|
const distance = isVertical ?
|
||||||
|
layout.height.__getValue() :
|
||||||
|
layout.width.__getValue();
|
||||||
|
|
||||||
|
return (
|
||||||
|
Math.abs(gesture[axis]) > RESPOND_THRESHOLD &&
|
||||||
|
distance > 0 &&
|
||||||
|
index > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPanResponderGrant(): void {
|
||||||
|
this._isResponding = false;
|
||||||
|
this._delegate.getPosition().stopAnimation((value: number) => {
|
||||||
|
this._isResponding = true;
|
||||||
|
this._startValue = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPanResponderMove(event: any, gesture: any): void {
|
||||||
|
if (!this._isResponding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delegate = this._delegate;
|
||||||
|
const layout = delegate.getLayout();
|
||||||
|
const isVertical = delegate.getDirection() === Directions.VERTICAL;
|
||||||
|
const axis = isVertical ? 'dy' : 'dx';
|
||||||
|
const index = delegate.getIndex();
|
||||||
|
const distance = isVertical ?
|
||||||
|
layout.height.__getValue() :
|
||||||
|
layout.width.__getValue();
|
||||||
|
|
||||||
|
const value = clamp(
|
||||||
|
index - 1,
|
||||||
|
this._startValue - (gesture[axis] / distance),
|
||||||
|
index
|
||||||
|
);
|
||||||
|
|
||||||
|
this._delegate.getPosition().setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPanResponderRelease(event: any, gesture: any): void {
|
||||||
|
if (!this._isResponding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isResponding = false;
|
||||||
|
|
||||||
|
const delegate = this._delegate;
|
||||||
|
const isVertical = delegate.getDirection() === Directions.VERTICAL;
|
||||||
|
const axis = isVertical ? 'dy' : 'dx';
|
||||||
|
const index = delegate.getIndex();
|
||||||
|
const velocity = gesture[axis];
|
||||||
|
|
||||||
|
delegate.getPosition().stopAnimation((value: number) => {
|
||||||
|
this._reset();
|
||||||
|
if (velocity > VELOCITY_THRESHOLD || value <= index - POSITION_THRESHOLD) {
|
||||||
|
delegate.onNavigate(Actions.BACK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPanResponderTerminate(): void {
|
||||||
|
this._isResponding = false;
|
||||||
|
this._reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
_reset(): void {
|
||||||
|
Animated.timing(
|
||||||
|
this._delegate.getPosition(),
|
||||||
|
{
|
||||||
|
toValue: this._delegate.getIndex(),
|
||||||
|
duration: ANIMATION_DURATION,
|
||||||
|
}
|
||||||
|
).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLinearPanResponder.Actions = Actions;
|
||||||
|
NavigationLinearPanResponder.Directions = Directions;
|
||||||
|
|
||||||
|
module.exports = NavigationLinearPanResponder;
|
Загрузка…
Ссылка в новой задаче