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

Commit 883a169

Browse files
committed
wip: rewrite based on reanimated
1 parent 10fe551 commit 883a169

40 files changed

+1683
-4542
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
"react": "16.5.0",
6464
"react-dom": "16.5.0",
6565
"react-native": "~0.57.7",
66-
"react-native-gesture-handler": "^1.1.0",
66+
"react-native-gesture-handler": "^1.2.1",
67+
"react-native-reanimated": "^1.0.1",
6768
"react-native-screens": "^1.0.0-alpha.22",
6869
"react-test-renderer": "16.5.0",
6970
"release-it": "^11.0.0",
@@ -76,6 +77,7 @@
7677
"react": "*",
7778
"react-native": "*",
7879
"react-native-gesture-handler": "^1.0.0",
80+
"react-native-reanimated": "^1.0.0",
7981
"react-native-screens": "^1.0.0 || ^1.0.0-alpha"
8082
},
8183
"jest": {
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import Animated from 'react-native-reanimated';
2+
import { CardInterpolationProps, CardInterpolatedStyle } from '../types';
3+
4+
const { cond, multiply, interpolate } = Animated;
5+
6+
/**
7+
* Standard iOS-style slide in from the right.
8+
*/
9+
export function forHorizontalIOS({
10+
positions: { current, next },
11+
layout,
12+
}: CardInterpolationProps): CardInterpolatedStyle {
13+
const translateFocused = interpolate(current, {
14+
inputRange: [0, 1],
15+
outputRange: [layout.width, 0],
16+
});
17+
const translateUnfocused = next
18+
? interpolate(next, {
19+
inputRange: [0, 1],
20+
outputRange: [0, multiply(layout.width, -0.3)],
21+
})
22+
: 0;
23+
24+
const opacity = interpolate(current, {
25+
inputRange: [0, 1],
26+
outputRange: [0, 0.07],
27+
});
28+
29+
const shadowOpacity = interpolate(current, {
30+
inputRange: [0, 1],
31+
outputRange: [0, 0.3],
32+
});
33+
34+
return {
35+
cardStyle: {
36+
backgroundColor: '#eee',
37+
transform: [
38+
// Translation for the animation of the current card
39+
{ translateX: translateFocused },
40+
// Translation for the animation of the card on top of this
41+
{ translateX: translateUnfocused },
42+
],
43+
shadowOpacity,
44+
},
45+
overlayStyle: { opacity },
46+
};
47+
}
48+
49+
/**
50+
* Standard iOS-style slide in from the bottom (used for modals).
51+
*/
52+
export function forVerticalIOS({
53+
positions: { current },
54+
layout,
55+
}: CardInterpolationProps): CardInterpolatedStyle {
56+
const translateY = interpolate(current, {
57+
inputRange: [0, 1],
58+
outputRange: [layout.height, 0],
59+
});
60+
61+
return {
62+
cardStyle: {
63+
backgroundColor: '#eee',
64+
transform: [
65+
// Translation for the animation of the current card
66+
{ translateY },
67+
],
68+
},
69+
};
70+
}
71+
72+
/**
73+
* Standard Android-style fade in from the bottom for Android Oreo.
74+
*/
75+
export function forFadeFromBottomAndroid({
76+
positions: { current },
77+
layout,
78+
closing,
79+
}: CardInterpolationProps): CardInterpolatedStyle {
80+
const translateY = interpolate(current, {
81+
inputRange: [0, 1],
82+
outputRange: [multiply(layout.height, 0.08), 0],
83+
});
84+
85+
const opacity = cond(
86+
closing,
87+
current,
88+
interpolate(current, {
89+
inputRange: [0, 0.5, 0.9, 1],
90+
outputRange: [0, 0.25, 0.7, 1],
91+
})
92+
);
93+
94+
return {
95+
cardStyle: {
96+
opacity,
97+
transform: [{ translateY }],
98+
},
99+
};
100+
}
101+
102+
/**
103+
* Standard Android-style wipe from the bottom for Android Pie.
104+
*/
105+
export function forWipeFromBottomAndroid({
106+
positions: { current, next },
107+
layout,
108+
}: CardInterpolationProps): CardInterpolatedStyle {
109+
const containerTranslateY = interpolate(current, {
110+
inputRange: [0, 1],
111+
outputRange: [layout.height, 0],
112+
});
113+
const cardTranslateYFocused = interpolate(current, {
114+
inputRange: [0, 1],
115+
outputRange: [multiply(layout.height, 95.9 / 100, -1), 0],
116+
});
117+
const cardTranslateYUnfocused = next
118+
? interpolate(next, {
119+
inputRange: [0, 1],
120+
outputRange: [0, multiply(layout.height, 2 / 100, -1)],
121+
})
122+
: 0;
123+
const overlayOpacity = interpolate(current, {
124+
inputRange: [0, 0.36, 1],
125+
outputRange: [0, 0.1, 0.1],
126+
});
127+
128+
return {
129+
containerStyle: {
130+
transform: [{ translateY: containerTranslateY }],
131+
},
132+
cardStyle: {
133+
transform: [
134+
{ translateY: cardTranslateYFocused },
135+
{ translateY: cardTranslateYUnfocused },
136+
],
137+
},
138+
overlayStyle: { opacity: overlayOpacity },
139+
};
140+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import Animated from 'react-native-reanimated';
2+
import { HeaderInterpolationProps, HeaderInterpolatedStyle } from '../types';
3+
4+
const { interpolate, add } = Animated;
5+
6+
export function forUIKit({
7+
positions: { current, next },
8+
layouts,
9+
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
10+
const leftSpacing = 27;
11+
12+
// The title and back button title should cross-fade to each other
13+
// When screen is fully open, the title should be in center, and back title should be on left
14+
// When screen is closing, the previous title will animate to back title's position
15+
// And back title will animate to title's position
16+
// We achieve this by calculating the offsets needed to translate title to back title's position and vice-versa
17+
const backTitleOffset = layouts.backTitle
18+
? (layouts.screen.width - layouts.backTitle.width) / 2 - leftSpacing
19+
: undefined;
20+
const titleLeftOffset = layouts.title
21+
? (layouts.screen.width - layouts.title.width) / 2 - leftSpacing
22+
: undefined;
23+
24+
// When the current title is animating to right, it is centered in the right half of screen in middle of transition
25+
// The back title also animates in from this position
26+
const rightOffset = layouts.screen.width / 4;
27+
28+
const progress = add(current, next ? next : 0);
29+
30+
return {
31+
leftButtonStyle: {
32+
opacity: interpolate(progress, {
33+
inputRange: [0.3, 1, 1.5],
34+
outputRange: [0, 1, 0],
35+
}),
36+
},
37+
backTitleStyle: {
38+
// Title and back title are a bit different width due to title being bold
39+
// Adjusting the letterSpacing makes them coincide better
40+
letterSpacing: backTitleOffset
41+
? interpolate(progress, {
42+
inputRange: [0.3, 1, 2],
43+
outputRange: [0.35, 0, 0],
44+
})
45+
: 0,
46+
transform: [
47+
{
48+
// Avoid translating if we don't have its width
49+
// It means there's no back title set
50+
translateX: backTitleOffset
51+
? interpolate(progress, {
52+
inputRange: [0, 1, 2],
53+
outputRange: [backTitleOffset, 0, -rightOffset],
54+
})
55+
: 0,
56+
},
57+
],
58+
},
59+
titleStyle: {
60+
opacity: interpolate(progress, {
61+
inputRange: [0.4, 1, 1.5],
62+
outputRange: [0, 1, 0],
63+
}),
64+
transform: [
65+
{
66+
translateX: titleLeftOffset
67+
? interpolate(progress, {
68+
inputRange: [0.5, 1, 2],
69+
outputRange: [rightOffset, 0, -titleLeftOffset],
70+
})
71+
: 0,
72+
},
73+
],
74+
},
75+
};
76+
}
77+
78+
export function forFade({
79+
positions: { current, next },
80+
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
81+
const progress = add(current, next ? next : 0);
82+
const opacity = interpolate(progress, {
83+
inputRange: [0, 1, 2],
84+
outputRange: [0, 1, 0],
85+
});
86+
87+
return {
88+
leftButtonStyle: { opacity },
89+
titleStyle: { opacity },
90+
};
91+
}
92+
93+
export function forNoAnimation(): HeaderInterpolatedStyle {
94+
return {};
95+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
forHorizontalIOS,
3+
forVerticalIOS,
4+
forWipeFromBottomAndroid,
5+
forFadeFromBottomAndroid,
6+
} from './CardStyleInterpolators';
7+
import { forUIKit, forNoAnimation } from './HeaderStyleInterpolators';
8+
import {
9+
TransitionIOSSpec,
10+
WipeFromBottomAndroidSpec,
11+
FadeOutToBottomAndroidSpec,
12+
FadeInFromBottomAndroidSpec,
13+
} from './TransitionSpecs';
14+
import { TransitionPreset } from '../types';
15+
import { Platform } from 'react-native';
16+
17+
const ANDROID_VERSION_PIE = 28;
18+
19+
// Standard iOS navigation transition
20+
export const SlideFromRightIOS: TransitionPreset = {
21+
direction: 'horizontal',
22+
headerMode: 'float',
23+
transitionSpec: {
24+
open: TransitionIOSSpec,
25+
close: TransitionIOSSpec,
26+
},
27+
cardStyleInterpolator: forHorizontalIOS,
28+
headerStyleInterpolator: forUIKit,
29+
};
30+
31+
// Standard iOS navigation transition for modals
32+
export const ModalSlideFromBottomIOS: TransitionPreset = {
33+
direction: 'vertical',
34+
headerMode: 'screen',
35+
transitionSpec: {
36+
open: TransitionIOSSpec,
37+
close: TransitionIOSSpec,
38+
},
39+
cardStyleInterpolator: forVerticalIOS,
40+
headerStyleInterpolator: forNoAnimation,
41+
};
42+
43+
// Standard Android navigation transition when opening or closing an Activity on Android < 9
44+
export const FadeFromBottomAndroid: TransitionPreset = {
45+
direction: 'vertical',
46+
headerMode: 'screen',
47+
transitionSpec: {
48+
open: FadeInFromBottomAndroidSpec,
49+
close: FadeOutToBottomAndroidSpec,
50+
},
51+
cardStyleInterpolator: forFadeFromBottomAndroid,
52+
headerStyleInterpolator: forNoAnimation,
53+
};
54+
55+
// Standard Android navigation transition when opening or closing an Activity on Android >= 9
56+
export const WipeFromBottomAndroid: TransitionPreset = {
57+
direction: 'vertical',
58+
headerMode: 'screen',
59+
transitionSpec: {
60+
open: WipeFromBottomAndroidSpec,
61+
close: WipeFromBottomAndroidSpec,
62+
},
63+
cardStyleInterpolator: forWipeFromBottomAndroid,
64+
headerStyleInterpolator: forNoAnimation,
65+
};
66+
67+
export const DefaultTransition = Platform.select({
68+
ios: SlideFromRightIOS,
69+
default:
70+
Platform.OS === 'android' && Platform.Version < ANDROID_VERSION_PIE
71+
? FadeFromBottomAndroid
72+
: WipeFromBottomAndroid,
73+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Easing } from 'react-native-reanimated';
2+
import { TransitionSpec } from '../types';
3+
4+
export const TransitionIOSSpec: TransitionSpec = {
5+
timing: 'spring',
6+
config: {
7+
stiffness: 1000,
8+
damping: 500,
9+
mass: 3,
10+
overshootClamping: true,
11+
restDisplacementThreshold: 0.01,
12+
restSpeedThreshold: 0.01,
13+
},
14+
};
15+
16+
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
17+
export const FadeInFromBottomAndroidSpec: TransitionSpec = {
18+
timing: 'timing',
19+
config: {
20+
duration: 350,
21+
easing: Easing.out(Easing.poly(5)),
22+
},
23+
};
24+
25+
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
26+
export const FadeOutToBottomAndroidSpec: TransitionSpec = {
27+
timing: 'timing',
28+
config: {
29+
duration: 150,
30+
easing: Easing.in(Easing.linear),
31+
},
32+
};
33+
34+
// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
35+
export const WipeFromBottomAndroidSpec: TransitionSpec = {
36+
timing: 'timing',
37+
config: {
38+
duration: 425,
39+
// This is super rough approximation of the path used for the curve by android
40+
// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/interpolator/fast_out_extra_slow_in.xml
41+
easing: Easing.bezier(0.35, 0.45, 0, 1),
42+
},
43+
};

0 commit comments

Comments
 (0)