refactor: refactor and perf improvements

This commit is contained in:
satyajit.happy
2019-06-06 12:32:33 +02:00
parent 9b176e9dc8
commit efdfffaeee
12 changed files with 106 additions and 218 deletions

View File

@@ -23,7 +23,6 @@ type Props = ViewProps & {
onGestureCanceled?: () => void;
onGestureEnd?: () => void;
children: React.ReactNode;
animateIn: boolean;
gesturesEnabled: boolean;
gestureResponseDistance?: {
vertical?: number;
@@ -58,54 +57,46 @@ const GESTURE_RESPONSE_DISTANCE_HORIZONTAL = 50;
const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
const {
cond,
eq,
neq,
set,
abs,
and,
or,
block,
call,
cond,
divide,
eq,
greaterThan,
lessThan,
abs,
add,
max,
block,
stopClock,
startClock,
clockRunning,
min,
neq,
onChange,
Value,
Clock,
call,
or,
set,
spring,
sub,
timing,
interpolate,
startClock,
stopClock,
clockRunning,
Clock,
Value,
} = Animated;
export default class Card extends React.Component<Props> {
static defaultProps = {
animateIn: true,
gesturesEnabled: true,
};
componentDidUpdate(prevProps: Props) {
const { layout, direction, closing, animateIn } = this.props;
const { layout, direction, closing } = this.props;
const { width, height } = layout;
if (
width !== prevProps.layout.width ||
height !== prevProps.layout.height
) {
if (width !== prevProps.layout.width) {
this.layout.width.setValue(width);
this.layout.height.setValue(height);
}
this.position.setValue(
animateIn
? direction === 'vertical'
? layout.height
: layout.width
: 0
);
if (height !== prevProps.layout.height) {
this.layout.height.setValue(height);
}
if (direction !== prevProps.direction) {
@@ -143,14 +134,6 @@ export default class Card extends React.Component<Props> {
this.layout.width
);
private position = new Value(
this.props.animateIn
? this.props.direction === 'vertical'
? this.props.layout.height
: this.props.layout.width
: 0
);
private gesture = new Value(0);
private offset = new Value(0);
private velocity = new Value(0);
@@ -164,8 +147,10 @@ export default class Card extends React.Component<Props> {
private toValue = new Value(0);
private frameTime = new Value(0);
private transitionVelocity = new Value(0);
private transitionState = {
position: this.position,
position: this.props.current,
time: new Value(0),
finished: new Value(FALSE),
};
@@ -173,13 +158,17 @@ export default class Card extends React.Component<Props> {
private runTransition = (isVisible: Binary | Animated.Node<number>) => {
const { open: openingSpec, close: closingSpec } = this.props.transitionSpec;
const toValue = cond(isVisible, 0, this.distance);
return cond(eq(this.position, toValue), NOOP, [
return cond(eq(this.props.current, isVisible), NOOP, [
cond(clockRunning(this.clock), NOOP, [
// Animation wasn't running before
// Set the initial values and start the clock
set(this.toValue, toValue),
set(this.toValue, isVisible),
// The velocity value is ideal for translating the whole screen
// But since we have 0-1 scale, we need to adjust the velocity
set(
this.transitionVelocity,
cond(this.distance, divide(this.velocity, this.distance), 0)
),
set(this.frameTime, 0),
set(this.transitionState.time, 0),
set(this.transitionState.finished, FALSE),
@@ -192,11 +181,11 @@ export default class Card extends React.Component<Props> {
}),
]),
cond(
eq(toValue, 0),
eq(isVisible, 1),
openingSpec.timing === 'spring'
? spring(
this.clock,
{ ...this.transitionState, velocity: this.velocity },
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...openingSpec.config, toValue: this.toValue }
)
: timing(
@@ -207,7 +196,7 @@ export default class Card extends React.Component<Props> {
closingSpec.timing === 'spring'
? spring(
this.clock,
{ ...this.transitionState, velocity: this.velocity },
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...closingSpec.config, toValue: this.toValue }
)
: timing(
@@ -237,7 +226,7 @@ export default class Card extends React.Component<Props> {
]);
};
private translate = block([
private exec = block([
onChange(
this.isClosing,
cond(this.isClosing, set(this.nextIsVisible, FALSE))
@@ -276,18 +265,6 @@ export default class Card extends React.Component<Props> {
}
)
),
// Synchronize the translation with the animated value representing the progress
set(
this.props.current,
cond(
or(eq(this.layout.width, 0), eq(this.layout.height, 0)),
this.isVisible,
interpolate(this.position, {
inputRange: [0, this.distance],
outputRange: [1, 0],
})
)
),
cond(
eq(this.gestureState, GestureState.ACTIVE),
[
@@ -297,10 +274,22 @@ export default class Card extends React.Component<Props> {
set(this.isSwiping, TRUE),
set(this.isSwipeGesture, TRUE),
// Also update the drag offset to the last position
set(this.offset, this.position),
set(this.offset, this.props.current),
]),
// Update position with next offset + gesture distance
set(this.position, max(add(this.offset, this.gesture), 0)),
set(
this.props.current,
min(
max(
sub(
this.offset,
cond(this.distance, divide(this.gesture, this.distance), 1)
),
0
),
1
)
),
// Stop animations while we're dragging
stopClock(this.clock),
],
@@ -342,7 +331,6 @@ export default class Card extends React.Component<Props> {
),
]
),
this.position,
]);
private handleGestureEventHorizontal = Animated.event([
@@ -442,7 +430,7 @@ export default class Card extends React.Component<Props> {
return (
<StackGestureContext.Provider value={this.gestureRef}>
<View pointerEvents="box-none" {...rest}>
<Animated.Code exec={this.translate} />
<Animated.Code exec={this.exec} />
{overlayStyle ? (
<Animated.View
pointerEvents="none"

View File

@@ -160,7 +160,6 @@ export default class Stack extends React.Component<Props, State> {
descriptors,
navigation,
routes,
openingRoutes,
closingRoutes,
onOpenRoute,
onCloseRoute,
@@ -206,7 +205,6 @@ export default class Stack extends React.Component<Props, State> {
closing={closingRoutes.includes(route.key)}
onOpen={() => onOpenRoute({ route })}
onClose={() => onCloseRoute({ route })}
animateIn={openingRoutes.includes(route.key)}
gesturesEnabled={getGesturesEnabled({ route })}
onTransitionStart={({ closing }) => {
onTransitionStart &&

View File

@@ -94,9 +94,9 @@ class StackView extends React.Component<Props, State> {
const { routes } = this.props.navigation.state;
const isFirst = routes[0].key === route.key;
const isLast = routes[routes.length - 1].key === route.key;
if (isFirst || !isLast) {
if (isFirst) {
// The user shouldn't be able to close the first screen
return false;
}
@@ -109,29 +109,26 @@ class StackView extends React.Component<Props, State> {
private renderScene = ({ route }: { route: Route }) => {
const descriptor = this.state.descriptors[route.key];
if (!descriptor) {
return null;
}
const { navigation, getComponent } = descriptor;
const SceneComponent = getComponent();
const { screenProps } = this.props;
return (
<SceneView
screenProps={screenProps}
screenProps={this.props.screenProps}
navigation={navigation}
component={SceneComponent}
/>
);
};
private handleGoBack = ({ route }: { route: Route }) =>
private handleGoBack = ({ route }: { route: Route }) => {
// This event will trigger when a gesture ends
// We need to perform the transition before popping the route completely
this.props.navigation.dispatch(StackActions.pop({ key: route.key }));
};
private handleTransitionComplete = ({ route }: { route: Route }) => {
// When transition completes, we need to remove the item from the pushing/popping arrays
this.props.navigation.dispatch(
StackActions.completeTransition({ toChildKey: route.key })
);
@@ -142,6 +139,9 @@ class StackView extends React.Component<Props, State> {
};
private handleCloseRoute = ({ route }: { route: Route }) => {
// This event will trigger when the transition for closing the route ends
// In this case, we need to clean up any state tracking the route and pop it immediately
// @ts-ignore
this.setState(state => ({
routes: state.routes.filter(r => r.key !== route.key),

View File

@@ -1,105 +0,0 @@
import * as React from 'react';
import { ViewProps } from 'react-native';
import Animated from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
import StackGestureContext from '../../utils/StackGestureContext';
import { Layout } from '../../types';
type Props = ViewProps & {
gesture: Animated.Value<number>;
velocity: Animated.Value<number>;
gestureState: Animated.Value<number>;
layout: Layout;
direction: 'horizontal' | 'vertical';
gesturesEnabled: boolean;
gestureResponseDistance?: {
vertical?: number;
horizontal?: number;
};
children: React.ReactNode;
};
/**
* The distance of touch start from the edge of the screen where the gesture will be recognized
*/
const GESTURE_RESPONSE_DISTANCE_HORIZONTAL = 50;
const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
export default class Swipeable extends React.Component<Props> {
private handleGestureEventHorizontal = Animated.event([
{
nativeEvent: {
translationX: this.props.gesture,
velocityX: this.props.velocity,
state: this.props.gestureState,
},
},
]);
private handleGestureEventVertical = Animated.event([
{
nativeEvent: {
translationY: this.props.gesture,
velocityY: this.props.velocity,
state: this.props.gestureState,
},
},
]);
private gestureActivationCriteria() {
const { layout, direction, gestureResponseDistance } = this.props;
// Doesn't make sense for a response distance of 0, so this works fine
const distance =
direction === 'vertical'
? (gestureResponseDistance && gestureResponseDistance.vertical) ||
GESTURE_RESPONSE_DISTANCE_VERTICAL
: (gestureResponseDistance && gestureResponseDistance.horizontal) ||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
if (direction === 'vertical') {
return {
maxDeltaX: 15,
minOffsetY: 5,
hitSlop: { bottom: -layout.height + distance },
};
} else {
return {
minOffsetX: 5,
maxDeltaY: 20,
hitSlop: { right: -layout.width + distance },
};
}
}
private gestureRef: React.Ref<PanGestureHandler> = React.createRef();
render() {
const {
layout,
direction,
gesturesEnabled,
children,
...rest
} = this.props;
const handleGestureEvent =
direction === 'vertical'
? this.handleGestureEventVertical
: this.handleGestureEventHorizontal;
return (
<StackGestureContext.Provider value={this.gestureRef}>
<PanGestureHandler
ref={this.gestureRef}
enabled={layout.width !== 0 && gesturesEnabled}
onGestureEvent={handleGestureEvent}
onHandlerStateChange={handleGestureEvent}
{...this.gestureActivationCriteria()}
>
<Animated.View {...rest}>{children}</Animated.View>
</PanGestureHandler>
</StackGestureContext.Provider>
);
}
}