11import type { PropsWithChildren } from "react" ;
2- import React from "react" ;
2+ import React , { useCallback , useMemo } from "react" ;
33import type { StyleProp , ViewStyle } from "react-native" ;
4- import type { PanGestureHandlerGestureEvent } from "react-native-gesture-handler" ;
5- import { PanGestureHandler } from "react-native-gesture-handler" ;
4+ import type { GestureStateChangeEvent , PanGestureHandlerEventPayload } from "react-native-gesture-handler" ;
5+ import {
6+ Gesture ,
7+ GestureDetector ,
8+ } from "react-native-gesture-handler" ;
69import Animated , {
710 cancelAnimation ,
811 measure ,
912 runOnJS ,
10- useAnimatedGestureHandler ,
1113 useAnimatedReaction ,
1214 useAnimatedRef ,
1315 useDerivedValue ,
@@ -20,12 +22,6 @@ import { CTX } from "./store";
2022import type { WithTimingAnimation } from "./types" ;
2123import { dealWithAnimation } from "./utils/dealWithAnimation" ;
2224
23- interface GestureContext extends Record < string , unknown > {
24- validStart : boolean
25- panOffset : number
26- max : number
27- }
28-
2925interface Props {
3026 size : number
3127 infinite ?: boolean
@@ -44,7 +40,6 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
4440 vertical,
4541 pagingEnabled,
4642 snapEnabled,
47- panGestureHandlerProps,
4843 loop : infinite ,
4944 scrollAnimationDuration,
5045 withAnimation,
@@ -68,10 +63,14 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
6863
6964 const maxPage = dataLength ;
7065 const isHorizontal = useDerivedValue ( ( ) => ! vertical , [ vertical ] ) ;
66+ const max = useSharedValue ( 0 ) ;
67+ const panOffset = useSharedValue ( 0 ) ;
7168 const touching = useSharedValue ( false ) ;
69+ const validStart = useSharedValue ( false ) ;
7270 const scrollEndTranslation = useSharedValue ( 0 ) ;
7371 const scrollEndVelocity = useSharedValue ( 0 ) ;
7472 const containerRef = useAnimatedRef < Animated . View > ( ) ;
73+ const maxScrollDistancePerSwipeIsSet = typeof maxScrollDistancePerSwipe === "number" ;
7574
7675 // Get the limit of the scroll.
7776 const getLimit = React . useCallback ( ( ) => {
@@ -123,7 +122,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
123122 let finalTranslation : number = withDecay ( { velocity, deceleration : 0.999 } ) ;
124123
125124 // If the distance of the swipe exceeds the max scroll distance, keep the view at the current position
126- if ( typeof maxScrollDistancePerSwipe === "number" && Math . abs ( scrollEndTranslation . value ) > maxScrollDistancePerSwipe ) {
125+ if ( maxScrollDistancePerSwipeIsSet && Math . abs ( scrollEndTranslation . value ) > maxScrollDistancePerSwipe ) {
127126 finalTranslation = origin ;
128127 }
129128 else {
@@ -180,6 +179,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
180179 scrollEndVelocity . value ,
181180 maxScrollDistancePerSwipe ,
182181 scrollEndTranslation . value ,
182+ maxScrollDistancePerSwipeIsSet ,
183183 ] ,
184184 ) ;
185185
@@ -259,96 +259,115 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
259259 return translation ;
260260 }
261261
262- const panGestureEventHandler = useAnimatedGestureHandler <
263- PanGestureHandlerGestureEvent ,
264- GestureContext
265- > (
266- {
267- onStart : ( _ , ctx ) => {
268- touching . value = true ;
269- ctx . validStart = true ;
270- onScrollBegin && runOnJS ( onScrollBegin ) ( ) ;
271-
272- ctx . max = ( maxPage - 1 ) * size ;
273- if ( ! infinite && ! overscrollEnabled )
274- ctx . max = getLimit ( ) ;
275-
276- ctx . panOffset = translation . value ;
277- } ,
278- onActive : ( e , ctx ) => {
279- if ( ctx . validStart ) {
280- ctx . validStart = false ;
281- cancelAnimation ( translation ) ;
282- }
283- touching . value = true ;
284- let { translationX, translationY } = e ;
262+ const onGestureBegin = useCallback ( ( ) => {
263+ "worklet" ;
285264
286- const totalTranslation = isHorizontal . value ? translationX : translationY ;
265+ touching . value = true ;
266+ validStart . value = true ;
267+ onScrollBegin && runOnJS ( onScrollBegin ) ( ) ;
287268
288- if ( typeof maxScrollDistancePerSwipe === "number" && Math . abs ( totalTranslation ) > maxScrollDistancePerSwipe ) {
289- const overSwipe = Math . abs ( totalTranslation ) - maxScrollDistancePerSwipe ;
290- const dampedTranslation = maxScrollDistancePerSwipe + overSwipe * 0.5 ;
269+ max . value = ( maxPage - 1 ) * size ;
270+ if ( ! infinite && ! overscrollEnabled )
271+ max . value = getLimit ( ) ;
291272
292- translationX = isHorizontal . value ? dampedTranslation * Math . sign ( translationX ) : translationX ;
293- translationY = ! isHorizontal . value ? dampedTranslation * Math . sign ( translationY ) : translationY ;
294- }
273+ panOffset . value = translation . value ;
274+ } , [
275+ max ,
276+ size ,
277+ maxPage ,
278+ infinite ,
279+ touching ,
280+ panOffset ,
281+ validStart ,
282+ translation ,
283+ overscrollEnabled ,
284+ getLimit ,
285+ onScrollBegin ,
286+ ] ) ;
295287
296- const panTranslation = isHorizontal . value ? translationX : translationY ;
297- if ( ! infinite ) {
298- if ( ( translation . value > 0 || translation . value < - ctx . max ) ) {
299- const boundary = translation . value > 0 ? 0 : - ctx . max ;
300- const fixed = boundary - ctx . panOffset ;
301- const dynamic = panTranslation - fixed ;
302- translation . value = boundary + dynamic * 0.5 ;
303- return ;
304- }
305- }
288+ const onGestureUpdate = useCallback ( ( e : PanGestureHandlerEventPayload ) => {
289+ "worklet" ;
290+
291+ if ( validStart . value ) {
292+ validStart . value = false ;
293+ cancelAnimation ( translation ) ;
294+ }
295+ touching . value = true ;
296+ const { translationX, translationY } = e ;
297+ const panTranslation = isHorizontal . value
298+ ? translationX
299+ : translationY ;
300+ if ( ! infinite ) {
301+ if ( ( translation . value > 0 || translation . value < - max . value ) ) {
302+ const boundary = translation . value > 0 ? 0 : - max . value ;
303+ const fixed = boundary - panOffset . value ;
304+ const dynamic = panTranslation - fixed ;
305+ translation . value = boundary + dynamic * 0.5 ;
306+ return ;
307+ }
308+ }
309+
310+ const translationValue = panOffset . value + panTranslation ;
311+ translation . value = translationValue ;
312+ } , [
313+ isHorizontal ,
314+ max ,
315+ panOffset ,
316+ infinite ,
317+ overscrollEnabled ,
318+ translation ,
319+ validStart ,
320+ touching ,
321+ ] ) ;
306322
307- const translationValue = ctx . panOffset + panTranslation ;
323+ const onGestureFinish = useCallback ( ( e : GestureStateChangeEvent < PanGestureHandlerEventPayload > ) => {
324+ "worklet" ;
308325
309- translation . value = translationValue ;
310- } ,
311- onEnd : ( e , ctx ) => {
312- const { velocityX, velocityY, translationX, translationY } = e ;
313- scrollEndVelocity . value = isHorizontal . value
314- ? velocityX
315- : velocityY ;
316- scrollEndTranslation . value = isHorizontal . value
317- ? translationX
318- : translationY ;
326+ const { velocityX, velocityY, translationX, translationY } = e ;
327+ scrollEndVelocity . value = isHorizontal . value
328+ ? velocityX
329+ : velocityY ;
330+ scrollEndTranslation . value = isHorizontal . value
331+ ? translationX
332+ : translationY ;
319333
320- const totalTranslation = isHorizontal . value ? translationX : translationY ;
334+ const totalTranslation = scrollEndVelocity . value + scrollEndTranslation . value ;
321335
322- if ( typeof maxScrollDistancePerSwipe === "number" && Math . abs ( totalTranslation ) > maxScrollDistancePerSwipe ) {
323- const nextPage = Math . round ( ( ctx . panOffset + maxScrollDistancePerSwipe * Math . sign ( totalTranslation ) ) / size ) * size ;
324- translation . value = withSpring ( withProcessTranslation ( nextPage ) , onScrollEnd ) ;
325- }
326- else {
327- endWithSpring ( onScrollEnd ) ;
328- }
336+ if ( maxScrollDistancePerSwipeIsSet && Math . abs ( totalTranslation ) > maxScrollDistancePerSwipe ) {
337+ const nextPage = Math . round ( ( panOffset . value + maxScrollDistancePerSwipe * Math . sign ( totalTranslation ) ) / size ) * size ;
338+ translation . value = withSpring ( withProcessTranslation ( nextPage ) , onScrollEnd ) ;
339+ }
340+ else {
341+ endWithSpring ( onScrollEnd ) ;
342+ }
329343
330- if ( ! infinite )
331- touching . value = false ;
332- } ,
333- } ,
334- [
335- pagingEnabled ,
336- isHorizontal . value ,
337- infinite ,
338- maxPage ,
339- size ,
340- snapEnabled ,
341- onScrollBegin ,
342- onScrollEnd ,
343- ] ,
344- ) ;
344+ if ( ! infinite )
345+ touching . value = false ;
346+ } , [
347+ size ,
348+ infinite ,
349+ touching ,
350+ panOffset ,
351+ translation ,
352+ isHorizontal ,
353+ scrollEndVelocity ,
354+ scrollEndTranslation ,
355+ maxScrollDistancePerSwipeIsSet ,
356+ maxScrollDistancePerSwipe ,
357+ endWithSpring ,
358+ withSpring ,
359+ onScrollEnd ,
360+ ] ) ;
361+
362+ const gesture = useMemo ( ( ) => Gesture . Pan ( ) . onBegin ( onGestureBegin ) . onUpdate ( onGestureUpdate ) . onEnd ( onGestureFinish ) , [
363+ onGestureBegin ,
364+ onGestureUpdate ,
365+ onGestureFinish ,
366+ ] ) ;
367+ const GestureContainer = enabled ? GestureDetector : React . Fragment ;
345368
346369 return (
347- < PanGestureHandler
348- { ...panGestureHandlerProps }
349- enabled = { enabled }
350- onGestureEvent = { panGestureEventHandler }
351- >
370+ < GestureContainer gesture = { gesture } >
352371 < Animated . View
353372 ref = { containerRef }
354373 testID = { testID }
@@ -358,7 +377,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
358377 >
359378 { props . children }
360379 </ Animated . View >
361- </ PanGestureHandler >
380+ </ GestureContainer >
362381 ) ;
363382} ;
364383
0 commit comments