288 lines
6.6 KiB
JavaScript
288 lines
6.6 KiB
JavaScript
/**
|
|
* 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. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule NavigationTransitioner
|
|
* @flow
|
|
*/
|
|
'use strict';
|
|
|
|
const Animated = require('Animated');
|
|
const Easing = require('Easing');
|
|
const NavigationPropTypes = require('NavigationPropTypes');
|
|
const NavigationScenesReducer = require('NavigationScenesReducer');
|
|
const React = require('React');
|
|
const StyleSheet = require('StyleSheet');
|
|
const View = require('View');
|
|
|
|
import type {
|
|
NavigationAnimatedValue,
|
|
NavigationLayout,
|
|
NavigationScene,
|
|
NavigationState,
|
|
NavigationTransitionProps,
|
|
NavigationTransitionSpec,
|
|
} from 'NavigationTypeDefinition';
|
|
|
|
type Props = {
|
|
configureTransition: (
|
|
a: NavigationTransitionProps,
|
|
b: ?NavigationTransitionProps,
|
|
) => NavigationTransitionSpec,
|
|
navigationState: NavigationState,
|
|
onTransitionEnd: () => void,
|
|
onTransitionStart: () => void,
|
|
render: (a: NavigationTransitionProps, b: ?NavigationTransitionProps) => any,
|
|
style: any,
|
|
};
|
|
|
|
type State = {
|
|
layout: NavigationLayout,
|
|
position: NavigationAnimatedValue,
|
|
progress: NavigationAnimatedValue,
|
|
scenes: Array<NavigationScene>,
|
|
};
|
|
|
|
const {PropTypes} = React;
|
|
|
|
const DefaultTransitionSpec = {
|
|
duration: 250,
|
|
easing: Easing.inOut(Easing.ease),
|
|
timing: Animated.timing,
|
|
};
|
|
|
|
class NavigationTransitioner extends React.Component<any, Props, State> {
|
|
_onLayout: (event: any) => void;
|
|
_onTransitionEnd: () => void;
|
|
_prevTransitionProps: ?NavigationTransitionProps;
|
|
_transitionProps: NavigationTransitionProps;
|
|
_isMounted: boolean;
|
|
|
|
props: Props;
|
|
state: State;
|
|
|
|
static propTypes = {
|
|
configureTransition: PropTypes.func,
|
|
navigationState: NavigationPropTypes.navigationState.isRequired,
|
|
onTransitionEnd: PropTypes.func,
|
|
onTransitionStart: PropTypes.func,
|
|
render: PropTypes.func.isRequired,
|
|
};
|
|
|
|
constructor(props: Props, context: any) {
|
|
super(props, context);
|
|
|
|
// The initial layout isn't measured. Measured layout will be only available
|
|
// when the component is mounted.
|
|
const layout = {
|
|
height: new Animated.Value(0),
|
|
initHeight: 0,
|
|
initWidth: 0,
|
|
isMeasured: false,
|
|
width: new Animated.Value(0),
|
|
};
|
|
|
|
this.state = {
|
|
layout,
|
|
position: new Animated.Value(this.props.navigationState.index),
|
|
progress: new Animated.Value(1),
|
|
scenes: NavigationScenesReducer([], this.props.navigationState),
|
|
};
|
|
|
|
this._prevTransitionProps = null;
|
|
this._transitionProps = buildTransitionProps(props, this.state);
|
|
this._isMounted = false;
|
|
}
|
|
|
|
componentWillMount(): void {
|
|
this._onLayout = this._onLayout.bind(this);
|
|
this._onTransitionEnd = this._onTransitionEnd.bind(this);
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this._isMounted = true;
|
|
}
|
|
|
|
componentWillUnmount(): void {
|
|
this._isMounted = false;
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps: Props): void {
|
|
const nextScenes = NavigationScenesReducer(
|
|
this.state.scenes,
|
|
nextProps.navigationState,
|
|
this.props.navigationState
|
|
);
|
|
|
|
if (nextScenes === this.state.scenes) {
|
|
return;
|
|
}
|
|
|
|
const nextState = {
|
|
...this.state,
|
|
scenes: nextScenes,
|
|
};
|
|
|
|
const {
|
|
position,
|
|
progress,
|
|
} = nextState;
|
|
|
|
progress.setValue(0);
|
|
|
|
this._prevTransitionProps = this._transitionProps;
|
|
this._transitionProps = buildTransitionProps(nextProps, nextState);
|
|
|
|
// get the transition spec.
|
|
const transitionUserSpec = nextProps.configureTransition ?
|
|
nextProps.configureTransition(
|
|
this._transitionProps,
|
|
this._prevTransitionProps,
|
|
) :
|
|
null;
|
|
|
|
const transitionSpec = {
|
|
...DefaultTransitionSpec,
|
|
...transitionUserSpec,
|
|
};
|
|
|
|
const {timing} = transitionSpec;
|
|
delete transitionSpec.timing;
|
|
|
|
const animations = [
|
|
timing(
|
|
progress,
|
|
{
|
|
...transitionSpec,
|
|
toValue: 1,
|
|
},
|
|
),
|
|
];
|
|
|
|
if (nextProps.navigationState.index !== this.props.navigationState.index) {
|
|
animations.push(
|
|
timing(
|
|
position,
|
|
{
|
|
...transitionSpec,
|
|
toValue: nextProps.navigationState.index,
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// update scenes and play the transition
|
|
this.setState(nextState, () => {
|
|
nextProps.onTransitionStart && nextProps.onTransitionStart(
|
|
this._transitionProps,
|
|
this._prevTransitionProps,
|
|
);
|
|
Animated.parallel(animations).start(this._onTransitionEnd);
|
|
});
|
|
}
|
|
|
|
render(): React.Element<any> {
|
|
return (
|
|
<View
|
|
onLayout={this._onLayout}
|
|
style={[styles.main, this.props.style]}>
|
|
{this.props.render(this._transitionProps, this._prevTransitionProps)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
_onLayout(event: any): void {
|
|
const {height, width} = event.nativeEvent.layout;
|
|
if (this.state.layout.initWidth === width &&
|
|
this.state.layout.initHeight === height) {
|
|
return;
|
|
}
|
|
const layout = {
|
|
...this.state.layout,
|
|
initHeight: height,
|
|
initWidth: width,
|
|
isMeasured: true,
|
|
};
|
|
|
|
layout.height.setValue(height);
|
|
layout.width.setValue(width);
|
|
|
|
const nextState = {
|
|
...this.state,
|
|
layout,
|
|
};
|
|
|
|
this._transitionProps = buildTransitionProps(this.props, nextState);
|
|
this.setState(nextState);
|
|
}
|
|
|
|
_onTransitionEnd(): void {
|
|
if (!this._isMounted) {
|
|
return;
|
|
}
|
|
|
|
const prevTransitionProps = this._prevTransitionProps;
|
|
this._prevTransitionProps = null;
|
|
|
|
const nextState = {
|
|
...this.state,
|
|
scenes: this.state.scenes.filter(isSceneNotStale),
|
|
};
|
|
|
|
this._transitionProps = buildTransitionProps(this.props, nextState);
|
|
|
|
this.setState(nextState, () => {
|
|
this.props.onTransitionEnd && this.props.onTransitionEnd(
|
|
this._transitionProps,
|
|
prevTransitionProps,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
function buildTransitionProps(
|
|
props: Props,
|
|
state: State,
|
|
): NavigationTransitionProps {
|
|
const {
|
|
navigationState,
|
|
} = props;
|
|
|
|
const {
|
|
layout,
|
|
position,
|
|
progress,
|
|
scenes,
|
|
} = state;
|
|
|
|
return {
|
|
layout,
|
|
navigationState,
|
|
position,
|
|
progress,
|
|
scenes,
|
|
// $FlowFixMe(>=0.32.0) - find can return undefined
|
|
scene: scenes.find(isSceneActive),
|
|
};
|
|
}
|
|
|
|
function isSceneNotStale(scene: NavigationScene): boolean {
|
|
return !scene.isStale;
|
|
}
|
|
|
|
function isSceneActive(scene: NavigationScene): boolean {
|
|
return scene.isActive;
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
main: {
|
|
flex: 1,
|
|
},
|
|
});
|
|
|
|
module.exports = NavigationTransitioner;
|