Skip to content

Commit

Permalink
Expose a new prop transition for scene renderer.
Browse files Browse the repository at this point in the history
Summary:
= Breaking Change (for experimental features) =

Major API changes in  NavigationAnimatedView

= New prop `transition` for scene renderer

In NavigationAnimatedView, we should not use `position` as a proxy of the
transtion which happens whenever navigation state changes.

Because `position` does not change unless navigation index changes, it won't
be possible to build animations for actions that replace navigation state
without changing the index.

This diff introduces an abstract prop `transition` that is exposed to the scene
renderers.

= Replace `applyAnimation` with `configureTransition`.

Expose a new optional prop  `configureTransition` that allows people to configure
transitions easily.

For instance, to configure the transition, do this:

```
function configureTransition() {
  return {
    dutation: 123,
    easing: Easing.easeInOut,
  };
}

```
<NavigationAnimatedView configureTransition={configureTransition) />

```

Reviewed By: ericvicenti

Differential Revision: D3278698

fbshipit-source-id: 25ebad286d8b41f46c35c0f32d6023ebd01f19e7
  • Loading branch information
Hedger Wang authored and Facebook Github Bot 0 committed May 12, 2016
1 parent 763e9cc commit 55c3086
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 42 deletions.
109 changes: 70 additions & 39 deletions Libraries/NavigationExperimental/NavigationAnimatedView.js
Expand Up @@ -12,6 +12,7 @@
'use strict';

const Animated = require('Animated');
const Easing = require('Easing');
const NavigationPropTypes = require('NavigationPropTypes');
const NavigationScenesReducer = require('NavigationScenesReducer');
const React = require('React');
Expand All @@ -26,6 +27,7 @@ import type {
NavigationParentState,
NavigationScene,
NavigationSceneRenderer,
NavigationTransitionConfigurator,
} from 'NavigationTypeDefinition';

type Props = {
Expand All @@ -34,17 +36,28 @@ type Props = {
onNavigate: NavigationActionCaller,
renderOverlay: ?NavigationSceneRenderer,
renderScene: NavigationSceneRenderer,
configureTransition: NavigationTransitionConfigurator,
style: any,
};

type State = {
layout: NavigationLayout,
position: NavigationAnimatedValue,
scenes: Array<NavigationScene>,
transition: NavigationAnimatedValue,
};

const {PropTypes} = React;

const DefaultTransitionSpec = {
duration: 250,
easing: Easing.inOut(Easing.ease),
};

function isSceneNotStale(scene: NavigationScene): boolean {
return !scene.isStale;
}

function applyDefaultAnimation(
position: NavigationAnimatedValue,
navigationState: NavigationParentState,
Expand All @@ -62,14 +75,14 @@ class NavigationAnimatedView
extends React.Component<any, Props, State> {

_onLayout: (event: any) => void;
_onProgressChange: (data: {value: number}) => void;
_positionListener: any;
_onTransitionEnd: () => void;

props: Props;
state: State;

static propTypes = {
applyAnimation: PropTypes.func,
configureTransition: PropTypes.func,
navigationState: NavigationPropTypes.navigationState.isRequired,
onNavigate: PropTypes.func.isRequired,
renderOverlay: PropTypes.func,
Expand All @@ -78,6 +91,7 @@ class NavigationAnimatedView

static defaultProps = {
applyAnimation: applyDefaultAnimation,
configureTransition: () => DefaultTransitionSpec,
};

constructor(props: Props, context: any) {
Expand All @@ -97,58 +111,64 @@ class NavigationAnimatedView
layout,
position: new Animated.Value(this.props.navigationState.index),
scenes: NavigationScenesReducer([], this.props.navigationState),
transition: new Animated.Value(1),
};
}

componentWillMount(): void {
this._onLayout = this._onLayout.bind(this);
this._onProgressChange = this._onProgressChange.bind(this);
}

componentDidMount(): void {
this._positionListener =
this.state.position.addListener(this._onProgressChange);
this._onTransitionEnd = this._onTransitionEnd.bind(this);
}

componentWillReceiveProps(nextProps: Props): void {
if (nextProps.navigationState !== this.props.navigationState) {
this.setState({
scenes: NavigationScenesReducer(
this.state.scenes,
nextProps.navigationState,
this.props.navigationState
),
});
}
}

componentDidUpdate(lastProps: Props): void {
if (lastProps.navigationState.index !== this.props.navigationState.index) {
this.props.applyAnimation(
this.state.position,
this.props.navigationState,
lastProps.navigationState
);
}
}

componentWillUnmount(): void {
this.state.position.removeListener(this._positionListener);
}
const nextScenes = NavigationScenesReducer(
this.state.scenes,
nextProps.navigationState,
this.props.navigationState
);

_onProgressChange(data: Object): void {
const delta = Math.abs(data.value - this.props.navigationState.index);
if (delta > Number.EPSILON) {
if (nextScenes === this.state.scenes) {
return;
}

const scenes = this.state.scenes.filter(scene => {
return !scene.isStale;
const {
position,
transition,
} = this.state;

// update scenes.
this.setState({
scenes: nextScenes,
});

if (scenes.length !== this.state.scenes.length) {
this.setState({ scenes });
// get the transition spec.
const transtionSpec = nextProps.configureTransition();
transition.setValue(0);

const animations = [
Animated.timing(
transition,
{
...transtionSpec,
toValue: 1,
},
),
];

if (nextProps.navigationState.index !== this.props.navigationState.index) {
animations.push(
Animated.timing(
position,
{
...transtionSpec,
toValue: nextProps.navigationState.index,
},
),
);
}

// play the transition.
Animated.parallel(animations).start(this._onTransitionEnd);
}

render(): ReactElement {
Expand Down Expand Up @@ -180,6 +200,7 @@ class NavigationAnimatedView
const {
position,
scenes,
transition,
} = this.state;

return renderScene({
Expand All @@ -189,6 +210,7 @@ class NavigationAnimatedView
position,
scene,
scenes,
transition,
});
}

Expand All @@ -203,6 +225,7 @@ class NavigationAnimatedView
const {
position,
scenes,
transition,
} = this.state;

return renderOverlay({
Expand All @@ -212,6 +235,7 @@ class NavigationAnimatedView
position,
scene: scenes[navigationState.index],
scenes,
transition,
});
}
return null;
Expand All @@ -232,6 +256,13 @@ class NavigationAnimatedView

this.setState({ layout });
}

_onTransitionEnd(): void {
const scenes = this.state.scenes.filter(isSceneNotStale);
if (scenes.length !== this.state.scenes.length) {
this.setState({ scenes });
}
}
}

const styles = StyleSheet.create({
Expand Down
2 changes: 2 additions & 0 deletions Libraries/NavigationExperimental/NavigationPropTypes.js
Expand Up @@ -72,6 +72,7 @@ const SceneRenderer = {
position: animatedValue.isRequired,
scene: scene.isRequired,
scenes: PropTypes.arrayOf(scene).isRequired,
transition: animatedValue.isRequired,
};

/* NavigationPanPanHandlers */
Expand Down Expand Up @@ -103,6 +104,7 @@ function extractSceneRendererProps(
position: props.position,
scene: props.scene,
scenes: props.scenes,
transition: props.transition,
};
}

Expand Down
19 changes: 16 additions & 3 deletions Libraries/NavigationExperimental/NavigationTypeDefinition.js
Expand Up @@ -41,8 +41,6 @@ export type NavigationLayout = {
width: NavigationAnimatedValue,
};

export type NavigationPosition = NavigationAnimatedValue;

export type NavigationScene = {
index: number,
isStale: boolean,
Expand All @@ -61,13 +59,20 @@ export type NavigationSceneRendererProps = {
onNavigate: NavigationActionCaller,

// The progressive index of the containing view's navigation state.
position: NavigationPosition,
position: NavigationAnimatedValue,

// The scene to render.
scene: NavigationScene,

// All the scenes of the containing view's.
scenes: Array<NavigationScene>,

// The value that represents the progress of the transition when navigation
// state changes from one to another. Its numberic value will range from 0
// to 1.
// transition.__getAnimatedValue() < 1 : transtion is happening.
// transition.__getAnimatedValue() == 1 : transtion completes.
transition: NavigationAnimatedValue,
};

export type NavigationPanPanHandlers = {
Expand All @@ -85,6 +90,12 @@ export type NavigationPanPanHandlers = {
onStartShouldSetResponderCapture: Function,
};

export type NavigationTransitionSpec = {
duration: number,
// An easing function from `Easing`.
easing: () => any,
};

// Functions.

export type NavigationActionCaller = Function;
Expand Down Expand Up @@ -112,3 +123,5 @@ export type NavigationSceneRenderer = (
export type NavigationStyleInterpolator = (
props: NavigationSceneRendererProps,
) => Object;

export type NavigationTransitionConfigurator = () => NavigationTransitionSpec;
Expand Up @@ -72,6 +72,9 @@ function NavigationScenesReducer(
nextState: NavigationParentState,
prevState: ?NavigationParentState,
): Array<NavigationScene> {
if (prevState === nextState) {
return scenes;
}

const prevScenes = new Map();
const freshScenes = new Map();
Expand Down

0 comments on commit 55c3086

Please sign in to comment.