From 236e4b7f2dbbe29afc88de5f9f7da515148a6431 Mon Sep 17 00:00:00 2001 From: 94cstyles Date: Thu, 26 Oct 2017 15:25:59 -0500 Subject: [PATCH] Fix stack navigator animations (#1493) (#2520) * Fix animation (#1493) * Rewrite the file animatedInterpolate.js. To increase readability. * Rename the variable, To increase readability. * minor renaming --- .../react-navigation/src/TypeDefinition.js | 5 ++ ...tSceneIndicesForInterpolationInputRange.js | 56 ++++++++++++ .../CardStack/CardStackStyleInterpolator.js | 85 ++++++++++++------- .../views/Header/HeaderStyleInterpolator.js | 49 ++++++++--- 4 files changed, 153 insertions(+), 42 deletions(-) create mode 100644 packages/react-navigation/src/utils/getSceneIndicesForInterpolationInputRange.js diff --git a/packages/react-navigation/src/TypeDefinition.js b/packages/react-navigation/src/TypeDefinition.js index 320c54f2..fc829b95 100644 --- a/packages/react-navigation/src/TypeDefinition.js +++ b/packages/react-navigation/src/TypeDefinition.js @@ -493,3 +493,8 @@ export type LayoutEvent = { }, }, }; + +export type SceneIndicesForInterpolationInputRange = { + first: number, + last: number, +}; diff --git a/packages/react-navigation/src/utils/getSceneIndicesForInterpolationInputRange.js b/packages/react-navigation/src/utils/getSceneIndicesForInterpolationInputRange.js new file mode 100644 index 00000000..eb277aac --- /dev/null +++ b/packages/react-navigation/src/utils/getSceneIndicesForInterpolationInputRange.js @@ -0,0 +1,56 @@ +/* @flow */ + +import type { + NavigationSceneRendererProps, + NavigationScene, + SceneIndicesForInterpolationInputRange, +} from '../TypeDefinition'; + +function getSceneIndicesForInterpolationInputRange( + props: NavigationSceneRendererProps +): SceneIndicesForInterpolationInputRange | null { + const { scene, scenes } = props; + const index = scene.index; + const lastSceneIndexInScenes = scenes.length - 1; + const isBack = !scenes[lastSceneIndexInScenes].isActive; + + if (isBack) { + const currentSceneIndexInScenes = scenes.findIndex( + (item: NavigationScene) => item === scene + ); + const targetSceneIndexInScenes = scenes.findIndex( + (item: NavigationScene) => item.isActive + ); + const targetSceneIndex = scenes[targetSceneIndexInScenes].index; + const lastSceneIndex = scenes[lastSceneIndexInScenes].index; + + if ( + index !== targetSceneIndex && + currentSceneIndexInScenes === lastSceneIndexInScenes + ) { + return { + first: Math.min(targetSceneIndex, index - 1), + last: index + 1, + }; + } else if ( + index === targetSceneIndex && + currentSceneIndexInScenes === targetSceneIndexInScenes + ) { + return { + first: index - 1, + last: Math.max(lastSceneIndex, index + 1), + }; + } else if ( + index === targetSceneIndex || + currentSceneIndexInScenes > targetSceneIndexInScenes + ) { + return null; + } else { + return { first: index - 1, last: index + 1 }; + } + } else { + return { first: index - 1, last: index + 1 }; + } +} + +export default getSceneIndicesForInterpolationInputRange; diff --git a/packages/react-navigation/src/views/CardStack/CardStackStyleInterpolator.js b/packages/react-navigation/src/views/CardStack/CardStackStyleInterpolator.js index 1c01057c..79e79cd2 100644 --- a/packages/react-navigation/src/views/CardStack/CardStackStyleInterpolator.js +++ b/packages/react-navigation/src/views/CardStack/CardStackStyleInterpolator.js @@ -7,6 +7,8 @@ import type { AnimatedViewStyleProp, } from '../../TypeDefinition'; +import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange'; + /** * Utility that builds the style for the card in the cards stack. * @@ -51,33 +53,25 @@ function forHorizontal( if (!layout.isMeasured) { return forInitial(props); } + const interpolate = getSceneIndicesForInterpolationInputRange(props); + if (!interpolate) return { opacity: 0 }; + + const { first, last } = interpolate; const index = scene.index; - const inputRange = [index - 1, index, index + 1]; - - const width = layout.initWidth; - const outputRange = I18nManager.isRTL - ? ([-width, 0, width * 0.3]: Array) - : ([width, 0, width * -0.3]: Array); - - // Add [index - 1, index - 0.99] to the interpolated opacity for screen transition. - // This makes the screen's shadow to disappear smoothly. const opacity = position.interpolate({ - inputRange: ([ - index - 1, - index - 0.99, - index, - index + 0.99, - index + 1, - ]: Array), + inputRange: [first, first + 0.01, index, last - 0.01, last], outputRange: ([0, 1, 1, 0.85, 0]: Array), }); - const translateY = 0; + const width = layout.initWidth; const translateX = position.interpolate({ - inputRange, - outputRange, + inputRange: ([first, index, last]: Array), + outputRange: I18nManager.isRTL + ? ([-width, 0, width * 0.3]: Array) + : ([width, 0, width * -0.3]: Array), }); + const translateY = 0; return { opacity, @@ -96,26 +90,23 @@ function forVertical( if (!layout.isMeasured) { return forInitial(props); } + const interpolate = getSceneIndicesForInterpolationInputRange(props); + if (!interpolate) return { opacity: 0 }; + + const { first, last } = interpolate; const index = scene.index; - const height = layout.initHeight; - const opacity = position.interpolate({ - inputRange: ([ - index - 1, - index - 0.99, - index, - index + 0.99, - index + 1, - ]: Array), + inputRange: [first, first + 0.01, index, last - 0.01, last], outputRange: ([0, 1, 1, 0.85, 0]: Array), }); - const translateX = 0; + const height = layout.initHeight; const translateY = position.interpolate({ - inputRange: ([index - 1, index, index + 1]: Array), + inputRange: ([first, index, last]: Array), outputRange: ([height, 0, 0]: Array), }); + const translateX = 0; return { opacity, @@ -134,20 +125,24 @@ function forFadeFromBottomAndroid( if (!layout.isMeasured) { return forInitial(props); } + const interpolate = getSceneIndicesForInterpolationInputRange(props); + if (!interpolate) return { opacity: 0 }; + + const { first, last } = interpolate; const index = scene.index; - const inputRange = [index - 1, index, index + 0.99, index + 1]; + const inputRange = ([first, index, last - 0.01, last]: Array); const opacity = position.interpolate({ inputRange, outputRange: ([0, 1, 1, 0]: Array), }); - const translateX = 0; const translateY = position.interpolate({ inputRange, outputRange: ([50, 0, 0, 0]: Array), }); + const translateX = 0; return { opacity, @@ -155,6 +150,31 @@ function forFadeFromBottomAndroid( }; } +/** + * fadeIn and fadeOut + */ +function forFade(props: NavigationSceneRendererProps): AnimatedViewStyleProp { + const { layout, position, scene } = props; + + if (!layout.isMeasured) { + return forInitial(props); + } + const interpolate = getSceneIndicesForInterpolationInputRange(props); + + if (!interpolate) return { opacity: 0 }; + + const { first, last } = interpolate; + const index = scene.index; + const opacity = position.interpolate({ + inputRange: ([first, index, last]: Array), + outputRange: ([0, 1, 1]: Array), + }); + + return { + opacity, + }; +} + function canUseNativeDriver(): boolean { // The native driver can be enabled for this interpolator animating // opacity, translateX, and translateY is supported by the native animation @@ -166,5 +186,6 @@ export default { forHorizontal, forVertical, forFadeFromBottomAndroid, + forFade, canUseNativeDriver, }; diff --git a/packages/react-navigation/src/views/Header/HeaderStyleInterpolator.js b/packages/react-navigation/src/views/Header/HeaderStyleInterpolator.js index ba1c36ed..7647bb85 100644 --- a/packages/react-navigation/src/views/Header/HeaderStyleInterpolator.js +++ b/packages/react-navigation/src/views/Header/HeaderStyleInterpolator.js @@ -4,9 +4,12 @@ import { I18nManager } from 'react-native'; import type { NavigationSceneRendererProps, + NavigationScene, AnimatedViewStyleProp, } from '../../TypeDefinition'; +import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange'; + /** * Utility that builds the style for the navigation header. * @@ -19,11 +22,25 @@ import type { */ function forLeft(props: NavigationSceneRendererProps): AnimatedViewStyleProp { - const { position, scene } = props; - const { index } = scene; + const { position, scene, scenes } = props; + const interpolate = getSceneIndicesForInterpolationInputRange(props); + + if (!interpolate) return { opacity: 0 }; + + const activeScene = scenes.find((item: NavigationScene) => item.isActive); + const activeIndex = scenes.findIndex( + (item: NavigationScene) => item === activeScene + ); + const currentIndex = scenes.findIndex( + (item: NavigationScene) => item === scene + ); + const deviation = Math.abs((activeIndex - currentIndex) / 2); + const { first, last } = interpolate; + const index = scene.index; + return { opacity: position.interpolate({ - inputRange: [index - 1, index - 0.5, index, index + 0.5, index + 1], + inputRange: [first, first + deviation, index, last - deviation, last], outputRange: ([0, 0, 1, 0, 0]: Array), }), }; @@ -31,19 +48,26 @@ function forLeft(props: NavigationSceneRendererProps): AnimatedViewStyleProp { function forCenter(props: NavigationSceneRendererProps): AnimatedViewStyleProp { const { position, scene } = props; - const { index } = scene; + const interpolate = getSceneIndicesForInterpolationInputRange(props); + + if (!interpolate) return { opacity: 0 }; + + const { first, last } = interpolate; + const index = scene.index; + const inputRange = [first, index, last]; + return { opacity: position.interpolate({ - inputRange: [index - 1, index, index + 1], + inputRange, outputRange: ([0, 1, 0]: Array), }), transform: [ { translateX: position.interpolate({ - inputRange: [index - 1, index + 1], + inputRange, outputRange: I18nManager.isRTL - ? ([-200, 200]: Array) - : ([200, -200]: Array), + ? ([-200, 0, 200]: Array) + : ([200, 0, -200]: Array), }), }, ], @@ -52,10 +76,15 @@ function forCenter(props: NavigationSceneRendererProps): AnimatedViewStyleProp { function forRight(props: NavigationSceneRendererProps): AnimatedViewStyleProp { const { position, scene } = props; - const { index } = scene; + const interpolate = getSceneIndicesForInterpolationInputRange(props); + + if (!interpolate) return { opacity: 0 }; + const { first, last } = interpolate; + const index = scene.index; + return { opacity: position.interpolate({ - inputRange: [index - 1, index, index + 1], + inputRange: [first, index, last], outputRange: ([0, 1, 0]: Array), }), };