diff --git a/docs/props.md b/docs/props.md index 98fcddba..4452775b 100644 --- a/docs/props.md +++ b/docs/props.md @@ -20,7 +20,7 @@ | testID | ❌ | | string | Used to locate this view in end-to-end tests | | onSnapToItem | ❌ | | (index: number) => void | Callback fired when navigating to an item | | onScrollBegin | ❌ | | () => void | Callback fired when scroll begin | -| onScrollEnd | ❌ | | (previous: number, current: number) => void | Callback fired when scroll end | +| onScrollEnd | ❌ | | (index: number) => void | Callback fired when scroll end | | withAnimation | ❌ | | {type: 'spring';config: WithSpringConfig;} \| {type: 'timing';config: WithTimingConfig;} | Specifies the scrolling animation effect | | panGestureHandlerProps | ❌ | {} | Omit,'onHandlerStateChange'> | PanGestureHandler props | | windowSize | ❌ | 0 | number | The maximum number of items that can respond to pan gesture events, `0` means all items will respond to pan gesture events | diff --git a/docs/props.zh-CN.md b/docs/props.zh-CN.md index 3bdd6810..0c311a41 100644 --- a/docs/props.zh-CN.md +++ b/docs/props.zh-CN.md @@ -20,7 +20,7 @@ | testID | ❌ | | string | 在 E2E 测试中用来定位视图 | | onSnapToItem | ❌ | | (index: number) => void | 切换至另一张轮播图时触发 | | onScrollBegin | ❌ | | () => void | 切换动画开始时触发 | -| onScrollEnd | ❌ | | (previous: number, current: number) => void | 切换动画结束时触发 | +| onScrollEnd | ❌ | | (index: number) => void | 切换动画结束时触发 | | withAnimation | ❌ | | {type: 'spring';config: WithSpringConfig;} \| {type: 'timing';config: WithTimingConfig;} | 指定滚动时的动画效果 | | panGestureHandlerProps | ❌ | {} | Omit,'onHandlerStateChange'> | PanGestureHandler props | | windowSize | ❌ | 0 | number | 能响应平移手势事件的最大 item 数量,0 表示所有元素都会先响应 | diff --git a/src/Carousel.tsx b/src/Carousel.tsx index d92397f4..dc8548e2 100644 --- a/src/Carousel.tsx +++ b/src/Carousel.tsx @@ -1,9 +1,5 @@ import React from 'react'; -import Animated, { - runOnJS, - runOnUI, - useDerivedValue, -} from 'react-native-reanimated'; +import Animated, { runOnJS, useDerivedValue } from 'react-native-reanimated'; import { useCarouselController } from './hooks/useCarouselController'; import { useAutoPlay } from './hooks/useAutoPlay'; @@ -88,15 +84,8 @@ const Carousel = React.forwardRef>( duration: scrollAnimationDuration, }); - const { - sharedIndex, - sharedPreIndex, - to, - next, - prev, - scrollTo, - getCurrentIndex, - } = carouselController; + const { to, next, prev, scrollTo, getSharedIndex, getCurrentIndex } = + carouselController; const { start: startAutoPlay, pause: pauseAutoPlay } = useAutoPlay({ autoPlay, @@ -106,17 +95,15 @@ const Carousel = React.forwardRef>( }); const _onScrollEnd = React.useCallback(() => { - 'worklet'; - const _sharedIndex = Math.round(sharedIndex.value); - const _sharedPreIndex = Math.round(sharedPreIndex.value); + const _sharedIndex = Math.round(getSharedIndex()); if (onSnapToItem) { - runOnJS(onSnapToItem)(_sharedIndex); + onSnapToItem(_sharedIndex); } if (onScrollEnd) { - runOnJS(onScrollEnd)(_sharedPreIndex, _sharedIndex); + onScrollEnd(_sharedIndex); } - }, [onSnapToItem, onScrollEnd, sharedIndex, sharedPreIndex]); + }, [onSnapToItem, onScrollEnd, getSharedIndex]); const scrollViewGestureOnScrollBegin = React.useCallback(() => { pauseAutoPlay(); @@ -125,10 +112,7 @@ const Carousel = React.forwardRef>( const scrollViewGestureOnScrollEnd = React.useCallback(() => { startAutoPlay(); - /** - * TODO magic - */ - runOnUI(_onScrollEnd)(); + _onScrollEnd(); }, [_onScrollEnd, startAutoPlay]); const scrollViewGestureOnTouchBegin = React.useCallback(pauseAutoPlay, [ diff --git a/src/hooks/useCarouselController.tsx b/src/hooks/useCarouselController.tsx index 3ddf6e68..eee89e18 100644 --- a/src/hooks/useCarouselController.tsx +++ b/src/hooks/useCarouselController.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React, { useRef } from 'react'; import type Animated from 'react-native-reanimated'; import { Easing } from '../constants'; import { runOnJS, - useDerivedValue, + useAnimatedReaction, useSharedValue, } from 'react-native-reanimated'; import type { @@ -13,6 +13,7 @@ import type { } from '../types'; import { dealWithAnimation } from '@/utils/dealWithAnimation'; import { convertToSharedIndex } from '@/utils/computedWithAutoFillData'; +import { round } from '@/utils/log'; interface IOpts { loop: boolean; @@ -28,8 +29,7 @@ interface IOpts { } export interface ICarouselController { - sharedIndex: Animated.SharedValue; - sharedPreIndex: Animated.SharedValue; + getSharedIndex: () => number; prev: (opts?: TCarouselActionOptions) => void; next: (opts?: TCarouselActionOptions) => void; getCurrentIndex: () => number; @@ -60,8 +60,8 @@ export function useCarouselController(options: IOpts): ICarouselController { const index = useSharedValue(defaultIndex); // The Index displayed to the user - const sharedIndex = useSharedValue(defaultIndex); - const sharedPreIndex = useSharedValue(defaultIndex); + const sharedIndex = useRef(defaultIndex); + const sharedPreIndex = useRef(defaultIndex); const currentFixedPage = React.useCallback(() => { if (loop) { @@ -76,31 +76,46 @@ export function useCarouselController(options: IOpts): ICarouselController { ); }, [handlerOffsetX, dataInfo, size, loop]); - useDerivedValue(() => { - const handlerOffsetXValue = handlerOffsetX.value; - sharedPreIndex.value = sharedIndex.value; - const toInt = (handlerOffsetXValue / size) % dataInfo.length; - const isPositive = handlerOffsetXValue <= 0; - const i = isPositive - ? Math.abs(toInt) - : Math.abs(toInt > 0 ? dataInfo.length - toInt : 0); - index.value = i; - sharedIndex.value = convertToSharedIndex({ + function setSharedIndex(newSharedIndex: number) { + sharedIndex.current = newSharedIndex; + } + + useAnimatedReaction( + () => { + const handlerOffsetXValue = handlerOffsetX.value; + const toInt = round(handlerOffsetXValue / size) % dataInfo.length; + const isPositive = handlerOffsetXValue <= 0; + const i = isPositive + ? Math.abs(toInt) + : Math.abs(toInt > 0 ? dataInfo.length - toInt : 0); + + const newSharedIndexValue = convertToSharedIndex({ + loop, + rawDataLength: dataInfo.originalLength, + autoFillData: autoFillData!, + index: i, + }); + + return { + i, + newSharedIndexValue, + }; + }, + ({ i, newSharedIndexValue }) => { + index.value = i; + runOnJS(setSharedIndex)(newSharedIndexValue); + }, + [ + sharedPreIndex, + sharedIndex, + size, + dataInfo, + index, loop, - rawDataLength: dataInfo.originalLength, - autoFillData: autoFillData!, - index: i, - }); - }, [ - sharedPreIndex, - sharedIndex, - size, - dataInfo, - index, - loop, - autoFillData, - handlerOffsetX, - ]); + autoFillData, + handlerOffsetX, + ] + ); const getCurrentIndex = React.useCallback(() => { return index.value; @@ -255,12 +270,11 @@ export function useCarouselController(options: IOpts): ICarouselController { ); return { - sharedIndex, - sharedPreIndex, to, next, prev, scrollTo, getCurrentIndex, + getSharedIndex: () => sharedIndex.current, }; } diff --git a/src/types.ts b/src/types.ts index f68b9064..f9b5fe19 100644 --- a/src/types.ts +++ b/src/types.ts @@ -172,7 +172,7 @@ export type TCarouselProps = { /** * On scroll end */ - onScrollEnd?: (previous: number, current: number) => void; + onScrollEnd?: (index: number) => void; /** * On progress change * @param offsetProgress Total of offset distance (0 390 780 ...) diff --git a/src/utils/log.ts b/src/utils/log.ts index 9061b4fa..f3f60cb7 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -5,3 +5,8 @@ export function log(...msg: any) { console.log(...msg); } + +export function round(number: number) { + 'worklet'; + return Math.round(number); +}