Skip to content
This repository has been archived by the owner on Feb 8, 2020. It is now read-only.

Commit

Permalink
feat: new implementation with reanimated
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Aug 18, 2019
1 parent 049e7ed commit 9b176e9
Show file tree
Hide file tree
Showing 33 changed files with 2,592 additions and 0 deletions.
140 changes: 140 additions & 0 deletions packages/stack/src/TransitionConfigs/CardStyleInterpolators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import Animated from 'react-native-reanimated';
import { CardInterpolationProps, CardInterpolatedStyle } from '../types';

const { cond, multiply, interpolate } = Animated;

/**
* Standard iOS-style slide in from the right.
*/
export function forHorizontalIOS({
progress: { current, next },
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const translateFocused = interpolate(current, {
inputRange: [0, 1],
outputRange: [screen.width, 0],
});
const translateUnfocused = next
? interpolate(next, {
inputRange: [0, 1],
outputRange: [0, multiply(screen.width, -0.3)],
})
: 0;

const opacity = interpolate(current, {
inputRange: [0, 1],
outputRange: [0, 0.07],
});

const shadowOpacity = interpolate(current, {
inputRange: [0, 1],
outputRange: [0, 0.3],
});

return {
cardStyle: {
backgroundColor: '#eee',
transform: [
// Translation for the animation of the current card
{ translateX: translateFocused },
// Translation for the animation of the card on top of this
{ translateX: translateUnfocused },
],
shadowOpacity,
},
overlayStyle: { opacity },
};
}

/**
* Standard iOS-style slide in from the bottom (used for modals).
*/
export function forVerticalIOS({
progress: { current },
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [screen.height, 0],
});

return {
cardStyle: {
backgroundColor: '#eee',
transform: [
// Translation for the animation of the current card
{ translateY },
],
},
};
}

/**
* Standard Android-style fade in from the bottom for Android Oreo.
*/
export function forFadeFromBottomAndroid({
progress: { current },
layouts: { screen },
closing,
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [multiply(screen.height, 0.08), 0],
});

const opacity = cond(
closing,
current,
interpolate(current, {
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
})
);

return {
cardStyle: {
opacity,
transform: [{ translateY }],
},
};
}

/**
* Standard Android-style wipe from the bottom for Android Pie.
*/
export function forWipeFromBottomAndroid({
progress: { current, next },
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const containerTranslateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [screen.height, 0],
});
const cardTranslateYFocused = interpolate(current, {
inputRange: [0, 1],
outputRange: [multiply(screen.height, 95.9 / 100, -1), 0],
});
const cardTranslateYUnfocused = next
? interpolate(next, {
inputRange: [0, 1],
outputRange: [0, multiply(screen.height, 2 / 100, -1)],
})
: 0;
const overlayOpacity = interpolate(current, {
inputRange: [0, 0.36, 1],
outputRange: [0, 0.1, 0.1],
});

return {
containerStyle: {
transform: [{ translateY: containerTranslateY }],
},
cardStyle: {
transform: [
{ translateY: cardTranslateYFocused },
{ translateY: cardTranslateYUnfocused },
],
},
overlayStyle: { opacity: overlayOpacity },
};
}
100 changes: 100 additions & 0 deletions packages/stack/src/TransitionConfigs/HeaderStyleInterpolators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Animated from 'react-native-reanimated';
import { HeaderInterpolationProps, HeaderInterpolatedStyle } from '../types';

const { interpolate, add } = Animated;

export function forUIKit({
progress: { current, next },
layouts,
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
const defaultOffset = 100;
const leftSpacing = 27;

// The title and back button title should cross-fade to each other
// When screen is fully open, the title should be in center, and back title should be on left
// When screen is closing, the previous title will animate to back title's position
// And back title will animate to title's position
// We achieve this by calculating the offsets needed to translate title to back title's position and vice-versa
const leftLabelOffset = layouts.leftLabel
? (layouts.screen.width - layouts.leftLabel.width) / 2 - leftSpacing
: defaultOffset;
const titleLeftOffset = layouts.title
? (layouts.screen.width - layouts.title.width) / 2 - leftSpacing
: defaultOffset;

// When the current title is animating to right, it is centered in the right half of screen in middle of transition
// The back title also animates in from this position
const rightOffset = layouts.screen.width / 4;

const progress = add(current, next ? next : 0);

return {
leftButtonStyle: {
opacity: interpolate(progress, {
inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0],
}),
},
leftLabelStyle: {
transform: [
{
translateX: interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [leftLabelOffset, 0, -rightOffset],
}),
},
],
},
rightButtonStyle: {
opacity: interpolate(progress, {
inputRange: [0.3, 1, 1.5],
outputRange: [0, 1, 0],
}),
},
titleStyle: {
opacity: interpolate(progress, {
inputRange: [0, 0.4, 1, 1.5],
outputRange: [0, 0.1, 1, 0],
}),
transform: [
{
translateX: interpolate(progress, {
inputRange: [0.5, 1, 2],
outputRange: [rightOffset, 0, -titleLeftOffset],
}),
},
],
},
backgroundStyle: {
transform: [
{
translateX: interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [layouts.screen.width, 0, -layouts.screen.width],
}),
},
],
},
};
}

