mirror of
https://github.com/zhigang1992/react-native-bottom-sheet.git
synced 2026-04-28 12:15:39 +08:00
feat: allow modify animation configs (#333)
* chore: modify animation hooks * refactor: updated bottom sheet animation handling * refactor: updated bottom sheet modal * chore: updated examples
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { BottomSheetModal } from '@gorhom/bottom-sheet';
|
||||
import Button from '../../components/button';
|
||||
@@ -6,8 +6,12 @@ import ContactListContainer from '../../components/contactListContainer';
|
||||
import withModalProvider from '../withModalProvider';
|
||||
|
||||
const SimpleExample = () => {
|
||||
// refs
|
||||
const bottomSheetRef = useRef<BottomSheetModal>(null);
|
||||
|
||||
// variables
|
||||
const snapPoints = useMemo(() => ['25%', '50%'], []);
|
||||
|
||||
// callbacks
|
||||
const handleChange = useCallback((index: number) => {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -55,7 +59,7 @@ const SimpleExample = () => {
|
||||
/>
|
||||
<BottomSheetModal
|
||||
ref={bottomSheetRef}
|
||||
snapPoints={['25%', '50%']}
|
||||
snapPoints={snapPoints}
|
||||
animationDuration={250}
|
||||
onDismiss={handleDismiss}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -22,7 +22,6 @@ import Animated, {
|
||||
Extrapolate,
|
||||
runOnUI,
|
||||
useWorkletCallback,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import { State } from 'react-native-gesture-handler';
|
||||
import {
|
||||
@@ -42,7 +41,13 @@ import BottomSheetHandleContainer from '../bottomSheetHandleContainer';
|
||||
import BottomSheetBackgroundContainer from '../bottomSheetBackgroundContainer';
|
||||
import BottomSheetDraggableView from '../bottomSheetDraggableView';
|
||||
// import BottomSheetDebugView from '../bottomSheetDebugView';
|
||||
import { GESTURE, ANIMATION_STATE, WINDOW_HEIGHT } from '../../constants';
|
||||
import {
|
||||
GESTURE,
|
||||
ANIMATION_STATE,
|
||||
WINDOW_HEIGHT,
|
||||
ANIMATION_METHOD,
|
||||
} from '../../constants';
|
||||
import { animate } from '../../utilities';
|
||||
import {
|
||||
DEFAULT_ANIMATION_EASING,
|
||||
DEFAULT_ANIMATION_DURATION,
|
||||
@@ -74,8 +79,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
|
||||
//#region extract props
|
||||
const {
|
||||
// animations configurations
|
||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
animationEasing = DEFAULT_ANIMATION_EASING,
|
||||
animationDuration: _providedAnimationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
animationEasing: _providedAnimationEasing = DEFAULT_ANIMATION_EASING,
|
||||
animationConfigs: _providedAnimationConfigs,
|
||||
|
||||
// configurations
|
||||
@@ -289,39 +294,52 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
|
||||
animationState.value = ANIMATION_STATE.STOPPED;
|
||||
}, [animatedIndex, animationState, handleOnChange, refreshUIElements]);
|
||||
const animateToPoint = useWorkletCallback(
|
||||
(point: number, velocity: number = 0) => {
|
||||
(
|
||||
point: number,
|
||||
velocity: number = 0,
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => {
|
||||
animationState.value = ANIMATION_STATE.RUNNING;
|
||||
runOnJS(handleOnAnimate)(point);
|
||||
|
||||
if (_providedAnimationConfigs) {
|
||||
/**
|
||||
* force animation configs from parameters, if provided
|
||||
*/
|
||||
if (animationDuration !== undefined) {
|
||||
animatedPosition.value = animate(ANIMATION_METHOD.TIMING, {
|
||||
duration: animationDuration,
|
||||
easing: animationEasing
|
||||
? animationEasing
|
||||
: DEFAULT_ANIMATION_EASING,
|
||||
})(point, velocity, animateToPointCompleted);
|
||||
} else if (_providedAnimationConfigs) {
|
||||
/**
|
||||
* use animationConfigs callback, if provided
|
||||
*/
|
||||
animatedPosition.value = _providedAnimationConfigs(
|
||||
point,
|
||||
velocity,
|
||||
animateToPointCompleted
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
/**
|
||||
* @deprecated this will be removed in next major release.
|
||||
*/
|
||||
animatedPosition.value = animate(ANIMATION_METHOD.TIMING, {
|
||||
duration: _providedAnimationDuration,
|
||||
easing: _providedAnimationEasing,
|
||||
})(point, velocity, animateToPointCompleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will be removed in next major release.
|
||||
*/
|
||||
animatedPosition.value = withTiming(
|
||||
point,
|
||||
{
|
||||
duration: animationDuration,
|
||||
easing: animationEasing,
|
||||
},
|
||||
animateToPointCompleted
|
||||
);
|
||||
},
|
||||
[
|
||||
_providedAnimationConfigs,
|
||||
animationState,
|
||||
animatedPosition,
|
||||
animationDuration,
|
||||
animationEasing,
|
||||
animateToPointCompleted,
|
||||
handleOnAnimate,
|
||||
_providedAnimationConfigs,
|
||||
_providedAnimationDuration,
|
||||
_providedAnimationEasing,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -368,7 +386,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
|
||||
|
||||
//#region public methods
|
||||
const handleSnapTo = useCallback(
|
||||
(index: number) => {
|
||||
(index: number, ...args) => {
|
||||
invariant(
|
||||
index >= -1 && index <= snapPoints.length - 1,
|
||||
`'index' was provided but out of the provided snap points range! expected value to be between -1, ${
|
||||
@@ -379,31 +397,40 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
|
||||
return;
|
||||
}
|
||||
const newSnapPoint = snapPoints[index];
|
||||
runOnUI(animateToPoint)(newSnapPoint);
|
||||
runOnUI(animateToPoint)(newSnapPoint, 0, ...args);
|
||||
},
|
||||
[animateToPoint, snapPoints]
|
||||
);
|
||||
const handleClose = useCallback(
|
||||
(...args) => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
isClosing.current = true;
|
||||
runOnUI(animateToPoint)(safeContainerHeight, 0, ...args);
|
||||
},
|
||||
[animateToPoint, safeContainerHeight]
|
||||
);
|
||||
const handleExpand = useCallback(
|
||||
(...args) => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
const newSnapPoint = snapPoints[snapPoints.length - 1];
|
||||
runOnUI(animateToPoint)(newSnapPoint, 0, ...args);
|
||||
},
|
||||
[animateToPoint, snapPoints]
|
||||
);
|
||||
const handleCollapse = useCallback(
|
||||
(...args) => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
const newSnapPoint = snapPoints[0];
|
||||
runOnUI(animateToPoint)(newSnapPoint, 0, ...args);
|
||||
},
|
||||
[animateToPoint, snapPoints]
|
||||
);
|
||||
const handleClose = useCallback(() => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
isClosing.current = true;
|
||||
runOnUI(animateToPoint)(safeContainerHeight);
|
||||
}, [animateToPoint, safeContainerHeight]);
|
||||
const handleExpand = useCallback(() => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
const newSnapPoint = snapPoints[snapPoints.length - 1];
|
||||
runOnUI(animateToPoint)(newSnapPoint);
|
||||
}, [animateToPoint, snapPoints]);
|
||||
const handleCollapse = useCallback(() => {
|
||||
if (isClosing.current) {
|
||||
return;
|
||||
}
|
||||
const newSnapPoint = snapPoints[0];
|
||||
runOnUI(animateToPoint)(newSnapPoint);
|
||||
}, [animateToPoint, snapPoints]);
|
||||
useImperativeHandle(ref, () => ({
|
||||
snapTo: handleSnapTo,
|
||||
expand: handleExpand,
|
||||
@@ -512,7 +539,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
|
||||
isLayoutCalculated &&
|
||||
didMountOnAnimate.current === false &&
|
||||
isClosing.current === false &&
|
||||
snapPoints[_providedIndex] !== safeContainerHeight
|
||||
snapPoints[_providedIndex] !== safeContainerHeight &&
|
||||
_providedIndex !== -1
|
||||
) {
|
||||
const newSnapPoint = snapPoints[_providedIndex];
|
||||
requestAnimationFrame(() => runOnUI(animateToPoint)(newSnapPoint));
|
||||
|
||||
@@ -87,7 +87,8 @@ const BottomSheetModalComponent = forwardRef<
|
||||
forcedDismissed.current = false;
|
||||
}, []);
|
||||
const adjustIndex = useCallback(
|
||||
(_index: number) => (dismissOnPanDown ? _index - 1 : _index),
|
||||
(_index: number, internal = true) =>
|
||||
dismissOnPanDown ? (internal ? _index - 1 : _index + 1) : _index,
|
||||
[dismissOnPanDown]
|
||||
);
|
||||
const unmount = useCallback(() => {
|
||||
@@ -113,34 +114,40 @@ const BottomSheetModalComponent = forwardRef<
|
||||
//#endregion
|
||||
|
||||
//#region bottom sheet methods
|
||||
const handleSnapTo = useCallback(() => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
const handleSnapTo = useCallback(
|
||||
(_index: number, ...args) => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
bottomSheetRef.current?.snapTo(adjustIndex(currentIndexRef.current));
|
||||
}, [adjustIndex]);
|
||||
const handleExpand = useCallback(() => {
|
||||
bottomSheetRef.current?.snapTo(adjustIndex(_index, false), ...args);
|
||||
},
|
||||
[adjustIndex]
|
||||
);
|
||||
const handleExpand = useCallback((...args) => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
bottomSheetRef.current?.expand();
|
||||
bottomSheetRef.current?.expand(...args);
|
||||
}, []);
|
||||
const handleCollapse = useCallback(() => {
|
||||
const handleCollapse = useCallback(
|
||||
(...args) => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
if (dismissOnPanDown) {
|
||||
bottomSheetRef.current?.snapTo(1, ...args);
|
||||
} else {
|
||||
bottomSheetRef.current?.collapse(...args);
|
||||
}
|
||||
},
|
||||
[dismissOnPanDown]
|
||||
);
|
||||
const handleClose = useCallback((...args) => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
if (dismissOnPanDown) {
|
||||
bottomSheetRef.current?.snapTo(1);
|
||||
} else {
|
||||
bottomSheetRef.current?.collapse();
|
||||
}
|
||||
}, [dismissOnPanDown]);
|
||||
const handleClose = useCallback(() => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
}
|
||||
bottomSheetRef.current?.close();
|
||||
bottomSheetRef.current?.close(...args);
|
||||
}, []);
|
||||
//#endregion
|
||||
|
||||
@@ -151,22 +158,25 @@ const BottomSheetModalComponent = forwardRef<
|
||||
mountSheet(key, ref, stackBehavior);
|
||||
});
|
||||
}, [key, stackBehavior, ref, mountSheet]);
|
||||
const handleDismiss = useCallback(() => {
|
||||
/**
|
||||
* if modal is already been dismiss, we exit the method.
|
||||
*/
|
||||
if (currentIndexRef.current === -1 && minimized.current === false) {
|
||||
return;
|
||||
}
|
||||
const handleDismiss = useCallback(
|
||||
(...args) => {
|
||||
/**
|
||||
* if modal is already been dismiss, we exit the method.
|
||||
*/
|
||||
if (currentIndexRef.current === -1 && minimized.current === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (minimized.current) {
|
||||
unmount();
|
||||
return;
|
||||
}
|
||||
willUnmountSheet(key);
|
||||
forcedDismissed.current = true;
|
||||
bottomSheetRef.current?.close();
|
||||
}, [willUnmountSheet, unmount, key]);
|
||||
if (minimized.current) {
|
||||
unmount();
|
||||
return;
|
||||
}
|
||||
willUnmountSheet(key);
|
||||
forcedDismissed.current = true;
|
||||
bottomSheetRef.current?.close(...args);
|
||||
},
|
||||
[willUnmountSheet, unmount, key]
|
||||
);
|
||||
const handleMinimize = useCallback(() => {
|
||||
if (minimized.current) {
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,11 @@ enum ANIMATION_STATE {
|
||||
STOPPED,
|
||||
}
|
||||
|
||||
enum ANIMATION_METHOD {
|
||||
TIMING,
|
||||
SPRING,
|
||||
}
|
||||
|
||||
const MODAL_STACK_BEHAVIOR = {
|
||||
replace: 'replace',
|
||||
push: 'push',
|
||||
@@ -24,5 +29,6 @@ export {
|
||||
WINDOW_WIDTH,
|
||||
GESTURE,
|
||||
ANIMATION_STATE,
|
||||
ANIMATION_METHOD,
|
||||
MODAL_STACK_BEHAVIOR,
|
||||
};
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import Animated, {
|
||||
useWorkletCallback,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { ANIMATION_METHOD } from '../constants';
|
||||
import { animate } from '../utilities';
|
||||
|
||||
/**
|
||||
* Generate spring animation configs.
|
||||
* @param configs overridable configs.
|
||||
*/
|
||||
export const useBottomSheetSpringConfigs = (
|
||||
configs: Omit<Animated.WithSpringConfig, 'velocity'>
|
||||
) => {
|
||||
const animationConfig = useWorkletCallback(
|
||||
(point: number, velocity: number = 0, callback: () => void) => {
|
||||
// @ts-ignore override velocity
|
||||
configs.velocity = velocity;
|
||||
return withSpring(point, configs, callback);
|
||||
},
|
||||
[configs]
|
||||
);
|
||||
return animationConfig;
|
||||
return animate(ANIMATION_METHOD.SPRING, configs);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { useMemo } from 'react';
|
||||
import Animated, {
|
||||
useWorkletCallback,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { ANIMATION_METHOD } from '../constants';
|
||||
import { animate } from '../utilities';
|
||||
import {
|
||||
DEFAULT_ANIMATION_DURATION,
|
||||
DEFAULT_ANIMATION_EASING,
|
||||
} from '../components/bottomSheet/constants';
|
||||
|
||||
/**
|
||||
* Generate animation timing configs.
|
||||
* Generate timing animation configs.
|
||||
* @default
|
||||
* - easing: Easing.out(Easing.exp)
|
||||
* - duration 500
|
||||
@@ -33,10 +32,6 @@ export const useBottomSheetTimingConfigs = (
|
||||
}
|
||||
return _configs;
|
||||
}, [configs.duration, configs.easing]);
|
||||
const animationConfig = useWorkletCallback(
|
||||
(point: number, _, callback: () => void) =>
|
||||
withTiming(point, overrideConfigs, callback),
|
||||
[overrideConfigs]
|
||||
);
|
||||
return animationConfig;
|
||||
|
||||
return animate(ANIMATION_METHOD.TIMING, overrideConfigs);
|
||||
};
|
||||
|
||||
38
src/types.d.ts
vendored
38
src/types.d.ts
vendored
@@ -1,27 +1,50 @@
|
||||
import type { FlatList, ScrollView, SectionList } from 'react-native';
|
||||
import type Animated from 'react-native-reanimated';
|
||||
|
||||
//#region Methods
|
||||
export interface BottomSheetMethods {
|
||||
/**
|
||||
* Snap to one of the provided points from `snapPoints`.
|
||||
* @param index snap point index.
|
||||
* @param animationDuration snap animation duration.
|
||||
* @param animationEasing snap animation easing function.
|
||||
* @type (index: number) => void
|
||||
*/
|
||||
snapTo: (index: number) => void;
|
||||
snapTo: (
|
||||
index: number,
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => void;
|
||||
/**
|
||||
* Snap to the maximum provided point from `snapPoints`.
|
||||
* @param animationDuration snap animation duration.
|
||||
* @param animationEasing snap animation easing function.
|
||||
* @type () => void
|
||||
*/
|
||||
expand: () => void;
|
||||
expand: (
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => void;
|
||||
/**
|
||||
* Snap to the minimum provided point from `snapPoints`.
|
||||
* @param animationDuration snap animation duration.
|
||||
* @param animationEasing snap animation easing function.
|
||||
* @type () => void
|
||||
*/
|
||||
collapse: () => void;
|
||||
collapse: (
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => void;
|
||||
/**
|
||||
* Close the bottom sheet.
|
||||
* @param animationDuration snap animation duration.
|
||||
* @param animationEasing snap animation easing function.
|
||||
* @type () => void
|
||||
*/
|
||||
close: () => void;
|
||||
close: (
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface BottomSheetModalMethods extends BottomSheetMethods {
|
||||
@@ -32,9 +55,14 @@ export interface BottomSheetModalMethods extends BottomSheetMethods {
|
||||
present: () => void;
|
||||
/**
|
||||
* Close and unmount the modal.
|
||||
* @param animationDuration snap animation duration.
|
||||
* @param animationEasing snap animation easing function.
|
||||
* @type () => void;
|
||||
*/
|
||||
dismiss: () => void;
|
||||
dismiss: (
|
||||
animationDuration?: number,
|
||||
animationEasing?: Animated.EasingFunction
|
||||
) => void;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
22
src/utilities/animate.ts
Normal file
22
src/utilities/animate.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import Animated, { withSpring, withTiming } from 'react-native-reanimated';
|
||||
import { ANIMATION_METHOD } from '../constants';
|
||||
|
||||
export const animate = (
|
||||
type: ANIMATION_METHOD,
|
||||
configs: Animated.WithSpringConfig | Animated.WithTimingConfig
|
||||
) => {
|
||||
'worklet';
|
||||
if (type === ANIMATION_METHOD.TIMING) {
|
||||
return (point: number, _: number, callback: () => void) => {
|
||||
'worklet';
|
||||
return withTiming(point, configs as Animated.WithTimingConfig, callback);
|
||||
};
|
||||
} else {
|
||||
return (point: number, velocity: number = 0, callback: () => void) => {
|
||||
'worklet';
|
||||
// @ts-ignore
|
||||
configs.velocity = velocity;
|
||||
return withSpring(point, configs as Animated.WithSpringConfig, callback);
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export { normalizeSnapPoints } from './normalizeSnapPoints';
|
||||
export { animate } from './animate';
|
||||
|
||||
Reference in New Issue
Block a user