Skip to content

Commit b33bb78

Browse files
committed
feat: add goToIndex method
1 parent 36d05ca commit b33bb78

File tree

5 files changed

+120
-31
lines changed

5 files changed

+120
-31
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import Carousel from "react-native-reanimated-carousel";
7777
| --------------- | ---------- | ---------------------- |
7878
| prev | ()=>void | Play the last one |
7979
| loop | ()=>void | Play the next one |
80+
| goToIndex | (index: number, animated?: boolean) => void | Go to index |
8081
| getCurrentIndex | ()=>number | Get current item index |
8182

8283
## Contributing

example/src/App.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
/* eslint-disable react-native/no-inline-styles */
22
import * as React from 'react';
3-
import { Button, Dimensions, Text, View } from 'react-native';
3+
import { Alert, Button, Dimensions, Text, TextInput, View } from 'react-native';
44
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
55
import { ICarouselInstance } from '../../src/Carousel';
66

77
import Carousel from '../../src/index';
88

99
const { width } = Dimensions.get('window');
1010

11+
const data = [
12+
{ color: 'red' },
13+
{ color: 'purple' },
14+
{ color: 'blue' },
15+
{ color: 'yellow' },
16+
];
17+
1118
export default function App() {
19+
const [index, setIndex] = React.useState<string>('');
1220
const r = React.useRef<ICarouselInstance | null>(null);
13-
1421
return (
1522
<View
1623
style={{
@@ -26,12 +33,7 @@ export default function App() {
2633
ref={r}
2734
mode="parallax"
2835
width={width}
29-
data={[
30-
{ color: 'red' },
31-
{ color: 'purple' },
32-
{ color: 'blue' },
33-
{ color: 'yellow' },
34-
]}
36+
data={data}
3537
parallaxScrollingScale={0.8}
3638
onSnapToItem={(index) => {
3739
console.log('current index:', index);
@@ -81,6 +83,24 @@ export default function App() {
8183
r.current.next();
8284
}}
8385
/>
86+
<TextInput
87+
placeholder={'Set go to index'}
88+
placeholderTextColor="white"
89+
style={{ color: 'white' }}
90+
value={index}
91+
onChangeText={setIndex}
92+
/>
93+
<Button
94+
title="GoTo"
95+
onPress={() => {
96+
const idx = Number(index);
97+
if (idx < 0 || idx >= data.length) {
98+
Alert.alert('invalid index');
99+
return;
100+
}
101+
r.current.goToIndex(idx, true);
102+
}}
103+
/>
84104
</View>
85105
);
86106
}

src/Carousel.tsx

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ParallaxLayout } from './layouts/index';
1818
import { useCarouselController } from './useCarouselController';
1919
import { useComputedAnim } from './useComputedAnim';
2020
import { useAutoPlay } from './useAutoPlay';
21-
import { useComputedIndex } from './useComputedIndex';
21+
import { useIndexController } from './useIndexController';
2222
import { useLockController } from './useLock';
2323

2424
const defaultTimingConfig: Animated.WithTimingConfig = {
@@ -105,6 +105,10 @@ export interface ICarouselInstance {
105105
* Get current item index
106106
*/
107107
getCurrentIndex: () => number;
108+
/**
109+
* Go to index
110+
*/
111+
goToIndex: (index: number, animated?: boolean) => void;
108112
}
109113

110114
function Carousel<T extends unknown = any>(
@@ -144,32 +148,38 @@ function Carousel<T extends unknown = any>(
144148
}, [_data, loop]);
145149

146150
const computedAnimResult = useComputedAnim(width, data.length);
151+
152+
const indexController = useIndexController({
153+
length: data.length,
154+
handlerOffsetX,
155+
width,
156+
});
157+
147158
const carouselController = useCarouselController({
148159
width,
149160
handlerOffsetX,
161+
indexController,
150162
lockController,
151163
timingConfig,
152164
disable: !data.length,
153165
onNext: (isFinished) => isFinished && callComputedIndex(),
154166
onPrev: (isFinished) => isFinished && callComputedIndex(),
155167
});
168+
169+
const { index, computedIndex } = indexController;
170+
171+
const offsetX = useDerivedValue(() => {
172+
const x = handlerOffsetX.value % computedAnimResult.WL;
173+
return isNaN(x) ? 0 : x;
174+
}, [computedAnimResult]);
175+
156176
useAutoPlay({
157177
autoPlay,
158178
autoPlayInterval,
159179
autoPlayReverse,
160180
carouselController,
161181
lockController,
162182
});
163-
const { index, computedIndex } = useComputedIndex({
164-
length: data.length,
165-
handlerOffsetX,
166-
width,
167-
});
168-
169-
const offsetX = useDerivedValue(() => {
170-
const x = handlerOffsetX.value % computedAnimResult.WL;
171-
return isNaN(x) ? 0 : x;
172-
}, [computedAnimResult]);
173183

174184
useAnimatedReaction(
175185
() => index.value,
@@ -206,6 +216,13 @@ function Carousel<T extends unknown = any>(
206216
return index.value;
207217
}, [index]);
208218

219+
const goToIndex = React.useCallback(
220+
(i: number, animated?: boolean) => {
221+
carouselController.to(i, animated);
222+
},
223+
[carouselController]
224+
);
225+
209226
const animatedListScrollHandler =
210227
useAnimatedGestureHandler<PanGestureHandlerGestureEvent>(
211228
{
@@ -302,6 +319,7 @@ function Carousel<T extends unknown = any>(
302319
next,
303320
prev,
304321
getCurrentIndex,
322+
goToIndex,
305323
};
306324
});
307325

src/useCarouselController.tsx

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React from 'react';
22
import type Animated from 'react-native-reanimated';
33
import { runOnJS, withTiming } from 'react-native-reanimated';
4+
import type { IIndexController } from './useIndexController';
45
import type { ILockController } from './useLock';
56

67
interface IOpts {
78
width: number;
89
handlerOffsetX: Animated.SharedValue<number>;
910
lockController: ILockController;
11+
indexController: IIndexController;
1012
timingConfig: Animated.WithTimingConfig;
1113
disable?: boolean;
1214
onPrev?: (isFinished: boolean) => void;
@@ -16,6 +18,7 @@ interface IOpts {
1618
export interface ICarouselController {
1719
prev: () => void;
1820
next: () => void;
21+
to: (index: number, animated?: boolean) => void;
1922
}
2023

2124
export function useCarouselController(opts: IOpts): ICarouselController {
@@ -24,11 +27,16 @@ export function useCarouselController(opts: IOpts): ICarouselController {
2427
handlerOffsetX,
2528
timingConfig,
2629
lockController,
30+
indexController,
2731
disable = false,
2832
onPrev,
2933
onNext,
3034
} = opts;
3135

36+
const canSliding = React.useCallback(() => {
37+
return !disable && !lockController.isLock();
38+
}, [lockController, disable]);
39+
3240
const closeLock = React.useCallback(
3341
(isFinished: boolean) => {
3442
if (isFinished) {
@@ -39,8 +47,7 @@ export function useCarouselController(opts: IOpts): ICarouselController {
3947
);
4048

4149
const next = React.useCallback(() => {
42-
if (disable) return;
43-
if (lockController.isLock()) return;
50+
if (!canSliding()) return;
4451
lockController.lock();
4552
handlerOffsetX.value = withTiming(
4653
handlerOffsetX.value - width,
@@ -52,17 +59,17 @@ export function useCarouselController(opts: IOpts): ICarouselController {
5259
);
5360
}, [
5461
onNext,
62+
closeLock,
63+
canSliding,
5564
width,
56-
lockController,
5765
timingConfig,
58-
closeLock,
5966
handlerOffsetX,
60-
disable,
67+
lockController,
6168
]);
6269

6370
const prev = React.useCallback(() => {
64-
if (disable) return;
65-
if (lockController.isLock()) return;
71+
if (!canSliding()) return;
72+
6673
lockController.lock();
6774
handlerOffsetX.value = withTiming(
6875
handlerOffsetX.value + width,
@@ -74,16 +81,54 @@ export function useCarouselController(opts: IOpts): ICarouselController {
7481
);
7582
}, [
7683
onPrev,
84+
closeLock,
85+
canSliding,
7786
width,
78-
lockController,
7987
timingConfig,
80-
closeLock,
8188
handlerOffsetX,
82-
disable,
89+
lockController,
8390
]);
8491

92+
const to = React.useCallback(
93+
(index: number, animated: boolean = false) => {
94+
if (index === indexController.index.value) return;
95+
if (!canSliding()) return;
96+
97+
lockController.lock();
98+
99+
const offset =
100+
handlerOffsetX.value +
101+
(indexController.index.value - index) * width;
102+
103+
if (animated) {
104+
handlerOffsetX.value = withTiming(
105+
offset,
106+
timingConfig,
107+
(isFinished: boolean) => {
108+
indexController.index.value = index;
109+
runOnJS(closeLock)(isFinished);
110+
}
111+
);
112+
} else {
113+
handlerOffsetX.value = offset;
114+
indexController.index.value = index;
115+
runOnJS(closeLock)(true);
116+
}
117+
},
118+
[
119+
canSliding,
120+
closeLock,
121+
width,
122+
timingConfig,
123+
indexController,
124+
lockController,
125+
handlerOffsetX,
126+
]
127+
);
128+
85129
return {
86130
next,
87131
prev,
132+
to,
88133
};
89134
}

src/useComputedIndex.ts renamed to src/useIndexController.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import * as React from 'react';
22
import Animated, { useSharedValue } from 'react-native-reanimated';
33

4-
export function useComputedIndex(opts: {
4+
export interface IIndexController {
5+
index: Animated.SharedValue<number>;
6+
computedIndex: () => void;
7+
}
8+
9+
export function useIndexController(opts: {
510
handlerOffsetX: Animated.SharedValue<number>;
611
length: number;
712
width: number;
8-
}) {
13+
}): IIndexController {
914
const { length, width, handlerOffsetX } = opts;
1015
const index = useSharedValue<number>(0);
1116

0 commit comments

Comments
 (0)