export function forFade({
progress: { current, next },
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
const progress = add(current, next ? next : 0);
const opacity = interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [0, 1, 0],
});

return {
leftButtonStyle: { opacity },
rightButtonStyle: { opacity },
titleStyle: { opacity },
backgroundStyle: { opacity },
};
}

export function forNoAnimation(): HeaderInterpolatedStyle {
return {};
}
69 changes: 69 additions & 0 deletions packages/stack/src/TransitionConfigs/TransitionPresets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
forHorizontalIOS,
forVerticalIOS,
forWipeFromBottomAndroid,
forFadeFromBottomAndroid,
} from './CardStyleInterpolators';
import { forUIKit, forNoAnimation } from './HeaderStyleInterpolators';
import {
TransitionIOSSpec,
WipeFromBottomAndroidSpec,
FadeOutToBottomAndroidSpec,
FadeInFromBottomAndroidSpec,
} from './TransitionSpecs';
import { TransitionPreset } from '../types';
import { Platform } from 'react-native';

const ANDROID_VERSION_PIE = 28;

// Standard iOS navigation transition
export const SlideFromRightIOS: TransitionPreset = {
direction: 'horizontal',
transitionSpec: {
open: TransitionIOSSpec,
close: TransitionIOSSpec,
},
cardStyleInterpolator: forHorizontalIOS,
headerStyleInterpolator: forUIKit,
};

// Standard iOS navigation transition for modals
export const ModalSlideFromBottomIOS: TransitionPreset = {
direction: 'vertical',
transitionSpec: {
open: TransitionIOSSpec,
close: TransitionIOSSpec,
},
cardStyleInterpolator: forVerticalIOS,
headerStyleInterpolator: forNoAnimation,
};

// Standard Android navigation transition when opening or closing an Activity on Android < 9
export const FadeFromBottomAndroid: TransitionPreset = {
direction: 'vertical',
transitionSpec: {
open: FadeInFromBottomAndroidSpec,
close: FadeOutToBottomAndroidSpec,
},
cardStyleInterpolator: forFadeFromBottomAndroid,
headerStyleInterpolator: forNoAnimation,
};

// Standard Android navigation transition when opening or closing an Activity on Android >= 9
export const WipeFromBottomAndroid: TransitionPreset = {
direction: 'vertical',
transitionSpec: {
open: WipeFromBottomAndroidSpec,
close: WipeFromBottomAndroidSpec,
},
cardStyleInterpolator: forWipeFromBottomAndroid,
headerStyleInterpolator: forNoAnimation,
};

export const DefaultTransition = Platform.select({
ios: SlideFromRightIOS,
default:
Platform.OS === 'android' && Platform.Version < ANDROID_VERSION_PIE
? FadeFromBottomAndroid
: WipeFromBottomAndroid,
});
43 changes: 43 additions & 0 deletions packages/stack/src/TransitionConfigs/TransitionSpecs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Easing } from 'react-native-reanimated';
import { TransitionSpec } from '../types';

export const TransitionIOSSpec: TransitionSpec = {
timing: 'spring',
config: {
stiffness: 1000,
damping: 500,
mass: 3,
overshootClamping: true,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
},
};

// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
export const FadeInFromBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
config: {
duration: 350,
easing: Easing.out(Easing.poly(5)),
},
};

// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
export const FadeOutToBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
config: {
duration: 150,
easing: Easing.in(Easing.linear),
},
};

// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
export const WipeFromBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
config: {
duration: 425,
// This is super rough approximation of the path used for the curve by android
// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/interpolator/fast_out_extra_slow_in.xml
easing: Easing.bezier(0.35, 0.45, 0, 1),
},
};
33 changes: 33 additions & 0 deletions packages/stack/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as CardStyleInterpolators from './TransitionConfigs/CardStyleInterpolators';
import * as HeaderStyleInterpolators from './TransitionConfigs/HeaderStyleInterpolators';
import * as TransitionPresets from './TransitionConfigs/TransitionPresets';

/**
* Navigators
*/
export {
default as createStackNavigator,
} from './navigators/createStackNavigator';

export const Assets = [
require('./views/assets/back-icon.png'),
require('./views/assets/back-icon-mask.png'),
];

/**
* Views
*/
export { default as Header } from './views/Header/Header';
export { default as HeaderTitle } from './views/Header/HeaderTitle';
export { default as HeaderBackButton } from './views/Header/HeaderBackButton';

/**
* Transition presets
*/
export { CardStyleInterpolators, HeaderStyleInterpolators, TransitionPresets };

/**
* Utilities
*/

export { default as StackGestureContext } from './utils/StackGestureContext';
Loading

0 comments on commit 9b176e9

Please sign in to comment.