Skip to content

Commit e650505

Browse files
94cstylesvonovak
authored andcommitted
Fix stack navigator animations (react-navigation#1493) (react-navigation#2520)
* Fix animation (react-navigation#1493) * Rewrite the file animatedInterpolate.js. To increase readability. * Rename the variable, To increase readability. * minor renaming
1 parent c5ffe41 commit e650505

File tree

4 files changed

+153
-42
lines changed

4 files changed

+153
-42
lines changed

src/TypeDefinition.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,8 @@ export type LayoutEvent = {
493493
},
494494
},
495495
};
496+
497+
export type SceneIndicesForInterpolationInputRange = {
498+
first: number,
499+
last: number,
500+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* @flow */
2+
3+
import type {
4+
NavigationSceneRendererProps,
5+
NavigationScene,
6+
SceneIndicesForInterpolationInputRange,
7+
} from '../TypeDefinition';
8+
9+
function getSceneIndicesForInterpolationInputRange(
10+
props: NavigationSceneRendererProps
11+
): SceneIndicesForInterpolationInputRange | null {
12+
const { scene, scenes } = props;
13+
const index = scene.index;
14+
const lastSceneIndexInScenes = scenes.length - 1;
15+
const isBack = !scenes[lastSceneIndexInScenes].isActive;
16+
17+
if (isBack) {
18+
const currentSceneIndexInScenes = scenes.findIndex(
19+
(item: NavigationScene) => item === scene
20+
);
21+
const targetSceneIndexInScenes = scenes.findIndex(
22+
(item: NavigationScene) => item.isActive
23+
);
24+
const targetSceneIndex = scenes[targetSceneIndexInScenes].index;
25+
const lastSceneIndex = scenes[lastSceneIndexInScenes].index;
26+
27+
if (
28+
index !== targetSceneIndex &&
29+
currentSceneIndexInScenes === lastSceneIndexInScenes
30+
) {
31+
return {
32+
first: Math.min(targetSceneIndex, index - 1),
33+
last: index + 1,
34+
};
35+
} else if (
36+
index === targetSceneIndex &&
37+
currentSceneIndexInScenes === targetSceneIndexInScenes
38+
) {
39+
return {
40+
first: index - 1,
41+
last: Math.max(lastSceneIndex, index + 1),
42+
};
43+
} else if (
44+
index === targetSceneIndex ||
45+
currentSceneIndexInScenes > targetSceneIndexInScenes
46+
) {
47+
return null;
48+
} else {
49+
return { first: index - 1, last: index + 1 };
50+
}
51+
} else {
52+
return { first: index - 1, last: index + 1 };
53+
}
54+
}
55+
56+
export default getSceneIndicesForInterpolationInputRange;

src/views/CardStack/CardStackStyleInterpolator.js

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
AnimatedViewStyleProp,
88
} from '../../TypeDefinition';
99

