mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 09:13:43 +08:00
refactor: rewrite drawer layout with reanimated (#60)
This commit is contained in:
committed by
satyajit.happy
parent
e47dcdba5d
commit
4bdd06a71a
@@ -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';
|
||||
|
||||
|
||||
@@ -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 = {}) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
586
packages/drawer/src/views/Drawer.tsx
Normal file
586
packages/drawer/src/views/Drawer.tsx
Normal 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',
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user