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

Commit

Permalink
feat: optimizations in stack
Browse files Browse the repository at this point in the history
  • Loading branch information
osdnk committed Sep 1, 2019
1 parent 06b3e6b commit 3f853d4
Showing 1 changed file with 145 additions and 39 deletions.
184 changes: 145 additions & 39 deletions packages/stack/src/views/Stack/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import {
PanGestureHandler,
State as GestureState,
} from 'react-native-gesture-handler';
import { TransitionSpec, CardStyleInterpolator, Layout } from '../../types';
import {
TransitionSpec,
CardStyleInterpolator,
Layout,
SpringConfig,
TimingConfig,
} from '../../types';
import memoize from '../../utils/memoize';
import StackGestureContext from '../../utils/StackGestureContext';
import PointerEventsView from './PointerEventsView';
Expand Down Expand Up @@ -50,12 +56,31 @@ type Props = ViewProps & {
contentStyle?: StyleProp<ViewStyle>;
};

type AnimatedSpringConfig = {
damping: Animated.Value<number>;
mass: Animated.Value<number>;
stiffness: Animated.Value<number>;
restSpeedThreshold: Animated.Value<number>;
restDisplacementThreshold: Animated.Value<number>;
overshootClamping: Animated.Value<boolean>;
};

export type AnimatedTimingConfig = {
duration: Animated.Value<number>;
easing: Animated.EasingFunction;
};

type Binary = 0 | 1;

const TRUE = 1;
const TRUE_NODE = new Animated.Value(TRUE);
const FALSE = 0;
const NOOP = 0;
const FALSE_NODE = new Animated.Value(FALSE);
const NOOP_NODE = FALSE_NODE;
const UNSET = -1;
const UNSET_NODE = new Animated.Value(UNSET);

const MINUS_ONE_NODE = UNSET_NODE;

const DIRECTION_VERTICAL = -1;
const DIRECTION_HORIZONTAL = 1;
Expand Down Expand Up @@ -110,7 +135,7 @@ if (Animated.proc) {
damping: Animated.Adaptable<number>,
mass: Animated.Adaptable<number>,
stiffness: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<boolean>,
restSpeedThreshold: Animated.Adaptable<number>,
restDisplacementThreshold: Animated.Adaptable<number>,
clock: Animated.Clock
Expand Down Expand Up @@ -174,6 +199,30 @@ if (Animated.proc) {
};
}

function transformSpringConfigToAnimatedValues(
config: SpringConfig
): AnimatedSpringConfig {
return {
damping: new Animated.Value(config.damping),
stiffness: new Animated.Value(config.stiffness),
mass: new Animated.Value(config.mass),
restDisplacementThreshold: new Animated.Value(
config.restDisplacementThreshold
),
restSpeedThreshold: new Animated.Value(config.restSpeedThreshold),
overshootClamping: new Animated.Value(config.overshootClamping),
};
}

function transformTimingConfigToAnimatedValues(
config: TimingConfig
): AnimatedTimingConfig {
return {
duration: new Animated.Value(config.duration),
easing: config.easing,
};
}

