import React, { Component } from 'react'; import { Animated, StyleSheet, View, Dimensions, NativeSyntheticEvent, NativeScrollEvent, } from 'react-native'; import { PanGestureHandler, NativeViewGestureHandler, State, TapGestureHandler, PanGestureHandlerStateChangeEvent, PanGestureHandlerGestureEvent, } from 'react-native-gesture-handler'; import { LoremIpsum } from '../../common'; import { USE_NATIVE_DRIVER } from '../../config'; type BottomSheetState = { lastSnap: number; }; const HEADER_HEIGHT = 50; const windowHeight = Dimensions.get('window').height; const SNAP_POINTS_FROM_TOP = [50, windowHeight * 0.4, windowHeight * 0.8]; export class BottomSheet extends Component< Record, BottomSheetState > { private masterdrawer = React.createRef(); private drawer = React.createRef(); private drawerheader = React.createRef(); private scroll = React.createRef(); private lastScrollYValue: number; private lastScrollY: Animated.Value; private onRegisterLastScroll: ( event: NativeSyntheticEvent ) => void; private dragY: Animated.Value; private onGestureEvent: (event: PanGestureHandlerGestureEvent) => void; private reverseLastScrollY: Animated.AnimatedMultiplication; private translateYOffset: Animated.Value; private translateY: Animated.AnimatedInterpolation; constructor(props: Record) { super(props); const START = SNAP_POINTS_FROM_TOP[0]; const END = SNAP_POINTS_FROM_TOP[SNAP_POINTS_FROM_TOP.length - 1]; this.state = { lastSnap: END, }; this.lastScrollYValue = 0; this.lastScrollY = new Animated.Value(0); this.onRegisterLastScroll = Animated.event( [{ nativeEvent: { contentOffset: { y: this.lastScrollY } } }], { useNativeDriver: USE_NATIVE_DRIVER } ); this.lastScrollY.addListener(({ value }) => { this.lastScrollYValue = value; }); this.dragY = new Animated.Value(0); this.onGestureEvent = Animated.event( [{ nativeEvent: { translationY: this.dragY } }], { useNativeDriver: USE_NATIVE_DRIVER } ); this.reverseLastScrollY = Animated.multiply( new Animated.Value(-1), this.lastScrollY ); this.translateYOffset = new Animated.Value(END); this.translateY = Animated.add( this.translateYOffset, Animated.add(this.dragY, this.reverseLastScrollY) ).interpolate({ inputRange: [START, END], outputRange: [START, END], extrapolate: 'clamp', }); } private onHeaderHandlerStateChange = ({ nativeEvent, }: PanGestureHandlerStateChangeEvent) => { if (nativeEvent.oldState === State.BEGAN) { this.lastScrollY.setValue(0); } this.onHandlerStateChange({ nativeEvent }); }; private onHandlerStateChange = ({ nativeEvent, }: PanGestureHandlerStateChangeEvent) => { if (nativeEvent.oldState === State.ACTIVE) { let { velocityY, translationY } = nativeEvent; translationY -= this.lastScrollYValue; const dragToss = 0.05; const endOffsetY = this.state.lastSnap + translationY + dragToss * velocityY; let destSnapPoint = SNAP_POINTS_FROM_TOP[0]; for (const snapPoint of SNAP_POINTS_FROM_TOP) { const distFromSnap = Math.abs(snapPoint - endOffsetY); if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) { destSnapPoint = snapPoint; } } this.setState({ lastSnap: destSnapPoint }); this.translateYOffset.extractOffset(); this.translateYOffset.setValue(translationY); this.translateYOffset.flattenOffset(); this.dragY.setValue(0); Animated.spring(this.translateYOffset, { velocity: velocityY, tension: 68, friction: 12, toValue: destSnapPoint, useNativeDriver: USE_NATIVE_DRIVER, }).start(); } }; render() { return ( ); } } export default class Example extends Component { render() { return ( ); } } const styles = StyleSheet.create({ container: { flex: 1, }, header: { height: HEADER_HEIGHT, backgroundColor: 'red', }, });