Skip to content

Commit

Permalink
feat: add onSnapToItem props,should get current item info
Browse files Browse the repository at this point in the history
  • Loading branch information
dohooo committed Sep 8, 2021
1 parent d6de7c2 commit 6ae05fc
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 45 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ import Carousel from "react-native-reanimated-carousel";
| style | false | {} | ViewStyle | Carousel container style |
| height | false | '100%' | undefined \| string \| number | Specified carousel container height |

## Ref

| name | types | description |
| --------------- | ---------- | ---------------------- |
| prev | ()=>void | Play the last one |
| loop | ()=>void | Play the next one |
| getCurrentIndex | ()=>number | Get current item index |

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
13 changes: 10 additions & 3 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react-native/no-inline-styles */
import * as React from 'react';
import { Button, Dimensions, View } from 'react-native';
import { Button, Dimensions, Text, View } from 'react-native';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { ICarouselInstance } from '../../src/Carousel';

Expand Down Expand Up @@ -31,7 +31,10 @@ export default function App() {
{ color: 'yellow' },
]}
parallaxScrollingScale={0.8}
renderItem={({ color }) => {
onSnapToItem={(index) => {
console.log('current index:', index);
}}
renderItem={({ color }, index) => {
return (
<View
style={{
Expand All @@ -50,8 +53,12 @@ export default function App() {
style={{
flex: 1,
backgroundColor: color,
justifyContent: 'center',
alignItems: 'center',
}}
/>
>
<Text>{index}</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
Expand Down
91 changes: 66 additions & 25 deletions src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedReaction,
useDerivedValue,
useSharedValue,
withTiming,
Expand All @@ -17,6 +18,8 @@ import type { TMode } from './layouts';
import { ParallaxLayout } from './layouts/index';
import { useCarouselController } from './useCarouselController';
import { useComputedAnim } from './useComputedAnim';
import { useLoop } from './useLoop';
import { useComputedIndex } from './useComputedIndex';

export const _withTiming = (
num: number,
Expand Down Expand Up @@ -91,11 +94,25 @@ export interface ICarouselProps<T extends unknown> {
* @default 0.8
*/
parallaxScrollingScale?: number;
/**
* Callback fired when navigating to an item
*/
onSnapToItem?: (index: number) => void;
}

export interface ICarouselInstance {
/**
* Play the last one
*/
prev: () => void;
/**
* Play the next one
*/
next: () => void;
/**
* Get current item index
*/
getCurrentIndex: () => number;
}

function Carousel<T extends unknown = any>(
Expand All @@ -109,15 +126,15 @@ function Carousel<T extends unknown = any>(
loop = true,
mode = 'default',
renderItem,
autoPlay = false,
autoPlayReverse = false,
autoPlayInterval = 1000,
autoPlay,
autoPlayReverse,
autoPlayInterval,
parallaxScrollingOffset,
parallaxScrollingScale,
onSnapToItem,
style,
} = props;
const handlerOffsetX = useSharedValue<number>(0);
const timer = React.useRef<NodeJS.Timer>();
const data = React.useMemo<T[]>(() => {
if (_data.length === 1) {
return [_data[0], _data[0], _data[0]];
Expand All @@ -129,14 +146,47 @@ function Carousel<T extends unknown = any>(
}, [_data]);

const computedAnimResult = useComputedAnim(width, data.length);

const { next, prev } = useCarouselController({ width, handlerOffsetX });
const carouselController = useCarouselController({ width, handlerOffsetX });
useLoop({
autoPlay,
autoPlayInterval,
autoPlayReverse,
carouselController,
});
const { index, computedIndex } = useComputedIndex({
length: data.length,
handlerOffsetX,
width,
});

const offsetX = useDerivedValue(() => {
const x = handlerOffsetX.value % computedAnimResult.WL;
return isNaN(x) ? 0 : x;
}, [computedAnimResult]);

useAnimatedReaction(
() => index.value,
(i) => onSnapToItem && runOnJS(onSnapToItem)(i),
[onSnapToItem]
);

const callComputedIndex = React.useCallback(
(isFinished: boolean) => isFinished && computedIndex?.(),
[computedIndex]
);

const next = React.useCallback(() => {
return carouselController.next(callComputedIndex);
}, [carouselController, callComputedIndex]);

const prev = React.useCallback(() => {
return carouselController.prev(callComputedIndex);
}, [carouselController, callComputedIndex]);

const getCurrentIndex = React.useCallback(() => {
return index.value;
}, [index]);

const animatedListScrollHandler =
useAnimatedGestureHandler<PanGestureHandlerGestureEvent>(
{
Expand All @@ -153,7 +203,7 @@ function Carousel<T extends unknown = any>(
handlerOffsetX.value = Math.max(
Math.min(
ctx.startContentOffsetX +
Math.round(e.translationX),
Math.round(e.translationX),
0
),
-(data.length - 1) * width
Expand All @@ -163,20 +213,24 @@ function Carousel<T extends unknown = any>(
const intTranslationX = Math.round(e.translationX);
const sub = Math.abs(intTranslationX);

function _withTimingCallback(num: number) {
return _withTiming(num, callComputedIndex);
}

if (intTranslationX > 0) {
if (!loop && handlerOffsetX.value >= 0) {
return;
}

if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
handlerOffsetX.value = _withTimingCallback(
fillNum(
width,
handlerOffsetX.value + (width - sub)
)
);
} else {
handlerOffsetX.value = _withTiming(
handlerOffsetX.value = _withTimingCallback(
fillNum(width, handlerOffsetX.value - sub)
);
}
Expand All @@ -192,14 +246,14 @@ function Carousel<T extends unknown = any>(
}

if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
handlerOffsetX.value = _withTimingCallback(
fillNum(
width,
handlerOffsetX.value - (width - sub)
)
);
} else {
handlerOffsetX.value = _withTiming(
handlerOffsetX.value = _withTimingCallback(
fillNum(width, handlerOffsetX.value + sub)
);
}
Expand All @@ -214,23 +268,10 @@ function Carousel<T extends unknown = any>(
return {
next,
prev,
getCurrentIndex,
};
});

React.useEffect(() => {
if (timer.current) {
clearInterval(timer.current);
}
if (autoPlay) {
timer.current = setInterval(() => {
autoPlayReverse ? prev() : next();
}, autoPlayInterval);
}
return () => {
!!timer.current && clearInterval(timer.current);
};
}, [autoPlay, autoPlayReverse, autoPlayInterval, prev, next]);

const Layouts = React.useMemo<React.FC<{ index: number }>>(() => {
switch (mode) {
case 'parallax':
Expand Down
51 changes: 34 additions & 17 deletions src/useCarouselController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ interface IOpts {
handlerOffsetX: Animated.SharedValue<number>;
}

export function useCarouselController(opts: IOpts) {
export interface ICarouselController {
prev: (callback?: (isFinished: boolean) => void) => void;
next: (callback?: (isFinished: boolean) => void) => void;
}

export function useCarouselController(opts: IOpts): ICarouselController {
const lock = useSharedValue<boolean>(false);
const { width, handlerOffsetX } = opts;

Expand All @@ -24,23 +29,35 @@ export function useCarouselController(opts: IOpts) {
lock.value = true;
}, [lock]);

const next = React.useCallback(() => {
if (lock.value) return;
openLock();
handlerOffsetX.value = _withTiming(
handlerOffsetX.value - width,
closeLock
);
}, [width, openLock, closeLock, lock, handlerOffsetX]);
const next = React.useCallback(
(callback?: (isFinished: boolean) => void) => {
if (lock.value) return;
openLock();
handlerOffsetX.value = _withTiming(
handlerOffsetX.value - width,
(isFinished: boolean) => {
callback?.(isFinished);
closeLock(isFinished);
}
);
},
[width, openLock, closeLock, lock, handlerOffsetX]
);

const prev = React.useCallback(() => {
if (lock.value) return;
openLock();
handlerOffsetX.value = _withTiming(
handlerOffsetX.value + width,
closeLock
);
}, [width, openLock, closeLock, lock, handlerOffsetX]);
const prev = React.useCallback(
(callback?: (isFinished: boolean) => void) => {
if (lock.value) return;
openLock();
handlerOffsetX.value = _withTiming(
handlerOffsetX.value + width,
(isFinished: boolean) => {
callback?.(isFinished);
closeLock(isFinished);
}
);
},
[width, openLock, closeLock, lock, handlerOffsetX]
);

return {
next,
Expand Down
22 changes: 22 additions & 0 deletions src/useComputedIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import Animated, { useSharedValue } from 'react-native-reanimated';

export function useComputedIndex(opts: {
handlerOffsetX: Animated.SharedValue<number>;
length: number;
width: number;
}) {
const { length, width, handlerOffsetX } = opts;
const index = useSharedValue<number>(0);

const computedIndex = React.useCallback(() => {
const toInt = (handlerOffsetX.value / width) % length;
const i =
handlerOffsetX.value <= 0
? Math.abs(toInt)
: Math.abs(toInt > 0 ? length - toInt : 0);
index.value = i;
}, [length, handlerOffsetX, index, width]);

return { index, computedIndex };
}
32 changes: 32 additions & 0 deletions src/useLoop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import type { ICarouselController } from './useCarouselController';

export function useLoop(opts: {
autoPlay?: boolean;
autoPlayInterval?: number;
autoPlayReverse?: boolean;
carouselController: ICarouselController;
}) {
const {
autoPlay = false,
autoPlayReverse = false,
autoPlayInterval = 1000,
carouselController,
} = opts;
const timer = React.useRef<NodeJS.Timer>();
React.useEffect(() => {
if (timer.current) {
clearInterval(timer.current);
}
if (autoPlay) {
timer.current = setInterval(() => {
autoPlayReverse
? carouselController.prev()
: carouselController.next();
}, autoPlayInterval);
}
return () => {
!!timer.current && clearInterval(timer.current);
};
}, [autoPlay, autoPlayReverse, autoPlayInterval, carouselController]);
}

0 comments on commit 6ae05fc

Please sign in to comment.