diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
index 94a6735d0..ae08ac75d 100644
--- a/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
@@ -33,35 +33,36 @@ class NavigationCardStackExample extends React.Component {
this._renderScene = this._renderScene.bind(this);
this._push = this._push.bind(this);
this._pop = this._pop.bind(this);
+ this._toggleDirection = this._toggleDirection.bind(this);
}
render() {
return (
);
}
_getInitialState() {
- const route = {key: 'First Route'};
const navigationState = {
index: 0,
- children: [route],
+ children: [{key: 'First Route'}],
};
return {
+ isHorizontal: true,
navigationState,
};
}
_push() {
const state = this.state.navigationState;
- const nextRoute = {key: 'Route ' + (state.index + 1)};
const nextState = NavigationStateUtils.push(
state,
- nextRoute,
+ {key: 'Route ' + (state.index + 1)},
);
this.setState({
navigationState: nextState,
@@ -83,7 +84,15 @@ class NavigationCardStackExample extends React.Component {
return (
+
);
}
+
+ _toggleDirection() {
+ this.setState({
+ isHorizontal: !this.state.isHorizontal,
+ });
+ }
}
const styles = StyleSheet.create({
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
index b4e53a63a..dbb81eff8 100644
--- a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
@@ -27,19 +27,21 @@
*/
'use strict';
+const Animated = require('Animated');
const NavigationAnimatedView = require('NavigationAnimatedView');
-const NavigationCard = require('NavigationCard');
+const NavigationCardStackItem = require('NavigationCardStackItem');
const NavigationContainer = require('NavigationContainer');
const React = require('React');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
const StyleSheet = require('StyleSheet');
const emptyFunction = require('emptyFunction');
const {PropTypes} = React;
+const {Directions} = NavigationCardStackItem;
import type {
NavigationParentState,
- NavigationState,
} from 'NavigationStateUtils';
import type {
@@ -47,9 +49,11 @@ import type {
NavigationStateRenderer,
NavigationStateRendererProps,
Position,
+ TimingSetter,
} from 'NavigationAnimatedView';
type Props = {
+ direction: string,
navigationState: NavigationParentState,
renderOverlay: NavigationStateRenderer,
renderScene: NavigationStateRenderer,
@@ -60,19 +64,30 @@ type Props = {
*/
class NavigationCardStack extends React.Component {
_renderScene : NavigationStateRenderer;
+ _setTiming: TimingSetter;
constructor(props: Props, context: any) {
super(props, context);
this._renderScene = this._renderScene.bind(this);
+ this._setTiming = this._setTiming.bind(this);
+ }
+
+ shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+ return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+ this,
+ nextProps,
+ nextState
+ );
}
render(): ReactElement {
return (
);
}
@@ -81,33 +96,48 @@ class NavigationCardStack extends React.Component {
const {
index,
layout,
- navigationParentState,
navigationState,
position,
} = props;
+
return (
-
- {this.props.renderScene(props)}
-
+ renderScene={this.props.renderScene}
+ />
);
}
+
+ _setTiming(position: Position, navigationState: NavigationParentState): void {
+ Animated.timing(
+ position,
+ {
+ duration: 500,
+ toValue: navigationState.index,
+ }
+ ).start();
+ }
}
NavigationCardStack.propTypes = {
+ direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
navigationState: PropTypes.object.isRequired,
renderOverlay: PropTypes.func,
renderScene: PropTypes.func.isRequired,
};
NavigationCardStack.defaultProps = {
+ direction: Directions.HORIZONTAL,
renderOverlay: emptyFunction.thatReturnsNull,
};
+NavigationCardStack.Directions = Directions;
+
const styles = StyleSheet.create({
animatedView: {
flex: 1,
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js
new file mode 100644
index 000000000..ff9d5f268
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js
@@ -0,0 +1,242 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc. All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software"). Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software"). Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationCardStackItem
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+const NavigationContainer = require('NavigationContainer');
+const React = require('React');
+const StyleSheet = require('StyleSheet');
+const View = require('View');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
+
+const {PropTypes} = React;
+
+import type {
+ NavigationParentState,
+} from 'NavigationStateUtils';
+
+
+import type {
+ Layout,
+ Position,
+ NavigationStateRenderer,
+} from 'NavigationAnimatedView';
+
+type AnimatedValue = Animated.Value;
+
+type Props = {
+ direction: string,
+ index: number;
+ layout: Layout;
+ navigationState: NavigationParentState;
+ position: Position;
+ renderScene: NavigationStateRenderer;
+};
+
+type State = {
+ hash: string,
+ height: number,
+ width: number,
+};
+
+class AmimatedValueSubscription {
+ _value: AnimatedValue;
+ _token: string;
+
+ constructor(value: AnimatedValue, callback: Function) {
+ this._value = value;
+ this._token = value.addListener(callback);
+ }
+
+ remove() {
+ this._value.removeListener(this._token);
+ }
+}
+
+/**
+ * Component that renders the scene as card for the .
+ */
+class NavigationCardStackItem extends React.Component {
+ props: Props;
+ state: State;
+ _calculateState: (t: Layout) => State;
+ _layoutListeners: Array;
+
+ constructor(props: Props, context: any) {
+ super(props, context);
+
+ this._calculateState = this._calculateState.bind(this);
+ this.state = this._calculateState(props.layout);
+ this._layoutListeners = [];
+ }
+
+ shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+ return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+ this,
+ nextProps,
+ nextState
+ );
+ }
+
+ componentDidMount(): void {
+ this._applyLayout(this.props.layout);
+ }
+
+ componentWillUnmount(): void {
+ this._layoutListeners.forEach(subscription => subscription.remove);
+ }
+
+ componentWillReceiveProps(nextProps: Props): void {
+ this._applyLayout(nextProps.layout);
+ }
+
+ render(): ReactElement {
+ const {
+ direction,
+ index,
+ navigationState,
+ position,
+ layout,
+ } = this.props;
+ const {
+ height,
+ width,
+ } = this.state;
+
+ const isVertical = direction === 'vertical';
+ const inputRange = [index - 1, index, index + 1];
+ const animatedStyle = {
+
+ opacity: position.interpolate({
+ inputRange,
+ outputRange: [1, 1, 0.3],
+ }),
+
+ transform: [
+ {
+ scale: position.interpolate({
+ inputRange,
+ outputRange: [1, 1, 0.95],
+ }),
+ },
+ {
+ translateX: isVertical ? 0 :
+ position.interpolate({
+ inputRange,
+ outputRange: [width, 0, -10],
+ }),
+ },
+ {
+ translateY: !isVertical ? 0 :
+ position.interpolate({
+ inputRange,
+ outputRange: [height, 0, -10],
+ }),
+ },
+ ],
+ };
+
+ return (
+
+ {this.props.renderScene(this.props)}
+
+ );
+ }
+
+ _calculateState(layout: Layout): State {
+ const width = layout.width.__getValue();
+ const height = layout.height.__getValue();
+ const hash = 'layout-' + width + '-' + height;
+ const state = {
+ height,
+ width,
+ hash,
+ };
+ return state;
+ }
+
+ _applyLayout(layout: Layout) {
+ this._layoutListeners.forEach(subscription => subscription.remove);
+
+ this._layoutListeners.length = 0;
+
+ const callback = this._applyLayout.bind(this, layout);
+
+ this._layoutListeners.push(
+ new AmimatedValueSubscription(layout.width, callback),
+ new AmimatedValueSubscription(layout.height, callback),
+ );
+
+ const nextState = this._calculateState(layout);
+ if (nextState.hash !== this.state.hash) {
+ this.setState(nextState);
+ }
+ }
+}
+
+const Directions = {
+ HORIZONTAL: 'horizontal',
+ VERTICAL: 'vertical',
+};
+
+NavigationCardStackItem.propTypes = {
+ direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
+ index: PropTypes.number.isRequired,
+ layout: PropTypes.object.isRequired,
+ navigationState: PropTypes.object.isRequired,
+ position: PropTypes.object.isRequired,
+ renderScene: PropTypes.func.isRequired,
+};
+
+NavigationCardStackItem.defaultProps = {
+ direction: Directions.HORIZONTAL,
+};
+
+NavigationCardStackItem = NavigationContainer.create(NavigationCardStackItem);
+
+NavigationCardStackItem.Directions = Directions;
+
+const styles = StyleSheet.create({
+ main: {
+ backgroundColor: '#E9E9EF',
+ bottom: 0,
+ left: 0,
+ position: 'absolute',
+ right: 0,
+ shadowColor: 'black',
+ shadowOffset: {width: 0, height: 0},
+ shadowOpacity: 0.4,
+ shadowRadius: 10,
+ top: 0,
+ },
+});
+
+
+
+module.exports = NavigationCardStackItem;
diff --git a/Libraries/NavigationExperimental/NavigationAnimatedView.js b/Libraries/NavigationExperimental/NavigationAnimatedView.js
index b19502166..bf3024ce8 100644
--- a/Libraries/NavigationExperimental/NavigationAnimatedView.js
+++ b/Libraries/NavigationExperimental/NavigationAnimatedView.js
@@ -115,8 +115,11 @@ class NavigationAnimatedView extends React.Component {
props: Props;
constructor(props) {
super(props);
- this._animatedHeight = new Animated.Value(0);
- this._animatedWidth = new Animated.Value(0);
+ this._lastWidth = 0;
+ this._lastHeight = 0;
+ this._animatedHeight = new Animated.Value(this._lastHeight);
+ this._animatedWidth = new Animated.Value(this._lastWidth);
+
this.state = {
position: new Animated.Value(this.props.navigationState.index),
scenes: new Map(),