export default class Card extends React.Component<Props> {
static defaultProps = {
overlayEnabled: Platform.OS !== 'ios',
Expand Down Expand Up @@ -244,14 +293,34 @@ export default class Card extends React.Component<Props> {
height: new Value(this.props.layout.height),
};

openingSpecConfig =
this.props.transitionSpec.open.animation === 'timing'
? transformTimingConfigToAnimatedValues(
this.props.transitionSpec.open.config
)
: transformSpringConfigToAnimatedValues(
this.props.transitionSpec.open.config
);

closingSpecConfig =
this.props.transitionSpec.close.animation === 'timing'
? transformTimingConfigToAnimatedValues(
this.props.transitionSpec.close.config
)
: transformSpringConfigToAnimatedValues(
this.props.transitionSpec.close.config
);

private distance = cond(
eq(this.direction, DIRECTION_VERTICAL),
this.layout.height,
this.layout.width
);

private gestureUntraversed = new Value(0);
private gesture = new Value(0);
private offset = new Value(0);
private velocityUntraversed = new Value(0);
private velocity = new Value(0);

private gestureState = new Value(0);
Expand Down Expand Up @@ -285,8 +354,8 @@ export default class Card extends React.Component<Props> {
private runTransition = (isVisible: Binary | Animated.Node<number>) => {
const { open: openingSpec, close: closingSpec } = this.props.transitionSpec;

return cond(eq(this.props.current, isVisible), NOOP, [
cond(clockRunning(this.clock), NOOP, [
return cond(eq(this.props.current, isVisible), NOOP_NODE, [
cond(clockRunning(this.clock), NOOP_NODE, [
// Animation wasn't running before
// Set the initial values and start the clock
set(this.toValue, isVisible),
Expand All @@ -295,13 +364,17 @@ export default class Card extends React.Component<Props> {
set(
this.transitionVelocity,
multiply(
cond(this.distance, divide(this.velocity, this.distance), 0),
cond(
this.distance,
divide(this.velocity, this.distance),
FALSE_NODE
),
-1
)
),
set(this.frameTime, 0),
set(this.transitionState.time, 0),
set(this.transitionState.finished, FALSE),
set(this.frameTime, FALSE_NODE),
set(this.transitionState.time, FALSE_NODE),
set(this.transitionState.finished, FALSE_NODE),
set(this.isVisible, isVisible),
startClock(this.clock),
call([this.isVisible], ([value]: ReadonlyArray<Binary>) => {
Expand All @@ -312,35 +385,49 @@ export default class Card extends React.Component<Props> {
}),
]),
cond(
eq(isVisible, 1),
eq(isVisible, TRUE_NODE),
openingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...openingSpec.config, toValue: this.toValue }
// @ts-ignore
{
...(this.openingSpecConfig as AnimatedSpringConfig),
toValue: this.toValue,
}
)
: timing(
this.clock,
{ ...this.transitionState, frameTime: this.frameTime },
{ ...openingSpec.config, toValue: this.toValue }
{
...(this.openingSpecConfig as AnimatedTimingConfig),
toValue: this.toValue,
}
),
closingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...closingSpec.config, toValue: this.toValue }
// @ts-ignore
{
...(this.closingSpecConfig as AnimatedSpringConfig),
toValue: this.toValue,
}
)
: timing(
this.clock,
{ ...this.transitionState, frameTime: this.frameTime },
{ ...closingSpec.config, toValue: this.toValue }
{
...(this.closingSpecConfig as AnimatedTimingConfig),
toValue: this.toValue,
}
)
),
cond(this.transitionState.finished, [
// Reset values
set(this.isSwipeGesture, FALSE),
set(this.gesture, 0),
set(this.velocity, 0),
set(this.isSwipeGesture, FALSE_NODE),
set(this.gesture, FALSE_NODE),
set(this.velocity, FALSE_NODE),
// When the animation finishes, stop the clock
stopClock(this.clock),
call([this.isVisible], ([value]: ReadonlyArray<Binary>) => {
Expand All @@ -365,22 +452,36 @@ export default class Card extends React.Component<Props> {
);

private exec = block([
set(
this.gesture,
multiply(
this.gestureUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
)
),
set(
this.velocity,
multiply(
this.velocityUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
)
),
onChange(
this.isClosing,
cond(this.isClosing, set(this.nextIsVisible, FALSE))
cond(this.isClosing, set(this.nextIsVisible, FALSE_NODE))
),
onChange(
this.nextIsVisible,
cond(neq(this.nextIsVisible, UNSET), [
cond(neq(this.nextIsVisible, UNSET_NODE), [
// Stop any running animations
cond(clockRunning(this.clock), [
call([], this.handleTransitionEnd),
stopClock(this.clock),
]),
set(this.gesture, 0),
set(this.gesture, FALSE_NODE),
// Update the index to trigger the transition
set(this.isVisible, this.nextIsVisible),
set(this.nextIsVisible, UNSET),
set(this.nextIsVisible, UNSET_NODE),
])
),
onChange(
Expand Down Expand Up @@ -418,11 +519,11 @@ export default class Card extends React.Component<Props> {
cond(
eq(this.gestureState, GestureState.ACTIVE),
[
cond(this.isSwiping, NOOP, [
cond(this.isSwiping, NOOP_NODE, [
// We weren't dragging before, set it to true
set(this.isSwipeCancelled, FALSE),
set(this.isSwiping, TRUE),
set(this.isSwipeGesture, TRUE),
set(this.isSwipeCancelled, FALSE_NODE),
set(this.isSwiping, TRUE_NODE),
set(this.isSwipeGesture, TRUE_NODE),
// Also update the drag offset to the last position
set(this.offset, this.props.current),
]),
Expand All @@ -433,11 +534,15 @@ export default class Card extends React.Component<Props> {
max(
sub(
this.offset,
cond(this.distance, divide(this.gesture, this.distance), 1)
cond(
this.distance,
divide(this.gesture, this.distance),
TRUE_NODE
)
),
0
FALSE_NODE
),
1
TRUE_NODE
)
),
// Stop animations while we're dragging
Expand All @@ -460,7 +565,7 @@ export default class Card extends React.Component<Props> {
this.isSwipeCancelled,
eq(this.gestureState, GestureState.CANCELLED)
),
set(this.isSwiping, FALSE),
set(this.isSwiping, FALSE_NODE),
this.runTransition(
cond(
greaterThan(
Expand All @@ -469,11 +574,15 @@ export default class Card extends React.Component<Props> {
),
cond(
lessThan(
cond(eq(this.velocity, 0), this.gesture, this.velocity),
0
cond(
eq(this.velocity, FALSE_NODE),
this.gesture,
this.velocity
),
FALSE_NODE
),
TRUE,
FALSE
TRUE_NODE,
FALSE_NODE
),
this.isVisible
)
Expand All @@ -485,11 +594,8 @@ export default class Card extends React.Component<Props> {
private handleGestureEventHorizontal = Animated.event([
{
nativeEvent: {
translationX: (x: Animated.Adaptable<number>) =>
set(this.gesture, multiply(x, I18nManager.isRTL ? -1 : 1)),
velocityX: (x: Animated.Adaptable<number>) =>
set(this.velocity, multiply(x, I18nManager.isRTL ? -1 : 1)),
state: this.gestureState,
translationX: this.gestureUntraversed,
velocityX: this.velocityUntraversed,
},
},
]);
Expand Down Expand Up @@ -526,7 +632,7 @@ export default class Card extends React.Component<Props> {
})
);

// Keep track of the style in a property to avoid changing the animated node when deps change
// Keep track of the style in a property to avoid changing the animated node when deps change`
// The style shouldn't change in the middle of the animation and should refer to what was there at the start of it
// Which will be the last value when just before the render which started the animation
// We need to make sure to update this when the running animation ends
Expand Down

0 comments on commit 3f853d4

Please sign in to comment.