chore: improve content/scrollable panning handling (#23)

This commit is contained in:
Mo Gorhom
2020-08-23 20:13:57 +02:00
committed by GitHub
parent 14d2f0fb5e
commit 9185a5fd1f
10 changed files with 135 additions and 66 deletions

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useRef } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { View, StyleSheet } from 'react-native';
import { useHeaderHeight } from '@react-navigation/stack';
import Animated, {
interpolate,
@@ -7,10 +7,9 @@ import Animated, {
Extrapolate,
} from 'react-native-reanimated';
import { useValue } from 'react-native-redash';
import BottomSheet from '@gorhom/bottom-sheet';
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import Handle from '../components/handle';
import Button from '../components/button';
import ContactList from '../components/contactList';
import { ReText } from 'react-native-redash';
const BasicExample = () => {
@@ -49,13 +48,13 @@ const BasicExample = () => {
}, []);
// renders
const renderHeader = useCallback(() => {
return (
<View style={styles.headerContainer}>
<Text style={styles.title}>Basic Screen</Text>
</View>
);
}, []);
// const renderHeader = useCallback(() => {
// return (
// <View style={styles.headerContainer}>
// <Text style={styles.title}>Basic Screen</Text>
// </View>
// );
// }, []);
return (
<View style={styles.container}>
@@ -153,7 +152,18 @@ const BasicExample = () => {
style={styles.buttonContainer}
onPress={() => handleSnapPress(1)}
/> */}
<ContactList type="ScrollView" header={renderHeader} />
{/* <ContactList type="ScrollView" header={renderHeader} /> */}
<View style={{ height: 100, backgroundColor: 'blue' }} />
<BottomSheetFlatList
contentContainerStyle={{ flexGrow: 1, backgroundColor: '#fff' }}
data={[0, 1, 2, 3, 4, 5, 6, 7, 8]}
renderItem={() => (
<View
style={{ backgroundColor: 'red', height: 100, marginBottom: 20 }}
/>
)}
/>
<View style={{ height: 100, backgroundColor: 'green' }} />
</BottomSheet>
</View>
);

View File

@@ -48,6 +48,7 @@ import {
import {
DEFAULT_ANIMATION_EASING,
DEFAULT_ANIMATION_DURATION,
GESTURE,
} from '../../constants';
import type { ScrollableRef, BottomSheetMethods } from '../../types';
import type { BottomSheetProps } from './types';
@@ -190,7 +191,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
//#endregion
//#region animation
const { position, currentPosition } = useTransition({
const { position, currentPosition, currentGesture } = useTransition({
animationDuration,
animationEasing,
contentPanGestureState,
@@ -299,6 +300,9 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
const internalContextVariables = useMemo(
() => ({
rootTapGestureRef,
handlePanGestureState,
handlePanGestureTranslationY,
handlePanGestureVelocityY,
contentPanGestureState,
contentPanGestureTranslationY,
contentPanGestureVelocityY,
@@ -366,7 +370,13 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
useCode(
() =>
cond(
and(eq(tapGestureState, State.FAILED), neq(position, 0)),
and(
eq(tapGestureState, State.FAILED),
eq(currentGesture, GESTURE.CONTENT),
eq(contentPanGestureState, State.END),
eq(handlePanGestureState, State.END),
neq(position, 0)
),
call([], () => {
scrollToTop();
})
@@ -423,6 +433,10 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
/>
)}
{/* <Animated.View pointerEvents="none" style={styles.debug}>
<ReText
style={styles.debugText}
text={concat('currentGesture: ', currentGesture)}
/>
<ReText
style={styles.debugText}
text={concat('tapState: ', tapGestureState)}
@@ -431,6 +445,10 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
style={styles.debugText}
text={concat('contentState: ', contentPanGestureState)}
/>
<ReText
style={styles.debugText}
text={concat('handleState: ', handlePanGestureState)}
/>
<ReText
style={styles.debugText}
text={concat(

View File

@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import Animated, {
eq,
set,
@@ -21,6 +22,7 @@ import Animated, {
import { State } from 'react-native-gesture-handler';
import { useClock, useValue, snapPoint } from 'react-native-redash';
import type { BottomSheetAnimationConfigs } from './types';
import { GESTURE } from '../../constants';
interface TransitionProps extends Required<BottomSheetAnimationConfigs> {
contentPanGestureState: Animated.Value<State>;
@@ -37,12 +39,6 @@ interface TransitionProps extends Required<BottomSheetAnimationConfigs> {
initialPosition: number;
}
enum GESTURE {
UNDETERMINED = 0,
CONTENT,
HANDLE,
}
export const useTransition = ({
animationDuration,
animationEasing,
@@ -66,26 +62,36 @@ export const useTransition = ({
const shouldAnimate = useValue(0);
const clock = useClock();
const config = {
toValue: useValue(0),
duration: animationDuration,
easing: animationEasing,
};
const config = useMemo(
() => ({
toValue: new Animated.Value(0),
duration: animationDuration,
easing: animationEasing,
}),
[animationEasing, animationDuration]
);
const animationState = {
finished: useValue(0),
position: useValue(initialPosition),
frameTime: useValue(0),
time: useValue(0),
};
const animationState = useMemo(
() => ({
finished: new Animated.Value(0),
position: new Animated.Value(initialPosition),
frameTime: new Animated.Value(0),
time: new Animated.Value(0),
}),
[initialPosition]
);
const finishTiming = [
set(shouldAnimate, 0),
set(currentPosition, config.toValue),
set(animationState.frameTime, 0),
set(animationState.time, 0),
stopClock(clock),
];
const finishTiming = useMemo(
() => [
set(shouldAnimate, 0),
set(currentPosition, config.toValue),
set(animationState.frameTime, 0),
set(animationState.time, 0),
stopClock(clock),
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const translateY = cond(
eq(currentGesture, GESTURE.CONTENT),
@@ -148,22 +154,28 @@ export const useTransition = ({
/**
* Gesture ended node.
*/
cond(
or(
eq(contentPanGestureState, State.END),
eq(handlePanGestureState, State.END)
),
[
set(contentPanGestureState, State.UNDETERMINED),
set(handlePanGestureState, State.UNDETERMINED),
set(
config.toValue,
snapPoint(add(currentPosition, translateY), velocityY, snapPoints)
onChange(
add(contentPanGestureState, handlePanGestureState),
cond(
or(
and(
eq(currentGesture, GESTURE.CONTENT),
eq(contentPanGestureState, State.END)
),
and(
neq(currentGesture, GESTURE.CONTENT),
eq(handlePanGestureState, State.END)
)
),
set(shouldAnimate, 1),
]
[
set(
config.toValue,
snapPoint(add(currentPosition, translateY), velocityY, snapPoints)
),
set(shouldAnimate, 1),
]
)
),
/**
* Manual snapping node.
*/
@@ -198,5 +210,6 @@ export const useTransition = ({
return {
position,
currentPosition,
currentGesture,
};
};

View File

@@ -7,9 +7,10 @@ import type { BottomSheetDraggableViewProps } from './types';
import { styles } from './styles';
const BottomSheetDraggableViewComponent = ({
children,
style,
nativeGestureRef,
gestureType = 'HANDLE',
style,
children,
...rest
}: BottomSheetDraggableViewProps) => {
// refs
@@ -18,6 +19,9 @@ const BottomSheetDraggableViewComponent = ({
// hooks
const {
rootTapGestureRef,
handlePanGestureState,
handlePanGestureTranslationY,
handlePanGestureVelocityY,
contentPanGestureState,
contentPanGestureTranslationY,
contentPanGestureVelocityY,
@@ -41,17 +45,27 @@ const BottomSheetDraggableViewComponent = ({
// callbacks
const handleGestureEvent = useMemo(
() =>
event([
{
nativeEvent: {
state: contentPanGestureState,
translationY: contentPanGestureTranslationY,
velocityY: contentPanGestureVelocityY,
},
},
]),
gestureType === 'CONTENT'
? event([
{
nativeEvent: {
state: contentPanGestureState,
translationY: contentPanGestureTranslationY,
velocityY: contentPanGestureVelocityY,
},
},
])
: event([
{
nativeEvent: {
state: handlePanGestureState,
translationY: handlePanGestureTranslationY,
velocityY: handlePanGestureVelocityY,
},
},
]),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[gestureType]
);
// effects

View File

@@ -1,7 +1,9 @@
import type { ViewProps as RNViewProps } from 'react-native';
import type { NativeViewGestureHandler } from 'react-native-gesture-handler';
import type { GESTURE } from '../../constants';
export type BottomSheetDraggableViewProps = RNViewProps & {
children: React.ReactNode[] | React.ReactNode;
gestureType?: keyof typeof GESTURE;
nativeGestureRef?: Ref<NativeViewGestureHandler> | null;
};

View File

@@ -53,8 +53,9 @@ const BottomSheetFlatListComponent = forwardRef(
// render
return (
<DraggableView
style={styles.container}
nativeGestureRef={nativeGestureRef}
gestureType="CONTENT"
style={styles.container}
>
<NativeViewGestureHandler
ref={nativeGestureRef}

View File

@@ -57,8 +57,9 @@ const BottomSheetScrollViewComponent = forwardRef(
return (
<DraggableView
style={styles.container}
nativeGestureRef={nativeGestureRef}
gestureType="CONTENT"
style={styles.container}
>
<NativeViewGestureHandler
ref={nativeGestureRef}

View File

@@ -52,8 +52,9 @@ const BottomSheetSectionListComponent = forwardRef(
// render
return (
<DraggableView
style={styles.container}
nativeGestureRef={nativeGestureRef}
gestureType="CONTENT"
style={styles.container}
>
<NativeViewGestureHandler
ref={nativeGestureRef}

View File

@@ -10,3 +10,9 @@ export const DEFAULT_ANIMATION_EASING: Animated.EasingFunction = Easing.out(
Easing.back(0.75)
);
export const DEFAULT_ANIMATION_DURATION = 500;
export enum GESTURE {
UNDETERMINED = 0,
CONTENT,
HANDLE,
}

View File

@@ -8,6 +8,9 @@ export type BottomSheetInternalContextType = {
contentPanGestureState: Animated.Value<State>;
contentPanGestureTranslationY: Animated.Value<number>;
contentPanGestureVelocityY: Animated.Value<number>;
handlePanGestureState: Animated.Value<State>;
handlePanGestureTranslationY: Animated.Value<number>;
handlePanGestureVelocityY: Animated.Value<number>;
scrollableContentOffsetY: Animated.Value<number>;
decelerationRate: Animated.Node<number>;
setScrollableRef: (ref: ScrollableRef) => void;