Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"react-native-reanimated-carousel": "3.5.1"
},
"changesets": []
}
7 changes: 7 additions & 0 deletions .changeset/proud-zebras-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-native-reanimated-carousel": major
---

feat: use new RNGH api

Updates `react-native-gesture-handler` to `>=2.9.0` and replaces usage of `useAnimatedGestureHandler` with the [new gesture handler API](https://docs.swmansion.com/react-native-gesture-handler/docs/#rngh-20) which supports the [new gesture handler web implementation](https://github.com/software-mansion/react-native-gesture-handler/pull/2157).
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ Or if you use npm:
npm install react-native-reanimated-carousel
```

Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated(>=2.0.0)`](https://github.com/kmagiera/react-native-reanimated).
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated).

| | react-native-reanimated | react-native-gesture-handler |
| -------------------------------------- | ----------------------- | ---------------------------- |
| react-native-reanimated-carousel < v3 | <2.7.0 | \* |
| react-native-reanimated-carousel >= v3 | >=2.7.0 | \* |
| react-native-reanimated-carousel v1、v2 | >=2.0 & <2.7.0 | <2.9.0 |
| react-native-reanimated-carousel v3 | >=2.7.0 & < 3.x | <2.9.0 |
| react-native-reanimated-carousel v4 | >=3.x | >=2.9.0 |

## Usage

Expand Down
10 changes: 6 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ yarn add react-native-reanimated-carousel
npm install react-native-reanimated-carousel
```

并且我们需要安装 [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) 、[`react-native-reanimated(>=2.0.0)`](https://github.com/kmagiera/react-native-reanimated),安装步骤可参考各自文档。
| | react-native-reanimated | react-native-gesture-handler |
并且我们需要安装 [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) 、[`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated),安装步骤可参考各自文档。
| | react-native-reanimated | react-native-gesture-handler |
| -------------------------------------- | ----------------------- | ---------------------------- |
| react-native-reanimated-carousel < v3 | <2.7.0 | \* |
| react-native-reanimated-carousel >= v3 | >=2.7.0 | \* |
| react-native-reanimated-carousel v1、v2 | >=2.0 & <2.7.0 | <2.9.0 |
| react-native-reanimated-carousel v3 | >=2.7.0 & < 3.x | <2.9.0 |
| react-native-reanimated-carousel v4 | >=3.x | >=2.9.0 |