10+
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
11+
1012
/**
1113
* Utility that builds the style for the card in the cards stack.
1214
*
@@ -51,33 +53,25 @@ function forHorizontal(
5153
if (!layout.isMeasured) {
5254
return forInitial(props);
5355
}
56+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
5457

55-
const index = scene.index;
56-
const inputRange = [index - 1, index, index + 1];
58+
if (!interpolate) return { opacity: 0 };
5759

58-
const width = layout.initWidth;
59-
const outputRange = I18nManager.isRTL
60-
? ([-width, 0, width * 0.3]: Array<number>)
61-
: ([width, 0, width * -0.3]: Array<number>);
62-
63-
// Add [index - 1, index - 0.99] to the interpolated opacity for screen transition.
64-
// This makes the screen's shadow to disappear smoothly.
60+
const { first, last } = interpolate;
61+
const index = scene.index;
6562
const opacity = position.interpolate({
66-
inputRange: ([
67-
index - 1,
68-
index - 0.99,
69-
index,
70-
index + 0.99,
71-
index + 1,
72-
]: Array<number>),
63+
inputRange: [first, first + 0.01, index, last - 0.01, last],
7364
outputRange: ([0, 1, 1, 0.85, 0]: Array<number>),
7465
});
7566

76-
const translateY = 0;
67+
const width = layout.initWidth;
7768
const translateX = position.interpolate({
78-
inputRange,
79-
outputRange,
69+
inputRange: ([first, index, last]: Array<number>),
70+
outputRange: I18nManager.isRTL
71+
? ([-width, 0, width * 0.3]: Array<number>)
72+
: ([width, 0, width * -0.3]: Array<number>),
8073
});
74+
const translateY = 0;
8175

8276
return {
8377
opacity,
@@ -96,26 +90,23 @@ function forVertical(
9690
if (!layout.isMeasured) {
9791
return forInitial(props);
9892
}
93+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
9994

100-
const index = scene.index;
101-
const height = layout.initHeight;
95+
if (!interpolate) return { opacity: 0 };
10296

97+
const { first, last } = interpolate;
98+
const index = scene.index;
10399
const opacity = position.interpolate({
104-
inputRange: ([
105-
index - 1,
106-
index - 0.99,
107-
index,
108-
index + 0.99,
109-
index + 1,
110-
]: Array<number>),
100+
inputRange: [first, first + 0.01, index, last - 0.01, last],
111101
outputRange: ([0, 1, 1, 0.85, 0]: Array<number>),
112102
});
113103

114-
const translateX = 0;
104+
const height = layout.initHeight;
115105
const translateY = position.interpolate({
116-
inputRange: ([index - 1, index, index + 1]: Array<number>),
106+
inputRange: ([first, index, last]: Array<number>),
117107
outputRange: ([height, 0, 0]: Array<number>),
118108
});
109+
const translateX = 0;
119110

120111
return {
121112
opacity,
@@ -134,27 +125,56 @@ function forFadeFromBottomAndroid(
134125
if (!layout.isMeasured) {
135126
return forInitial(props);
136127
}
128+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
129+
130+
if (!interpolate) return { opacity: 0 };
137131

132+
const { first, last } = interpolate;
138133
const index = scene.index;
139-
const inputRange = [index - 1, index, index + 0.99, index + 1];
134+
const inputRange = ([first, index, last - 0.01, last]: Array<number>);
140135

141136
const opacity = position.interpolate({
142137
inputRange,
143138
outputRange: ([0, 1, 1, 0]: Array<number>),
144139
});
145140

146-
const translateX = 0;
147141
const translateY = position.interpolate({
148142
inputRange,
149143
outputRange: ([50, 0, 0, 0]: Array<number>),
150144
});
145+
const translateX = 0;
151146

152147
return {
153148
opacity,
154149
transform: [{ translateX }, { translateY }],
155150
};
156151
}
157152

153+
/**
154+
* fadeIn and fadeOut
155+
*/
156+
function forFade(props: NavigationSceneRendererProps): AnimatedViewStyleProp {
157+
const { layout, position, scene } = props;
158+
159+
if (!layout.isMeasured) {
160+
return forInitial(props);
161+
}
162+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
163+
164+
if (!interpolate) return { opacity: 0 };
165+
166+
const { first, last } = interpolate;
167+
const index = scene.index;
168+
const opacity = position.interpolate({
169+
inputRange: ([first, index, last]: Array<number>),
170+
outputRange: ([0, 1, 1]: Array<number>),
171+
});
172+
173+
return {
174+
opacity,
175+
};
176+
}
177+
158178
function canUseNativeDriver(): boolean {
159179
// The native driver can be enabled for this interpolator animating
160180
// opacity, translateX, and translateY is supported by the native animation
@@ -166,5 +186,6 @@ export default {
166186
forHorizontal,
167187
forVertical,
168188
forFadeFromBottomAndroid,
189+
forFade,
169190
canUseNativeDriver,
170191
};

src/views/Header/HeaderStyleInterpolator.js

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import { I18nManager } from 'react-native';
44

55
import type {
66
NavigationSceneRendererProps,
7+
NavigationScene,
78
AnimatedViewStyleProp,
89
} from '../../TypeDefinition';
910

11+
import getSceneIndicesForInterpolationInputRange from '../../utils/getSceneIndicesForInterpolationInputRange';
12+
1013
/**
1114
* Utility that builds the style for the navigation header.
1215
*
@@ -19,31 +22,52 @@ import type {
1922
*/
2023

2124
function forLeft(props: NavigationSceneRendererProps): AnimatedViewStyleProp {
22-
const { position, scene } = props;
23-
const { index } = scene;
25+
const { position, scene, scenes } = props;
26+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
27+
28+
if (!interpolate) return { opacity: 0 };
29+
30+
const activeScene = scenes.find((item: NavigationScene) => item.isActive);
31+
const activeIndex = scenes.findIndex(
32+
(item: NavigationScene) => item === activeScene
33+
);
34+
const currentIndex = scenes.findIndex(
35+
(item: NavigationScene) => item === scene
36+
);
37+
const deviation = Math.abs((activeIndex - currentIndex) / 2);
38+
const { first, last } = interpolate;
39+
const index = scene.index;
40+
2441
return {
2542
opacity: position.interpolate({
26-
inputRange: [index - 1, index - 0.5, index, index + 0.5, index + 1],
43+
inputRange: [first, first + deviation, index, last - deviation, last],
2744
outputRange: ([0, 0, 1, 0, 0]: Array<number>),
2845
}),
2946
};
3047
}
3148

3249
function forCenter(props: NavigationSceneRendererProps): AnimatedViewStyleProp {
3350
const { position, scene } = props;
34-
const { index } = scene;
51+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
52+
53+
if (!interpolate) return { opacity: 0 };
54+
55+
const { first, last } = interpolate;
56+
const index = scene.index;
57+
const inputRange = [first, index, last];
58+
3559
return {
3660
opacity: position.interpolate({
37-
inputRange: [index - 1, index, index + 1],
61+
inputRange,
3862
outputRange: ([0, 1, 0]: Array<number>),
3963
}),
4064
transform: [
4165
{
4266
translateX: position.interpolate({
43-
inputRange: [index - 1, index + 1],
67+
inputRange,
4468
outputRange: I18nManager.isRTL
45-
? ([-200, 200]: Array<number>)
46-
: ([200, -200]: Array<number>),
69+
? ([-200, 0, 200]: Array<number>)
70+
: ([200, 0, -200]: Array<number>),
4771
}),
4872
},
4973
],
@@ -52,10 +76,15 @@ function forCenter(props: NavigationSceneRendererProps): AnimatedViewStyleProp {
5276

5377
function forRight(props: NavigationSceneRendererProps): AnimatedViewStyleProp {
5478
const { position, scene } = props;
55-
const { index } = scene;
79+
const interpolate = getSceneIndicesForInterpolationInputRange(props);
80+
81+
if (!interpolate) return { opacity: 0 };
82+
const { first, last } = interpolate;
83+
const index = scene.index;
84+
5685
return {
5786
opacity: position.interpolate({
58-
inputRange: [index - 1, index, index + 1],
87+
inputRange: [first, index, last],
5988
outputRange: ([0, 1, 0]: Array<number>),
6089
}),
6190
};

0 commit comments

Comments
 (0)