From e9571871262e3fbb69ae79b5eb1fa4065d491c4e Mon Sep 17 00:00:00 2001 From: Neo Date: Sun, 14 May 2017 23:40:02 +0800 Subject: [PATCH] stop using Animated.spring (#1500) * stop using Animated.spring * use layout instead of window.size --- .../react-navigation/src/views/CardStack.js | 73 ++++++++++--------- .../src/views/TransitionConfigs.js | 6 +- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/packages/react-navigation/src/views/CardStack.js b/packages/react-navigation/src/views/CardStack.js index f35f883b..5ec9cef6 100644 --- a/packages/react-navigation/src/views/CardStack.js +++ b/packages/react-navigation/src/views/CardStack.js @@ -10,6 +10,7 @@ import { Platform, View, I18nManager, + Easing, } from 'react-native'; import Card from './Card'; @@ -63,9 +64,11 @@ type Props = { }; /** - * The duration of the card animation in milliseconds. + * The max duration of the card animation in milliseconds after released gesture. + * The actual duration should be always less then that because the rest distance + * is always less then the full distance of the layout. */ -const ANIMATION_DURATION = 200; +const ANIMATION_DURATION = 250; /** * The gesture distance threshold to trigger the back behavior. For instance, @@ -94,14 +97,6 @@ const animatedSubscribeValue = (animatedValue: Animated.Value) => { } }; -/** - * The ratio between the gesture velocity and the animation velocity. This allows - * the velocity of a swipe release to carry on into the new animation. - * - * TODO: Understand and compute this ratio rather than using an approximation - */ -const GESTURE_ANIMATED_VELOCITY_RATIO = -4; - class CardStack extends Component { /** * Used to identify the starting point of the position when the gesture starts, such that it can @@ -199,17 +194,16 @@ class CardStack extends Component { animatedSubscribeValue(props.position); } - _reset(resetToIndex: number, velocity: number): void { - Animated.spring(this.props.position, { + _reset(resetToIndex: number, duration: number): void { + Animated.timing(this.props.position, { toValue: resetToIndex, - duration: ANIMATION_DURATION, + duration, + easing: Easing.inOut(Easing.ease), useNativeDriver: this.props.position.__isNative, - velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO, - bounciness: 0, }).start(); } - _goBack(backFromIndex: number, velocity: number) { + _goBack(backFromIndex: number, duration: number) { const { navigation, position, scenes } = this.props; const toValue = Math.max(backFromIndex - 1, 0); @@ -217,12 +211,11 @@ class CardStack extends Component { // dispatched at the end of the transition. this._immediateIndex = toValue; - Animated.spring(position, { + Animated.timing(position, { toValue, - duration: ANIMATION_DURATION, + duration, + easing: Easing.inOut(Easing.ease), useNativeDriver: position.__isNative, - velocity: velocity * GESTURE_ANIMATED_VELOCITY_RATIO, - bounciness: 0, }).start(() => { this._immediateIndex = null; const backFromScene = scenes.find((s: *) => s.index === toValue + 1); @@ -240,8 +233,10 @@ class CardStack extends Component { if (headerMode === 'float') { floatingHeader = this._renderHeader(this.props.scene, headerMode); } - const { navigation, position, scene, mode, scenes } = this.props; + const { navigation, position, layout, scene, scenes, mode } = this.props; const { index } = navigation.state; + const isVertical = mode === 'modal'; + const responder = PanResponder.create({ onPanResponderTerminate: () => { this._isResponding = false; @@ -257,11 +252,9 @@ class CardStack extends Component { event: { nativeEvent: { pageY: number, pageX: number } }, gesture: any, ) => { - const layout = this.props.layout; if (index !== scene.index) { return false; } - const isVertical = mode === 'modal'; const immediateIndex = this._immediateIndex == null ? index : this._immediateIndex; @@ -297,16 +290,14 @@ class CardStack extends Component { }, onPanResponderMove: (event: any, gesture: any) => { // Handle the moving touches for our granted responder - const layout = this.props.layout; - const isVertical = mode === 'modal'; const startValue = this._gestureStartValue; const axis = isVertical ? 'dy' : 'dx'; - const distance = isVertical + const axisDistance = isVertical ? layout.height.__getValue() : layout.width.__getValue(); const currentValue = I18nManager.isRTL && axis === 'dx' - ? startValue + gesture[axis] / distance - : startValue - gesture[axis] / distance; + ? startValue + gesture[axis] / axisDistance + : startValue - gesture[axis] / axisDistance; const value = clamp(index - 1, currentValue, index); position.setValue(value); }, @@ -319,31 +310,41 @@ class CardStack extends Component { return; } this._isResponding = false; - const isVertical = mode === 'modal'; - const velocity = gesture[isVertical ? 'vy' : 'vx']; + const immediateIndex = this._immediateIndex == null ? index : this._immediateIndex; + // Calculate animate duration according to gesture speed and moved distance + const axisDistance = isVertical + ? layout.height.__getValue() + : layout.width.__getValue(); + const movedDistance = gesture[isVertical ? 'moveY' : 'moveX']; + const defaultVelocity = axisDistance / ANIMATION_DURATION; + const gestureVelocity = gesture[isVertical ? 'vy' : 'vx']; + const velocity = Math.max(gestureVelocity, defaultVelocity); + const resetDuration = movedDistance / velocity; + const goBackDuration = (axisDistance - movedDistance) / velocity; + // To asyncronously get the current animated value, we need to run stopAnimation: position.stopAnimation((value: number) => { // If the speed of the gesture release is significant, use that as the indication // of intent - if (velocity < -0.5) { - this._reset(immediateIndex, velocity); + if (gestureVelocity < -0.5) { + this._reset(immediateIndex, resetDuration); return; } - if (velocity > 0.5) { - this._goBack(immediateIndex, velocity); + if (gestureVelocity > 0.5) { + this._goBack(immediateIndex, goBackDuration); return; } // Then filter based on the distance the screen was moved. Over a third of the way swiped, // and the back will happen. if (value <= index - POSITION_THRESHOLD) { - this._goBack(immediateIndex, velocity); + this._goBack(immediateIndex, goBackDuration); } else { - this._reset(immediateIndex, velocity); + this._reset(immediateIndex, resetDuration); } }); }, diff --git a/packages/react-navigation/src/views/TransitionConfigs.js b/packages/react-navigation/src/views/TransitionConfigs.js index eb30845c..44b8ec1b 100644 --- a/packages/react-navigation/src/views/TransitionConfigs.js +++ b/packages/react-navigation/src/views/TransitionConfigs.js @@ -12,11 +12,9 @@ import CardStackStyleInterpolator from './CardStackStyleInterpolator'; // Used for all animations unless overriden const DefaultTransitionSpec = ({ - // The following options are meant to mimic the nav animations of iOS 10 duration: 250, - timing: Animated.spring, - bounciness: 0, - speed: 9, + easing: Easing.inOut(Easing.ease), + timing: Animated.timing, }: NavigationTransitionSpec); // Standard iOS navigation transition