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

Commit

Permalink
wip: rewrite based on reanimated
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed May 31, 2019
1 parent 10fe551 commit 883a169
Show file tree
Hide file tree
Showing 40 changed files with 1,683 additions and 4,542 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"react": "16.5.0",
"react-dom": "16.5.0",
"react-native": "~0.57.7",
"react-native-gesture-handler": "^1.1.0",
"react-native-gesture-handler": "^1.2.1",
"react-native-reanimated": "^1.0.1",
"react-native-screens": "^1.0.0-alpha.22",
"react-test-renderer": "16.5.0",
"release-it": "^11.0.0",
Expand All @@ -76,6 +77,7 @@
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0",
"react-native-screens": "^1.0.0 || ^1.0.0-alpha"
},
"jest": {
Expand Down
140 changes: 140 additions & 0 deletions 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({
positions: { current, next },
layout,
}: CardInterpolationProps): CardInterpolatedStyle {
const translateFocused = interpolate(current, {
inputRange: [0, 1],
outputRange: [layout.width, 0],
});
const translateUnfocused = next
? interpolate(next, {
inputRange: [0, 1],
outputRange: [0, multiply(layout.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({
positions: { current },
layout,
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [layout.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({
positions: { current },
layout,
closing,
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [multiply(layout.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({
positions: { current, next },
layout,
}: CardInterpolationProps): CardInterpolatedStyle {
const containerTranslateY = interpolate(current, {
inputRange: [0, 1],
outputRange: [layout.height, 0],
});
const cardTranslateYFocused = interpolate(current, {
inputRange: [0, 1],
outputRange: [multiply(layout.height, 95.9 / 100, -1), 0],
});
const cardTranslateYUnfocused = next
? interpolate(next, {
inputRange: [0, 1],
outputRange: [0, multiply(layout.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 },
};
}
95 changes: 95 additions & 0 deletions src/TransitionConfigs/HeaderStyleInterpolators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Animated from 'react-native-reanimated';
import { HeaderInterpolationProps, HeaderInterpolatedStyle } from '../types';

const { interpolate, add } = Animated;

export function forUIKit({
positions: { current, next },
layouts,
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
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 backTitleOffset = layouts.backTitle
? (layouts.screen.width - layouts.backTitle.width) / 2 - leftSpacing
: undefined;
const titleLeftOffset = layouts.title
? (layouts.screen.width - layouts.title.width) / 2 - leftSpacing
: undefined;

// 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],
}),
},
backTitleStyle: {
// Title and back title are a bit different width due to title being bold
// Adjusting the letterSpacing makes them coincide better
letterSpacing: backTitleOffset
? interpolate(progress, {
inputRange: [0.3, 1, 2],
outputRange: [0.35, 0, 0],
})
: 0,
transform: [
{
// Avoid translating if we don't have its width
// It means there's no back title set
translateX: backTitleOffset
? interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [backTitleOffset, 0, -rightOffset],
})
: 0,
},
],
},
titleStyle: {
opacity: interpolate(progress, {
inputRange: [0.4, 1, 1.5],
outputRange: [0, 1, 0],
}),
transform: [
{
translateX: titleLeftOffset
? interpolate(progress, {
inputRange: [0.5, 1, 2],
outputRange: [rightOffset, 0, -titleLeftOffset],
})
: 0,
},
],
},
};
}

export function forFade({
positions: { 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 },
titleStyle: { opacity },
};
}

export function forNoAnimation(): HeaderInterpolatedStyle {
return {};
}
73 changes: 73 additions & 0 deletions src/TransitionConfigs/TransitionPresets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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',
headerMode: 'float',
transitionSpec: {
open: TransitionIOSSpec,
close: TransitionIOSSpec,
},
cardStyleInterpolator: forHorizontalIOS,
headerStyleInterpolator: forUIKit,
};

// Standard iOS navigation transition for modals
export const ModalSlideFromBottomIOS: TransitionPreset = {
direction: 'vertical',
headerMode: 'screen',
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',
headerMode: 'screen',
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',
headerMode: 'screen',
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 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),
},
};

0 comments on commit 883a169

Please sign in to comment.