refactor: rewrite drawer layout with reanimated (#60)

This commit is contained in:
Satyajit Sahoo
2019-05-01 23:58:06 +02:00
committed by satyajit.happy
parent e47dcdba5d
commit 4bdd06a71a
5 changed files with 679 additions and 158 deletions

View File

@@ -1,15 +1,15 @@
import * as DrawerAcions from './routers/DrawerActions';
/**
* Navigators
*/
/**
* Router
*/
import * as DrawerAcions from './routers/DrawerActions';
export {
default as createDrawerNavigator,
} from './navigators/createDrawerNavigator';
/**
* Router
*/
export { DrawerAcions };
export { default as DrawerRouter } from './routers/DrawerRouter';

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Dimensions, Platform, ScrollView } from 'react-native';
import { Dimensions, Platform, ScrollView, I18nManager } from 'react-native';
import { createNavigator } from '@react-navigation/core';
import { SafeAreaView } from '@react-navigation/native';
import DrawerRouter from '../routers/DrawerRouter';
@@ -35,14 +35,12 @@ const DefaultDrawerConfig = {
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: defaultContentComponent,
drawerPosition: 'left',
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
keyboardDismissMode: 'on-drag',
drawerBackgroundColor: 'white',
useNativeAnimations: true,
drawerType: 'front',
hideStatusBar: false,
statusBarAnimation: 'slide',
overlayColor: 'black',
};
const DrawerNavigator = (routeConfigs: object, config: any = {}) => {

View File

@@ -17,10 +17,6 @@ export type Navigation = {
key: string;
index: number;
routes: Route[];
openId: string;
closeId: string;
toggleId: string;
isDrawerIdle: boolean;
isDrawerOpen: boolean;
};
openDrawer: () => void;

View File

@@ -0,0 +1,586 @@
import * as React from 'react';
import {
StyleSheet,
ViewStyle,
LayoutChangeEvent,
I18nManager,
Platform,
Keyboard,
StatusBar,
} from 'react-native';
import {
PanGestureHandler,
TapGestureHandler,
State,
TapGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
const {
Clock,
Value,
onChange,
clockRunning,
startClock,
stopClock,
interpolate,
spring,
abs,
add,
and,
block,
call,
cond,
divide,
eq,
event,
greaterThan,
lessThan,
max,
min,
multiply,
neq,
or,
set,
sub,
} = Animated;
const TRUE = 1;
const FALSE = 0;
const NOOP = 0;
const UNSET = -1;
const PROGRESS_EPSILON = 0.05;
const DIRECTION_LEFT = 1;
const DIRECTION_RIGHT = -1;
const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
const SWIPE_DISTANCE_MINIMUM = 5;
const SPRING_CONFIG = {
damping: 30,
mass: 0.5,
stiffness: 150,
overshootClamping: true,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
};
type Binary = 0 | 1;
type Renderer = (props: { progress: Animated.Node<number> }) => React.ReactNode;
type Props = {
open: boolean;
onOpen: () => void;
onClose: () => void;
onGestureRef?: (ref: PanGestureHandler | null) => void;
locked: boolean;
drawerPosition: 'left' | 'right';
drawerType: 'front' | 'back' | 'slide';
keyboardDismissMode: 'none' | 'on-drag';
swipeEdgeWidth: number;
swipeDistanceThreshold?: number;
swipeVelocityThreshold: number;
hideStatusBar: boolean;
statusBarAnimation: 'slide' | 'none' | 'fade';
overlayStyle?: ViewStyle;
drawerStyle?: ViewStyle;
contentContainerStyle?: ViewStyle;
renderDrawerContent: Renderer;
renderSceneContent: Renderer;
};
export default class DrawerView extends React.PureComponent<Props> {
static defaultProps = {
locked: false,
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
drawerType: 'front',
swipeEdgeWidth: 32,
swipeVelocityThreshold: 500,
keyboardDismissMode: 'on-drag',
hideStatusBar: false,
statusBarAnimation: 'slide',
};
componentDidUpdate(prevProps: Props) {
const {
open,
drawerPosition,
drawerType,
swipeDistanceThreshold,
swipeVelocityThreshold,
hideStatusBar,
} = this.props;
if (
// If we're not in the middle of a transition, sync the drawer's open state
typeof this.pendingOpenValue !== 'boolean' ||
open !== this.pendingOpenValue
) {
this.toggleDrawer(open);
}
this.pendingOpenValue = undefined;
if (open !== prevProps.open && hideStatusBar) {
this.toggleStatusBar(open);
}
if (prevProps.drawerPosition !== drawerPosition) {
this.drawerPosition.setValue(
drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
);
}
if (prevProps.drawerType !== drawerType) {
this.isDrawerTypeFront.setValue(drawerType === 'front' ? TRUE : FALSE);
}
if (prevProps.swipeDistanceThreshold !== swipeDistanceThreshold) {
this.swipeDistanceThreshold.setValue(
swipeDistanceThreshold !== undefined
? swipeDistanceThreshold
: SWIPE_DISTANCE_THRESHOLD_DEFAULT
);
}
if (prevProps.swipeVelocityThreshold !== swipeVelocityThreshold) {
this.swipeVelocityThreshold.setValue(swipeVelocityThreshold);
}
}
componentWillUnmount() {
this.toggleStatusBar(false);
}
private clock = new Clock();
private isDrawerTypeFront = new Value<Binary>(
this.props.drawerType === 'front' ? TRUE : FALSE
);
private isOpen = new Value<Binary>(this.props.open ? TRUE : FALSE);
private nextIsOpen = new Value<Binary | -1>(UNSET);
private isSwiping = new Value<Binary>(FALSE);
private gestureState = new Value<number>(State.UNDETERMINED);
private touchX = new Value<number>(0);
private velocityX = new Value<number>(0);
private gestureX = new Value<number>(0);
private offsetX = new Value<number>(0);
private position = new Value<number>(0);
private containerWidth = new Value<number>(0);
private drawerWidth = new Value<number>(0);
private drawerOpacity = new Value<number>(0);
private drawerPosition = new Value<number>(
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
);
// Comment stolen from react-native-gesture-handler/DrawerLayout
//
// While closing the drawer when user starts gesture outside of its area (in greyed
// out part of the window), we want the drawer to follow only once finger reaches the
// edge of the drawer.
// E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
// dots. The touch gesture starts at '*' and moves left, touch path is indicated by
// an arrow pointing left
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// +---------------+ +---------------+ +---------------+ +---------------+
//
// For the above to work properly we define animated value that will keep start position
// of the gesture. Then we use that value to calculate how much we need to subtract from
// the dragX. If the gesture started on the greyed out area we take the distance from the
// edge of the drawer to the start position. Otherwise we don't subtract at all and the
// drawer be pulled back as soon as you start the pan.
//
// This is used only when drawerType is "front"
private touchDistanceFromDrawer = cond(
this.isDrawerTypeFront,
cond(
eq(this.drawerPosition, DIRECTION_LEFT),
max(
// Distance of touch start from left screen edge - Drawer width
sub(sub(this.touchX, this.gestureX), this.drawerWidth),
0
),
min(
multiply(
// Distance of drawer from left screen edge - Touch start point
sub(
sub(this.containerWidth, this.drawerWidth),
sub(this.touchX, this.gestureX)
),
DIRECTION_RIGHT
),
0
)
),
0
);
private swipeDistanceThreshold = new Value<number>(
this.props.swipeDistanceThreshold !== undefined
? this.props.swipeDistanceThreshold
: SWIPE_DISTANCE_THRESHOLD_DEFAULT
);
private swipeVelocityThreshold = new Value<number>(
this.props.swipeVelocityThreshold
);
private currentOpenValue: boolean = this.props.open;
private pendingOpenValue: boolean | undefined;
private isStatusBarHidden: boolean = false;
private transitionTo = (isOpen: number | Animated.Node<number>) => {
const toValue = new Value(0);
const frameTime = new Value(0);
const state = {
position: this.position,
time: new Value(0),
finished: new Value(FALSE),
};
return block([
cond(clockRunning(this.clock), NOOP, [
// Animation wasn't running before
// Set the initial values and start the clock
set(toValue, multiply(isOpen, this.drawerWidth, this.drawerPosition)),
set(frameTime, 0),
set(state.time, 0),
set(state.finished, FALSE),
set(this.isOpen, isOpen),
startClock(this.clock),
]),
spring(
this.clock,
{ ...state, velocity: this.velocityX },
{ ...SPRING_CONFIG, toValue }
),
cond(state.finished, [
// Reset gesture and velocity from previous gesture
set(this.touchX, 0),
set(this.gestureX, 0),
set(this.velocityX, 0),
set(this.offsetX, 0),
// When the animation finishes, stop the clock
stopClock(this.clock),
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
const open = Boolean(value);
if (open !== this.props.open) {
// Sync drawer's state after animation finished
// This shouldn't be necessary, but there seems to be an issue on iOS
this.toggleDrawer(this.props.open);
}
}),
]),
]);
};
private dragX = block([
onChange(
this.isOpen,
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
const open = Boolean(value);
this.currentOpenValue = open;
// Without this check, the drawer can go to an infinite update <-> animate loop for sync updates
if (open !== this.props.open) {
// If the mode changed, update state
if (open) {
this.props.onOpen();
} else {
this.props.onClose();
}
this.pendingOpenValue = open;
// Force componentDidUpdate to fire, whether user does a setState or not
// This allows us to detect when the user drops the update and revert back
// It's necessary to make sure that the state stays in sync
this.forceUpdate();
}
})
),
onChange(
this.nextIsOpen,
cond(neq(this.nextIsOpen, UNSET), [
// Stop any running animations
cond(clockRunning(this.clock), stopClock(this.clock)),
// Update the open value to trigger the transition
set(this.isOpen, this.nextIsOpen),
set(this.nextIsOpen, UNSET),
])
),
// This block must be after the this.isOpen listener since we check for current value
onChange(
this.isSwiping,
// Listen to updates for this value only when it changes
// Without `onChange`, this will fire even if the value didn't change
// We don't want to call the listeners if the value didn't change
call([this.isSwiping], ([value]: ReadonlyArray<Binary>) => {
const { keyboardDismissMode } = this.props;
if (value === TRUE) {
if (keyboardDismissMode === 'on-drag') {
Keyboard.dismiss();
}
this.toggleStatusBar(true);
} else {
this.toggleStatusBar(this.currentOpenValue);
}
})
),
cond(
eq(this.gestureState, State.ACTIVE),
[
cond(this.isSwiping, NOOP, [
// We weren't dragging before, set it to true
set(this.isSwiping, TRUE),
// Also update the drag offset to the last position
set(this.offsetX, this.position),
]),
// Update position with previous offset + gesture distance
set(
this.position,
add(this.offsetX, this.gestureX, this.touchDistanceFromDrawer)
),
// Stop animations while we're dragging
stopClock(this.clock),
],
[
set(this.isSwiping, FALSE),
set(this.touchX, 0),
this.transitionTo(
cond(
or(
and(
greaterThan(abs(this.gestureX), SWIPE_DISTANCE_MINIMUM),
greaterThan(abs(this.velocityX), this.swipeVelocityThreshold)
),
greaterThan(abs(this.gestureX), this.swipeDistanceThreshold)
),
cond(
eq(this.drawerPosition, DIRECTION_LEFT),
// If swiped to right, open the drawer, otherwise close it
greaterThan(
cond(eq(this.velocityX, 0), this.gestureX, this.velocityX),
0
),
// If swiped to left, open the drawer, otherwise close it
lessThan(
cond(eq(this.velocityX, 0), this.gestureX, this.velocityX),
0
)
),
this.isOpen
)
),
]
),
this.position,
]);
private translateX = cond(
eq(this.drawerPosition, DIRECTION_RIGHT),
min(max(multiply(this.drawerWidth, -1), this.dragX), 0),
max(min(this.drawerWidth, this.dragX), 0)
);
private progress = cond(
// Check if the drawer width is available to avoid division by zero
eq(this.drawerWidth, 0),
0,
abs(divide(this.translateX, this.drawerWidth))
);
private handleGestureEvent = event([
{
nativeEvent: {
x: this.touchX,
translationX: this.gestureX,
velocityX: this.velocityX,
state: this.gestureState,
},
},
]);
private handleTapStateChange = ({
nativeEvent,
}: TapGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE && !this.props.locked) {
this.toggleDrawer(false);
}
};
private handleContainerLayout = (e: LayoutChangeEvent) =>
this.containerWidth.setValue(e.nativeEvent.layout.width);
private handleDrawerLayout = (e: LayoutChangeEvent) => {
this.drawerWidth.setValue(e.nativeEvent.layout.width);
this.toggleDrawer(this.props.open);
// Until layout is available, drawer is hidden with opacity: 0 by default
// Show it in the next frame when layout is available
// If we don't delay it until the next frame, there's a visible flicker
requestAnimationFrame(() => this.drawerOpacity.setValue(1));
};
private toggleDrawer = (open: boolean) => {
this.nextIsOpen.setValue(open ? TRUE : FALSE);
// This value will also be set shortly after as changing this.nextIsOpen changes this.isOpen
// However, there's a race condition on Android, so we need to set a bit earlier
this.currentOpenValue = open;
};
private toggleStatusBar = (hidden: boolean) => {
const { hideStatusBar, statusBarAnimation } = this.props;
if (hideStatusBar && this.isStatusBarHidden !== hidden) {
this.isStatusBarHidden = hidden;
StatusBar.setHidden(hidden, statusBarAnimation);
}
};
render() {
const {
open,
locked,
drawerPosition,
drawerType,
swipeEdgeWidth,
contentContainerStyle,
drawerStyle,
overlayStyle,
onGestureRef,
renderDrawerContent,
renderSceneContent,
} = this.props;
const right = drawerPosition === 'right';
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
const drawerTranslateX =
drawerType === 'back'
? I18nManager.isRTL
? multiply(this.drawerWidth, DIRECTION_RIGHT)
: this.drawerWidth
: this.translateX;
const offset = I18nManager.isRTL ? '100%' : multiply(this.drawerWidth, -1);
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
// https://github.com/kmagiera/react-native-gesture-handler/issues/569
const hitSlop = right
? // Extend hitSlop to the side of the screen when drawer is closed
// This lets the user drag the drawer from the side of the screen
{ right: 0, width: open ? undefined : swipeEdgeWidth }
: { left: 0, width: open ? undefined : swipeEdgeWidth };
return (
<PanGestureHandler
ref={onGestureRef}
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureEvent}
hitSlop={hitSlop}
enabled={!locked}
>
<Animated.View
onLayout={this.handleContainerLayout}
style={styles.main}
>
<Animated.View
style={[
styles.content,
{
transform: [{ translateX: contentTranslateX }],
},
contentContainerStyle as any,
]}
>
{renderSceneContent({ progress: this.progress })}
<TapGestureHandler onHandlerStateChange={this.handleTapStateChange}>
<Animated.View
style={[
styles.overlay,
{
opacity: interpolate(this.progress, {
inputRange: [PROGRESS_EPSILON, 1],
outputRange: [0, 1],
}),
// We don't want the user to be able to press through the overlay when drawer is open
// One approach is to adjust the pointerEvents based on the progress
// But we can also send the overlay behind the screen, which works, and is much less code
zIndex: cond(
greaterThan(this.progress, PROGRESS_EPSILON),
0,
-1
),
},
overlayStyle,
]}
/>
</TapGestureHandler>
</Animated.View>
<Animated.View
accessibilityViewIsModal={open}
removeClippedSubviews={Platform.OS !== 'ios'}
onLayout={this.handleDrawerLayout}
style={[
styles.container,
right ? { right: offset } : { left: offset },
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
zIndex: drawerType === 'back' ? -1 : 0,
},
drawerStyle as any,
]}
>
{renderDrawerContent({ progress: this.progress })}
</Animated.View>
</Animated.View>
</PanGestureHandler>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
position: 'absolute',
top: 0,
bottom: 0,
width: '80%',
maxWidth: '100%',
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
content: {
flex: 1,
},
main: {
flex: 1,
overflow: 'hidden',
},
});

