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

Commit 3a41157

Browse files
committed
Improvements to cardStack, revive behind screen style transforming
1 parent bc7527b commit 3a41157

8 files changed

Lines changed: 124 additions & 91 deletions

File tree

App.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ const AppNavigator = createSwitchNavigator({
3939
...EXAMPLES,
4040
});
4141

42-
// const StatefulAppNavigator = createNavigationContainer(AppNavigator);
42+
const StatefulAppNavigator = createNavigationContainer(AppNavigator);
43+
4344
// const StatefulAppNavigator = createNavigationContainer(Fade);
4445
// const StatefulAppNavigator = createNavigationContainer(Modal);
45-
const StatefulAppNavigator = createNavigationContainer(Gesture);
46+
// const StatefulAppNavigator = createNavigationContainer(Gesture);
4647
// const StatefulAppNavigator = createNavigationContainer(CardStack);
4748
// const StatefulAppNavigator = createNavigationContainer(SharedEl);
4849

CardTransition.js

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const {
4646
} = Animated;
4747
const callWhenTrue = (val, callback) => cond(val, call([val], callback));
4848

49-
const TOSS_VELOCITY_MULTIPLIER = 0.2;
49+
const TOSS_VELOCITY_MULTIPLIER = 0.5;
5050

5151
export default class CardTransition extends React.Component {
5252
static navigationOptions = {
@@ -59,7 +59,7 @@ export default class CardTransition extends React.Component {
5959
transform: [
6060
{
6161
translateX: multiply(
62-
-0.3,
62+
-0.5,
6363
multiply(transition.screenWidth, transition.progress),
6464
),
6565
},
@@ -74,16 +74,30 @@ export default class CardTransition extends React.Component {
7474
const gestureVelocityX = new Value(0);
7575
const targetProgress = new Value(0);
7676
const screenWidth = new Value(Dimensions.get("window").width);
77-
const targetProgressDistance = multiply(targetProgress, screenWidth);
77+
const lastGestureTranslateX = new Value(0);
78+
const lastGestureVelocityX = new Value(0);
79+
80+
const isClosing = and(
81+
neq(gestureState, State.ACTIVE),
82+
lessThan(
83+
add(
84+
lastGestureTranslateX,
85+
multiply(TOSS_VELOCITY_MULTIPLIER, lastGestureVelocityX),
86+
),
87+
-100,
88+
),
89+
);
90+
const targetProgressDistance = multiply(
91+
cond(isClosing, 0, targetProgress),
92+
screenWidth,
93+
);
7894
const progressDistance = new Value(0);
7995
const isAtRest = and(
8096
not(clockRunning(clock)),
8197
neq(gestureState, State.ACTIVE),
8298
);
8399
const uprightGestureTranslateX = multiply(gestureTranslateX, -1);
84100
const uprightGestureVelocityX = multiply(gestureVelocityX, -1);
85-
const lastGestureTranslateX = new Value(0);
86-
const lastGestureVelocityX = new Value(0);
87101
const state = {
88102
finished: new Value(0),
89103
velocity: new Value(0),
@@ -92,12 +106,12 @@ export default class CardTransition extends React.Component {
92106
};
93107

94108
const config = {
95-
stiffness: 1000,
96-
damping: 1000,
109+
stiffness: 100,
110+
damping: 500,
97111
mass: 3,
98112
overshootClamping: true,
99-
restSpeedThreshold: 0.01,
100-
restDisplacementThreshold: 0.01,
113+
restSpeedThreshold: 0.1,
114+
restDisplacementThreshold: 1,
101115
toValue: targetProgressDistance,
102116
};
103117

@@ -110,19 +124,21 @@ export default class CardTransition extends React.Component {
110124
spring(clock, state, config),
111125
cond(state.finished, stopClock(clock)),
112126
];
113-
127+
const gestureProgressDistance = add(
128+
progressDistance,
129+
sub(uprightGestureTranslateX, prevGestureTranslateX),
130+
);
131+
const clampedGestureProgressDistance = cond(
132+
greaterThan(screenWidth, gestureProgressDistance),
133+
gestureProgressDistance,
134+
screenWidth,
135+
);
114136
const springProgressDistance = block([
115137
cond(
116138
eq(gestureState, State.ACTIVE),
117139
[
118140
stopClock(clock),
119-
set(
120-
progressDistance,
121-
add(
122-
progressDistance,
123-
sub(uprightGestureTranslateX, prevGestureTranslateX),
124-
),
125-
),
141+
set(progressDistance, clampedGestureProgressDistance),
126142
set(prevGestureTranslateX, uprightGestureTranslateX),
127143
set(lastGestureTranslateX, uprightGestureTranslateX),
128144
set(lastGestureVelocityX, uprightGestureVelocityX),
@@ -141,19 +157,8 @@ export default class CardTransition extends React.Component {
141157
callbacksWaitingForRest.push(resolve);
142158
});
143159
const closingCallback = () => {
144-
// transition.navigation.goBack(transition.transitionRouteKey);
145-
targetProgress.setValue(0);
160+
transition.navigation.goBack(transition.transitionRouteKey);
146161
};
147-
const isClosing = and(
148-
neq(gestureState, State.ACTIVE),
149-
lessThan(
150-
add(
151-
lastGestureTranslateX,
152-
multiply(TOSS_VELOCITY_MULTIPLIER, lastGestureVelocityX),
153-
),
154-
-100,
155-
),
156-
);
157162
const finalDistanceProgress = block([
158163
springProgressDistance,
159164
callWhenTrue(isAtRest, whenDoneCallback),
@@ -171,8 +176,9 @@ export default class CardTransition extends React.Component {
171176
waitForDone,
172177
};
173178
},
174-
runTransition: async transition => {
175-
transition.targetProgress.setValue(1);
179+
runTransition: async (transition, _, fromState, toState) => {
180+
const destVal = toState.index >= fromState.index ? 1 : 0;
181+
transition.targetProgress.setValue(destVal);
176182

177183
await transition.waitForDone();
178184
},

Shared.js

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ const createSharedTransition = transition => {
5151
...transition,
5252
progress,
5353
fromLayouts: {},
54-
toLayouts: {},
55-
toScreenLayout: {},
54+
transitionLayouts: {},
55+
transitionScreenLayout: {},
5656
fromScreenLayout: {},
5757
};
5858
};
@@ -67,12 +67,13 @@ const runSharedTransition = async (
6767
// elements and set those measurements into Animated values so that the pre-rendered
6868
// transition looks correct
6969

70-
const toRouteKey = toState.routes[toState.index].key;
71-
const fromRouteKey = fromState.routes[fromState.index].key;
70+
const transitionRouteKey = transition.transitionRouteKey;
71+
const fromRouteKey = transition.fromRouteKey;
7272
const fromScreen = transitionScreenRefs[fromRouteKey].current;
73-
const toScreen = transitionScreenRefs[toRouteKey].current;
73+
const transitionScreen = transitionScreenRefs[transitionRouteKey].current;
7474
const fromSharedElements = (fromScreen && fromScreen.sharedElements) || {};
75-
const toSharedElements = (toScreen && toScreen.sharedElements) || {};
75+
const toSharedElements =
76+
(transitionScreen && transitionScreen.sharedElements) || {};
7677
const sharedElementIds = Object.keys(fromSharedElements).filter(
7778
i => Object.keys(toSharedElements).indexOf(i) !== -1,
7879
);
@@ -82,27 +83,36 @@ const runSharedTransition = async (
8283
return await measureEl(element);
8384
}),
8485
); // todo, collapse these into one parallel promise.all:
85-
const toLayouts = await Promise.all(
86+
const transitionLayouts = await Promise.all(
8687
sharedElementIds.map(async id => {
8788
const element = toSharedElements[id];
8889
return await measureEl(element);
8990
}),
9091
);
91-
const toScreenLayout = await measureEl(toScreen.getEl());
92+
const transitionScreenLayout = await measureEl(transitionScreen.getEl());
9293
const fromScreenLayout = await measureEl(fromScreen.getEl());
9394

94-
setLayoutOnKey(transition, "toScreenLayout", toScreenLayout);
95+
setLayoutOnKey(transition, "transitionScreenLayout", transitionScreenLayout);
9596
setLayoutOnKey(transition, "fromScreenLayout", fromScreenLayout);
9697

9798
sharedElementIds.forEach((sharedElId, index) => {
98-
setLayoutOnKey(transition.toLayouts, sharedElId, toLayouts[index]);
99+
setLayoutOnKey(
100+
transition.transitionLayouts,
101+
sharedElId,
102+
transitionLayouts[index],
103+
);
99104
setLayoutOnKey(transition.fromLayouts, sharedElId, fromLayouts[index]);
100105
});
106+
const destValue = !!toState.routes.find(
107+
r => r.key === transition.transitionRouteKey,
108+
)
109+
? 1
110+
: 0;
101111
await new Promise(resolve => {
102112
timing(transition.progress, {
103113
easing: Easing.out(Easing.cubic),
104114
duration: 600,
105-
toValue: 1,
115+
toValue: destValue,
106116
useNativeDriver: true,
107117
}).start(resolve);
108118
});
@@ -161,24 +171,22 @@ export class SharedFadeTransition extends React.Component {
161171

162172
const getTransitionElementStyle = (transitionContext, screenContext, id) => {
163173
const transition = transitionContext.getTransition();
164-
const fromState = screenContext.getTransitioningFromState();
165-
const toState = screenContext.getTransitioningToState();
166174
const thisScreenKey = screenContext.getNavigation().state.key;
167175
if (!transition) {
168176
return [{ transform: [] }];
169177
}
170-
const toLayout = getLayout(transition.toLayouts, id);
178+
const transitionLayout = getLayout(transition.transitionLayouts, id);
171179
const fromLayout = getLayout(transition.fromLayouts, id);
172180

173-
if (!toLayout || !fromLayout) {
174-
return [{ transform: [] }];
181+
if (!transitionLayout || !fromLayout) {
182+
throw new Error("This is unexpected");
175183
}
176-
const toRouteKey = toState.routes[toState.index].key;
177-
const fromRouteKey = fromState.routes[fromState.index].key;
178-
const isToScreen = toRouteKey === thisScreenKey;
184+
const transitionRouteKey = transition.transitionRouteKey;
185+
const fromRouteKey = transition.fromRouteKey;
186+
const isTransitionScreen = transitionRouteKey === thisScreenKey;
179187
const isFromScreen = fromRouteKey === thisScreenKey;
180188

181-
const isMeasured = and(toLayout.hasMeasured, fromLayout.hasMeasured);
189+
const isMeasured = and(transitionLayout.hasMeasured, fromLayout.hasMeasured);
182190

183191
const doInterpolate = (measureVal, start, end) =>
184192
interpolate(transition.progress, {
@@ -187,7 +195,7 @@ const getTransitionElementStyle = (transitionContext, screenContext, id) => {
187195
});
188196

189197
const interpolateScale = (to, from) => {
190-
if (isToScreen) {
198+
if (isTransitionScreen) {
191199
return doInterpolate(1, divide(from, to), 1);
192200
} else if (isFromScreen) {
193201
return doInterpolate(1, 1, divide(to, from));
@@ -196,7 +204,7 @@ const getTransitionElementStyle = (transitionContext, screenContext, id) => {
196204
}
197205
};
198206
const interpolateTranslate = (toOffset, fromOffset, toScale, fromScale) => {
199-
if (isToScreen) {
207+
if (isTransitionScreen) {
200208
return doInterpolate(
201209
0,
202210
sub(
@@ -232,25 +240,25 @@ const getTransitionElementStyle = (transitionContext, screenContext, id) => {
232240
transform: [
233241
{
234242
translateX: translateTransform(
235-
toLayout.x,
243+
transitionLayout.x,
236244
fromLayout.x,
237-
toLayout.w,
245+
transitionLayout.w,
238246
fromLayout.w,
239247
),
240248
},
241249
{
242250
translateY: translateTransform(
243-
toLayout.y,
251+
transitionLayout.y,
244252
fromLayout.y,
245-
toLayout.h,
253+
transitionLayout.h,
246254
fromLayout.h,
247255
),
248256
},
249257
{
250-
scaleX: scaleTransform(toLayout.w, fromLayout.w),
258+
scaleX: scaleTransform(transitionLayout.w, fromLayout.w),
251259
},
252260
{
253-
scaleY: scaleTransform(toLayout.h, fromLayout.h),
261+
scaleY: scaleTransform(transitionLayout.h, fromLayout.h),
254262
},
255263
],
256264
};

Transitioner.js

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ const getStateForNavChange = (props, state) => {
6969
state.descriptors[transitionRouteKey] ||
7070
state.transitioningFromDescriptors[transitionRouteKey];
7171
const { options } = descriptor;
72+
const fromRoute = state.navState.routes[state.navState.index];
7273
const createTransition = options.createTransition || defaultCreateTransition;
7374
const transition =
7475
state.transitions[transitionRouteKey] ||
7576
createTransition({
7677
navigation: props.navigation,
7778
transitionRouteKey,
79+
fromRouteKey: fromRoute.key,
7880
});
7981
return {
8082
transitions: {
@@ -149,7 +151,12 @@ export class Transitioner extends React.Component {
149151
// after async animator, this.props may have changed. re-check it now:
150152
if (navState === this.props.navigation.state) {
151153
// Navigation state is currently the exact state we were transitioning to. Set final state and we're done
154+
const transitions = {}; // clear out unusued transitions
155+
navState.routes.map(r => r.key).forEach(activeRouteKey => {
156+
transitions[activeRouteKey] = state.transitions[activeRouteKey];
157+
});
152158
this.setState({
159+
transitions,
153160
transitionRouteKey: null,
154161
transitioningFromState: null,
155162
transitioningFromDescriptors: {},
@@ -205,7 +212,6 @@ export class Transitioner extends React.Component {
205212
navState,
206213
descriptors,
207214
} = this.state;
208-
209215
const mainRouteKeys = navState.routes.map(r => r.key);
210216
let routeKeys = mainRouteKeys;
211217

@@ -227,25 +233,32 @@ export class Transitioner extends React.Component {
227233
descriptors[key] || transitioningFromDescriptors[key];
228234
const C = descriptor.getComponent();
229235

230-
const backScreenStyles = {}; // FIX THIS:
231-
// const backScreenRouteKeys = routeKeys.slice(index + 1);
232-
// const backScreenStyles = backScreenRouteKeys.map(
233-
// backScreenRouteKey => {
234-
// const backScreenDescriptor =
235-
// toDescriptors[backScreenRouteKey] ||
236-
// this.state.descriptors[backScreenRouteKey];
237-
// const { options } = backScreenDescriptor;
238-
// if (!transition || !options.getBehindTransitionAnimatedStyle) {
239-
// return {};
240-
// }
241-
// return options.getBehindTransitionAnimatedStyle(transition);
242-
// },
243-
// );
236+
const aboveScreenRouteKeys = routeKeys.slice(index + 1);
237+
let behindScreenStyles = aboveScreenRouteKeys.map(
238+
(aboveScreenRouteKey, behindScreenRouteIndex) => {
239+
const aboveTransition = transitions[aboveScreenRouteKey];
240+
const aboveScreenDescriptor =
241+
descriptors[aboveScreenRouteKey] ||
242+
transitioningFromDescriptors[aboveScreenRouteKey];
243+
const { options } = aboveScreenDescriptor;
244+
if (
245+
!aboveTransition ||
246+
!options.getBehindTransitionAnimatedStyle
247+
) {
248+
return {};
249+
}
250+
return options.getBehindTransitionAnimatedStyle(aboveTransition);
251+
},
252+
);
244253
let transition = transitions[key];
245-
254+
if (behindScreenStyles.length === 0) {
255+
// bizarre react-native bug that refuses to clear Animated.View styles unless you do something like this..
256+
// to reproduce the problem, set a getBehindTransitionAnimatedStyle that puts opacity at 0.5
257+
behindScreenStyles = [{ opacity: 1 }];
258+
}
246259
return (
247260
<Animated.View
248-
style={[{ ...StyleSheet.absoluteFillObject }, backScreenStyles]}
261+
style={[{ ...StyleSheet.absoluteFillObject }, behindScreenStyles]}
249262
pointerEvents={"auto"}
250263
key={key}
251264
>

0 commit comments

Comments
 (0)