mirror of
https://github.com/zhigang1992/react-native-bottom-sheet.git
synced 2026-04-29 04:35:38 +08:00
chore: improve content/scrollable panning handling (#23)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
2
src/components/draggableView/types.d.ts
vendored
2
src/components/draggableView/types.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
@@ -53,8 +53,9 @@ const BottomSheetFlatListComponent = forwardRef(
|
||||
// render
|
||||
return (
|
||||
<DraggableView
|
||||
style={styles.container}
|
||||
nativeGestureRef={nativeGestureRef}
|
||||
gestureType="CONTENT"
|
||||
style={styles.container}
|
||||
>
|
||||
<NativeViewGestureHandler
|
||||
ref={nativeGestureRef}
|
||||
|
||||
@@ -57,8 +57,9 @@ const BottomSheetScrollViewComponent = forwardRef(
|
||||
|
||||
return (
|
||||
<DraggableView
|
||||
style={styles.container}
|
||||
nativeGestureRef={nativeGestureRef}
|
||||
gestureType="CONTENT"
|
||||
style={styles.container}
|
||||
>
|
||||
<NativeViewGestureHandler
|
||||
ref={nativeGestureRef}
|
||||
|
||||
@@ -52,8 +52,9 @@ const BottomSheetSectionListComponent = forwardRef(
|
||||
// render
|
||||
return (
|
||||
<DraggableView
|
||||
style={styles.container}
|
||||
nativeGestureRef={nativeGestureRef}
|
||||
gestureType="CONTENT"
|
||||
style={styles.container}
|
||||
>
|
||||
<NativeViewGestureHandler
|
||||
ref={nativeGestureRef}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user