View File

@@ -1,30 +1,28 @@
import * as React from 'react';
import { Dimensions, StyleSheet, ViewStyle, Animated } from 'react-native';
import { Dimensions, StyleSheet, ViewStyle } from 'react-native';
import { SceneView } from '@react-navigation/core';
import DrawerLayout from 'react-native-gesture-handler/DrawerLayout';
import { ScreenContainer } from 'react-native-screens';
import * as DrawerActions from '../routers/DrawerActions';
import DrawerSidebar, { ContentComponentProps } from './DrawerSidebar';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import ResourceSavingScene from '../views/ResourceSavingScene';
import ResourceSavingScene from './ResourceSavingScene';
import Drawer from './Drawer';
import { Navigation } from '../types';
import { PanGestureHandler } from 'react-native-gesture-handler';
type DrawerOptions = {
drawerBackgroundColor?: string;
overlayColor?: string;
minSwipeDistance?: number;
drawerPosition: 'left' | 'right';
drawerType: 'front' | 'back' | 'slide';
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open';
keyboardDismissMode?: 'on-drag' | 'none';
drawerType: 'front' | 'back' | 'slide';
drawerWidth: number | (() => number);
statusBarAnimation: 'slide' | 'none' | 'fade';
useNativeAnimations?: boolean;
onDrawerClose?: () => void;
onDrawerOpen?: () => void;
onDrawerStateChanged?: () => void;
drawerContainerStyle?: ViewStyle;
contentContainerStyle?: ViewStyle;
edgeWidth: number;
hideStatusBar?: boolean;
@@ -82,90 +80,36 @@ export default class DrawerView extends React.PureComponent<Props, State> {
};
componentDidMount() {
Dimensions.addEventListener('change', this._updateWidth);
}
componentDidUpdate(prevProps: Props) {
const {
openId,
closeId,
toggleId,
isDrawerOpen,
} = this.props.navigation.state;
const {
openId: prevOpenId,
closeId: prevCloseId,
toggleId: prevToggleId,
} = prevProps.navigation.state;
let prevIds = [prevOpenId, prevCloseId, prevToggleId];
let changedIds = [openId, closeId, toggleId]
.filter(id => !prevIds.includes(id))
// @ts-ignore
.sort((a, b) => a > b);
changedIds.forEach(id => {
if (id === openId) {
this._drawer.openDrawer();
} else if (id === closeId) {
this._drawer.closeDrawer();
} else if (id === toggleId) {
if (isDrawerOpen) {
this._drawer.closeDrawer();
} else {
this._drawer.openDrawer();
}
}
});
Dimensions.addEventListener('change', this.updateWidth);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this._updateWidth);
Dimensions.removeEventListener('change', this.updateWidth);
}
_drawer: typeof DrawerLayout;
private drawerGestureRef = React.createRef<PanGestureHandler>();
drawerGestureRef = React.createRef();
private handleDrawerOpen = () => {
const { navigation } = this.props;
_handleDrawerStateChange = (newState: string, willShow: boolean) => {
if (newState === 'Idle') {
if (!this.props.navigation.state.isDrawerIdle) {
this.props.navigation.dispatch({
type: DrawerActions.MARK_DRAWER_IDLE,
key: this.props.navigation.state.key,
});
}
} else if (newState === 'Settling') {
this.props.navigation.dispatch({
type: DrawerActions.MARK_DRAWER_SETTLING,
key: this.props.navigation.state.key,
willShow,
});
} else {
if (this.props.navigation.state.isDrawerIdle) {
this.props.navigation.dispatch({
type: DrawerActions.MARK_DRAWER_ACTIVE,
key: this.props.navigation.state.key,
});
}
}
navigation.dispatch(
DrawerActions.openDrawer({
key: navigation.state.key,
})
);
};
_handleDrawerOpen = () => {
this.props.navigation.dispatch({
type: DrawerActions.DRAWER_OPENED,
key: this.props.navigation.state.key,
});
private handleDrawerClose = () => {
const { navigation } = this.props;
navigation.dispatch(
DrawerActions.closeDrawer({
key: navigation.state.key,
})
);
};
_handleDrawerClose = () => {
this.props.navigation.dispatch({
type: DrawerActions.DRAWER_CLOSED,
key: this.props.navigation.state.key,
});
};
_updateWidth = () => {
private updateWidth = () => {
const drawerWidth =
typeof this.props.navigationConfig.drawerWidth === 'function'
? this.props.navigationConfig.drawerWidth()
@@ -176,27 +120,23 @@ export default class DrawerView extends React.PureComponent<Props, State> {
}
};
_renderNavigationView = (
drawerOpenProgress: Animated.AnimatedInterpolation
) => {
private renderNavigationView = ({ progress }: any) => {
return (
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<DrawerSidebar
screenProps={this.props.screenProps}
drawerOpenProgress={drawerOpenProgress}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
contentComponent={this.props.navigationConfig.contentComponent}
contentOptions={this.props.navigationConfig.contentOptions}
drawerPosition={this.props.navigationConfig.drawerPosition}
style={this.props.navigationConfig.style}
{...this.props.navigationConfig}
/>
</DrawerGestureContext.Provider>
<DrawerSidebar
screenProps={this.props.screenProps}
drawerOpenProgress={progress}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
contentComponent={this.props.navigationConfig.contentComponent}
contentOptions={this.props.navigationConfig.contentOptions}
drawerPosition={this.props.navigationConfig.drawerPosition}
style={this.props.navigationConfig.style}
{...this.props.navigationConfig}
/>
);
};
_renderContent = () => {
private renderContent = () => {
let { lazy, navigation } = this.props;
let { loaded } = this.state;
let { routes } = navigation.state;
@@ -214,7 +154,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
);
} else {
return (
<ScreenContainer style={styles.pages}>
<ScreenContainer style={styles.content}>
{routes.map((route, index) => {
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
@@ -246,67 +186,68 @@ export default class DrawerView extends React.PureComponent<Props, State> {
}
};
_setDrawerGestureRef = (ref: any) => {
private setDrawerGestureRef = (ref: PanGestureHandler | null) => {
// @ts-ignore
this.drawerGestureRef.current = ref;
};
render() {
const { navigation, screenProps } = this.props;
const { navigation } = this.props;
const {
drawerType,
drawerBackgroundColor,
overlayColor,
contentContainerStyle,
edgeWidth,
minSwipeDistance,
hideStatusBar,
statusBarAnimation,
} = this.props.navigationConfig;
const activeKey = navigation.state.routes[navigation.state.index].key;
const { drawerLockMode } = this.props.descriptors[activeKey].options;
const isOpen =
drawerLockMode === 'locked-closed'
? false
: drawerLockMode === 'locked-open'
? true
: this.props.navigation.state.isDrawerOpen;
return (
<DrawerLayout
ref={(c: any) => {
this._drawer = c;
}}
onGestureRef={this._setDrawerGestureRef}
drawerLockMode={
drawerLockMode ||
(typeof screenProps === 'object' &&
screenProps != null &&
// @ts-ignore
screenProps.drawerLockMode) ||
this.props.navigationConfig.drawerLockMode
}
drawerBackgroundColor={
this.props.navigationConfig.drawerBackgroundColor
}
keyboardDismissMode={this.props.navigationConfig.keyboardDismissMode}
drawerWidth={this.state.drawerWidth}
onDrawerOpen={this._handleDrawerOpen}
onDrawerClose={this._handleDrawerClose}
onDrawerStateChanged={this._handleDrawerStateChange}
useNativeAnimations={this.props.navigationConfig.useNativeAnimations}
renderNavigationView={this._renderNavigationView}
drawerPosition={
this.props.navigationConfig.drawerPosition === 'right'
? DrawerLayout.positions.Right
: DrawerLayout.positions.Left
}
/* props specific to react-native-gesture-handler/DrawerLayout */
drawerType={this.props.navigationConfig.drawerType}
edgeWidth={this.props.navigationConfig.edgeWidth}
hideStatusBar={this.props.navigationConfig.hideStatusBar}
statusBarAnimation={this.props.navigationConfig.statusBarAnimation}
minSwipeDistance={this.props.navigationConfig.minSwipeDistance}
overlayColor={this.props.navigationConfig.overlayColor}
drawerContainerStyle={this.props.navigationConfig.drawerContainerStyle}
contentContainerStyle={
this.props.navigationConfig.contentContainerStyle
}
>
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
{this._renderContent()}
</DrawerGestureContext.Provider>
</DrawerLayout>
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={isOpen}
locked={
drawerLockMode === 'locked-open' ||
drawerLockMode === 'locked-closed'
}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onGestureRef={this.setDrawerGestureRef}
drawerType={drawerType}
drawerPosition={this.props.navigationConfig.drawerPosition}
contentContainerStyle={contentContainerStyle}
drawerStyle={{
backgroundColor: drawerBackgroundColor || 'white',
width: this.state.drawerWidth,
}}
overlayStyle={{
backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)',
}}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
);
}
}
const styles = StyleSheet.create({
pages: {
content: {
flex: 1,
},
});