Skip to content

Commit

Permalink
feat: add snapToItem
Browse files Browse the repository at this point in the history
  • Loading branch information
r0b0t3d committed Apr 28, 2021
1 parent 244c7a0 commit df29a20
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 24 deletions.
56 changes: 52 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@
* https://github.com/facebook/react-native
*/

import React from 'react';
import { StyleSheet, View, Image, Dimensions } from 'react-native';
import Carousel, { PaginationIndicator } from '@r0b0t3d/react-native-carousel';
import React, { useCallback, useRef } from 'react';
import {
StyleSheet,
View,
Image,
Dimensions,
TouchableOpacity,
Text,
} from 'react-native';
import Carousel, {
CarouselHandles,
PaginationIndicator,
} from '@r0b0t3d/react-native-carousel';
import { useSharedValue } from 'react-native-reanimated';

const data = [
Expand Down Expand Up @@ -63,10 +73,25 @@ const { width } = Dimensions.get('window');

export default function App() {
const currentPage = useSharedValue(0);
const carousel = useRef<CarouselHandles>(null);

const handleRandom = useCallback(() => {
const randomIdx = Math.floor(Math.random() * data.length);
carousel.current?.snapToItem(randomIdx, true);
}, []);

const handleNext = useCallback(() => {
carousel.current?.goNext();
}, [])

const handlePrev = useCallback(() => {
carousel.current?.goPrev();
}, [])

return (
<View style={styles.container}>
<Carousel
ref={carousel}
style={{ height: 200 }}
data={data}
loop={true}
Expand Down Expand Up @@ -103,10 +128,22 @@ export default function App() {
borderRadius: 5,
}}
indicatorConfigs={{
spaceBetween: 10
spaceBetween: 10,
}}
/>
</View>

<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={handlePrev}>
<Text>PREV</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={handleRandom}>
<Text>RANDOM</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={handleNext}>
<Text>NEXT</Text>
</TouchableOpacity>
</View>
</View>
);
}
Expand All @@ -118,4 +155,15 @@ const styles = StyleSheet.create({
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
buttonContainer: {
marginTop: 30,
flexDirection: 'row'
},
button: {
backgroundColor: '#b2b2b2',
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 5,
marginRight: 20
},
});
53 changes: 36 additions & 17 deletions src/components/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Animated, {
runOnJS,
useAnimatedProps,
} from 'react-native-reanimated';
import type { CarouselProps, CarouselRef } from '../types';
import type { CarouselProps, CarouselHandles } from '../types';
import PageItem from './PageItem';
import { findNearestPage, generateOffsets } from '../utils';
import type { CarouselData } from '../types';
Expand All @@ -43,24 +43,26 @@ function Carousel(
onPageChange,
animatedPage = useSharedValue(0),
}: CarouselProps,
ref: Ref<CarouselRef>
ref: Ref<CarouselHandles>
) {
const currentPage = useSharedValue(loop ? additionalPagesPerSide : 0);
const [isDragging, setDragging] = useState(false);
const animatedScroll = useSharedValue(currentPage.value * sliderWidth);
const freeze = useSharedValue(loop);
const [isDragging, setDragging] = useState(false);
const expectedPosition = useRef(-1);
const pageMapper = useRef<Record<number, number>>({});

const horizontalPadding = useMemo(() => {
const padding = (sliderWidth - itemWidth) / 2;
return firstItemAlignment === 'center' || loop ? padding : spaceHeadTail;
}, [sliderWidth, itemWidth, firstItemAlignment, loop, spaceHeadTail]);
const pageMapper = useRef<any>({})

const scrollViewRef = useRef<any>(null);

useImperativeHandle(ref, () => ({
next: goNext,
prev: goPrev,
goNext,
goPrev,
snapToItem,
}));

const offsets = useMemo(() => {
Expand All @@ -81,7 +83,8 @@ function Carousel(
const tailItems = data.slice(0, additionalPagesPerSide);
const newItems = [...headItems, ...data, ...tailItems];
for (let i = 0; i < newItems.length; i++) {
pageMapper.current[i] = (data.length - additionalPagesPerSide + i) % data.length;
pageMapper.current[i] =
(data.length - additionalPagesPerSide + i) % data.length;
}
return newItems;
} else {
Expand All @@ -90,14 +93,11 @@ function Carousel(
}
return data;
}
}, [data, loop]);
}, [data, loop]);

const getActualPage = useCallback(
(page: number) => {
return pageMapper.current[page]
},
[]
);
const getActualPage = useCallback((page: number) => {
return pageMapper.current[page];
}, []);

const getRef = useCallback(() => {
if (!scrollViewRef.current) return;
Expand Down Expand Up @@ -140,6 +140,25 @@ function Carousel(
handleScrollTo(prev);
}, [handleScrollTo]);

const snapToItem = useCallback(
(index: number, animated = true) => {
if (index < 0 || index >= data.length) {
console.error(`Index not valid ${index}`);
return;
}
let pageIndex = index;
if (loop) {
const indices: number[] = Object.keys(pageMapper.current)
.filter((idx) => pageMapper.current[Number(idx)] === index)
.map((idx) => Number(idx));
const toIndex = findNearestPage(currentPage.value, indices, 10);
pageIndex = indices[toIndex];
}
handleScrollTo(pageIndex, animated);
},
[handleScrollTo]
);

const handlePageChange = useCallback(
(page: number) => {
const actualPage = getActualPage(page);
Expand All @@ -148,11 +167,11 @@ function Carousel(
onPageChange(actualPage);
}
currentPage.value = page;
if (!loop) return;
if (!loop) return;
if (page === pageItems.length - 1) {
jumpTo((additionalPagesPerSide * 2) - 1);
jumpTo(additionalPagesPerSide * 2 - 1);
} else if (page === 0) {
jumpTo(pageItems.length - (additionalPagesPerSide * 2));
jumpTo(pageItems.length - additionalPagesPerSide * 2);
}
},
[onPageChange, loop, getActualPage, jumpTo, pageItems]
Expand Down
7 changes: 4 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export type CarouselProps = {
onPageChange?: (index: number) => void;
};

export type CarouselRef = {
next(): void;
prev(): void;
export type CarouselHandles = {
goNext(): void;
goPrev(): void;
snapToItem(index: number, animated?: boolean): void;
};

export type AnimatorProps = {
Expand Down

0 comments on commit df29a20

Please sign in to comment.