## 使用

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@
"watch": "^1.0.2"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-native": ">=0.6.0",
"react-native-gesture-handler": ">=2.0.0",
"react": ">=18.0.0",
"react-native": ">=0.70.3",
"react-native-gesture-handler": ">=2.9.0",
"react-native-reanimated": ">=3.0.0"
},
"jest": {
Expand Down
203 changes: 111 additions & 92 deletions src/ScrollViewGesture.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { PropsWithChildren } from "react";
import React from "react";
import React, { useCallback, useMemo } from "react";
import type { StyleProp, ViewStyle } from "react-native";
import type { PanGestureHandlerGestureEvent } from "react-native-gesture-handler";
import { PanGestureHandler } from "react-native-gesture-handler";
import type { GestureStateChangeEvent, PanGestureHandlerEventPayload } from "react-native-gesture-handler";
import {
Gesture,
GestureDetector,
} from "react-native-gesture-handler";
import Animated, {
cancelAnimation,
measure,
runOnJS,
useAnimatedGestureHandler,
useAnimatedReaction,
useAnimatedRef,
useDerivedValue,
Expand All @@ -20,12 +22,6 @@ import { CTX } from "./store";
import type { WithTimingAnimation } from "./types";
import { dealWithAnimation } from "./utils/dealWithAnimation";

interface GestureContext extends Record<string, unknown> {
validStart: boolean
panOffset: number
max: number
}

interface Props {
size: number
infinite?: boolean
Expand All @@ -44,7 +40,6 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
vertical,
pagingEnabled,
snapEnabled,
panGestureHandlerProps,
loop: infinite,
scrollAnimationDuration,
withAnimation,
Expand All @@ -68,10 +63,14 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {

const maxPage = dataLength;
const isHorizontal = useDerivedValue(() => !vertical, [vertical]);
const max = useSharedValue(0);
const panOffset = useSharedValue(0);
const touching = useSharedValue(false);
const validStart = useSharedValue(false);
const scrollEndTranslation = useSharedValue(0);
const scrollEndVelocity = useSharedValue(0);
const containerRef = useAnimatedRef<Animated.View>();
const maxScrollDistancePerSwipeIsSet = typeof maxScrollDistancePerSwipe === "number";

// Get the limit of the scroll.
const getLimit = React.useCallback(() => {
Expand Down Expand Up @@ -123,7 +122,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
let finalTranslation: number = withDecay({ velocity, deceleration: 0.999 });

// If the distance of the swipe exceeds the max scroll distance, keep the view at the current position
if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(scrollEndTranslation.value) > maxScrollDistancePerSwipe) {
if (maxScrollDistancePerSwipeIsSet && Math.abs(scrollEndTranslation.value) > maxScrollDistancePerSwipe) {
finalTranslation = origin;
}
else {
Expand Down Expand Up @@ -180,6 +179,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
scrollEndVelocity.value,
maxScrollDistancePerSwipe,
scrollEndTranslation.value,
maxScrollDistancePerSwipeIsSet,
],
);

Expand Down Expand Up @@ -259,96 +259,115 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
return translation;
}

const panGestureEventHandler = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
GestureContext
>(
{
onStart: (_, ctx) => {
touching.value = true;
ctx.validStart = true;
onScrollBegin && runOnJS(onScrollBegin)();

ctx.max = (maxPage - 1) * size;
if (!infinite && !overscrollEnabled)
ctx.max = getLimit();

ctx.panOffset = translation.value;
},
onActive: (e, ctx) => {
if (ctx.validStart) {
ctx.validStart = false;
cancelAnimation(translation);
}
touching.value = true;
let { translationX, translationY } = e;
const onGestureBegin = useCallback(() => {
"worklet";

const totalTranslation = isHorizontal.value ? translationX : translationY;
touching.value = true;
validStart.value = true;
onScrollBegin && runOnJS(onScrollBegin)();

if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
const overSwipe = Math.abs(totalTranslation) - maxScrollDistancePerSwipe;
const dampedTranslation = maxScrollDistancePerSwipe + overSwipe * 0.5;
max.value = (maxPage - 1) * size;
if (!infinite && !overscrollEnabled)
max.value = getLimit();

translationX = isHorizontal.value ? dampedTranslation * Math.sign(translationX) : translationX;
translationY = !isHorizontal.value ? dampedTranslation * Math.sign(translationY) : translationY;
}
panOffset.value = translation.value;
}, [
max,
size,
maxPage,
infinite,
touching,
panOffset,
validStart,
translation,
overscrollEnabled,
getLimit,
onScrollBegin,
]);

const panTranslation = isHorizontal.value ? translationX : translationY;
if (!infinite) {
if ((translation.value > 0 || translation.value < -ctx.max)) {
const boundary = translation.value > 0 ? 0 : -ctx.max;
const fixed = boundary - ctx.panOffset;
const dynamic = panTranslation - fixed;
translation.value = boundary + dynamic * 0.5;
return;
}
}
const onGestureUpdate = useCallback((e: PanGestureHandlerEventPayload) => {
"worklet";

if (validStart.value) {
validStart.value = false;
cancelAnimation(translation);
}
touching.value = true;
const { translationX, translationY } = e;
const panTranslation = isHorizontal.value
? translationX
: translationY;
if (!infinite) {
if ((translation.value > 0 || translation.value < -max.value)) {
const boundary = translation.value > 0 ? 0 : -max.value;
const fixed = boundary - panOffset.value;
const dynamic = panTranslation - fixed;
translation.value = boundary + dynamic * 0.5;
return;
}
}

const translationValue = panOffset.value + panTranslation;
translation.value = translationValue;
}, [
isHorizontal,
max,
panOffset,
infinite,
overscrollEnabled,
translation,
validStart,
touching,
]);

const translationValue = ctx.panOffset + panTranslation;
const onGestureFinish = useCallback((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
"worklet";

translation.value = translationValue;
},
onEnd: (e, ctx) => {
const { velocityX, velocityY, translationX, translationY } = e;
scrollEndVelocity.value = isHorizontal.value
? velocityX
: velocityY;
scrollEndTranslation.value = isHorizontal.value
? translationX
: translationY;
const { velocityX, velocityY, translationX, translationY } = e;
scrollEndVelocity.value = isHorizontal.value
? velocityX
: velocityY;
scrollEndTranslation.value = isHorizontal.value
? translationX
: translationY;

const totalTranslation = isHorizontal.value ? translationX : translationY;
const totalTranslation = scrollEndVelocity.value + scrollEndTranslation.value;

if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
const nextPage = Math.round((ctx.panOffset + maxScrollDistancePerSwipe * Math.sign(totalTranslation)) / size) * size;
translation.value = withSpring(withProcessTranslation(nextPage), onScrollEnd);
}
else {
endWithSpring(onScrollEnd);
}
if (maxScrollDistancePerSwipeIsSet && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
const nextPage = Math.round((panOffset.value + maxScrollDistancePerSwipe * Math.sign(totalTranslation)) / size) * size;
translation.value = withSpring(withProcessTranslation(nextPage), onScrollEnd);
}
else {
endWithSpring(onScrollEnd);
}

if (!infinite)
touching.value = false;
},
},
[
pagingEnabled,
isHorizontal.value,
infinite,
maxPage,
size,
snapEnabled,
onScrollBegin,
onScrollEnd,
],
);
if (!infinite)
touching.value = false;
}, [
size,
infinite,
touching,
panOffset,
translation,
isHorizontal,
scrollEndVelocity,
scrollEndTranslation,
maxScrollDistancePerSwipeIsSet,
maxScrollDistancePerSwipe,
endWithSpring,
withSpring,
onScrollEnd,
]);

const gesture = useMemo(() => Gesture.Pan().onBegin(onGestureBegin).onUpdate(onGestureUpdate).onEnd(onGestureFinish), [
onGestureBegin,
onGestureUpdate,
onGestureFinish,
]);
const GestureContainer = enabled ? GestureDetector : React.Fragment;

return (
<PanGestureHandler
{...panGestureHandlerProps}
enabled={enabled}
onGestureEvent={panGestureEventHandler}
>
<GestureContainer gesture={gesture}>
<Animated.View
ref={containerRef}
testID={testID}
Expand All @@ -358,7 +377,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
>
{props.children}
</Animated.View>
</PanGestureHandler>
</GestureContainer>
);
};

Expand Down
Loading