Skip to content

Commit a3894ff

Browse files
committed
feat: add onProgressChange props
1 parent c180763 commit a3894ff

File tree

4 files changed

+135
-18
lines changed

4 files changed

+135
-18
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ import Carousel from "react-native-reanimated-carousel";
114114
| onScrollBegin || | () => void | Callback fired when scroll begin |
115115
| onScrollEnd || | (previous: number, current: number) => void | Callback fired when scroll end |
116116
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
117+
| onProgressChange || | onProgressChange?: (offsetProgress: number,absoluteProgress: number) => void | On progress change. `offsetProgress`:Total of offset distance (0 390 780 ...); `absoluteProgress`:Convert to index (0 1 2 ...) |
118+
119+
120+
117121

118122
## Ref
119123

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ import Carousel from "react-native-reanimated-carousel";
113113
| onScrollBegin || | () => void | 切换动画开始时触发 |
114114
| onScrollEnd || | (previous: number, current: number) => void | 切换动画结束时触发 |
115115
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
116+
| onProgressChange || | onProgressChange?: (offsetProgress: number,absoluteProgress: number) => void | 当滚动进度发生变化时触发 `offsetProgress`:总的偏移值 (0 390 780 ...); `absoluteProgress`:转化为index的进度变化 (0 1 2 ...) |
116117

117118
## Ref
118119

example/src/App.tsx

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import * as React from 'react';
33
import { Dimensions, Image, ImageSourcePropType, View } from 'react-native';
44
import Carousel from '../../src/index';
55
import type { ICarouselInstance } from '../../src/Carousel';
6+
import Animated, {
7+
Extrapolate,
8+
interpolate,
9+
useAnimatedStyle,
10+
useSharedValue,
11+
} from 'react-native-reanimated';
612

713
const { width } = Dimensions.get('window');
814

@@ -13,6 +19,7 @@ const data: ImageSourcePropType[] = [
1319
];
1420

1521
export default function App() {
22+
const progressValue = useSharedValue<number>(0);
1623
const r = React.useRef<ICarouselInstance | null>(null);
1724
return (
1825
<View
@@ -33,15 +40,20 @@ export default function App() {
3340
<View style={{ flex: 1 }}>
3441
<Image
3542
source={source}
36-
style={{ width: '100%', height: '100%' }}
43+
style={{
44+
width: '100%',
45+
height: '100%',
46+
}}
3747
/>
3848
</View>
3949
)}
4050
/>
4151
</View>
4252
<View style={{ height: 300 }}>
4353
<Carousel<ImageSourcePropType>
44-
ref={r}
54+
onProgressChange={(_, absoluteProgress) => {
55+
progressValue.value = absoluteProgress;
56+
}}
4557
mode="parallax"
4658
width={width}
4759
data={data}
@@ -50,12 +62,86 @@ export default function App() {
5062
<View style={{ flex: 1 }}>
5163
<Image
5264
source={source}
53-
style={{ width: '100%', height: '100%' }}
65+
style={{
66+
width: '100%',
67+
height: '100%',
68+
}}
5469
/>
5570
</View>
5671
)}
5772
/>
73+
{!!progressValue && (
74+
<View
75+
style={{
76+
flexDirection: 'row',
77+
justifyContent: 'space-between',
78+
width: 100,
79+
alignSelf: 'center',
80+
}}
81+
>
82+
{data.map((_, index) => {
83+
return (
84+
<PaginationItem
85+
animValue={progressValue}
86+
index={index}
87+
key={index}
88+
length={data.length}
89+
/>
90+
);
91+
})}
92+
</View>
93+
)}
5894
</View>
5995
</View>
6096
);
6197
}
98+
99+
const PaginationItem: React.FC<{
100+
index: number;
101+
length: number;
102+
animValue: Animated.SharedValue<number>;
103+
}> = (props) => {
104+
const { animValue, index, length } = props;
105+
const width = 20;
106+
107+
const animStyle = useAnimatedStyle(() => {
108+
let inputRange = [index - 1, index, index + 1];
109+
let outputRange = [-width, 0, width];
110+
111+
if (index === 0 && animValue?.value > length - 1) {
112+
inputRange = [length - 1, length, length + 1];
113+
outputRange = [-width, 0, width];
114+
}
115+
116+
return {
117+
transform: [
118+
{
119+
translateX: interpolate(
120+
animValue?.value,
121+
inputRange,
122+
outputRange,
123+
Extrapolate.CLAMP
124+
),
125+
},
126+
],
127+
};
128+
}, [animValue, index, length]);
129+
return (
130+
<View
131+
style={{
132+
backgroundColor: 'white',
133+
width,
134+
height: width,
135+
borderRadius: 50,
136+
overflow: 'hidden',
137+
}}
138+
>
139+
<Animated.View
140+
style={[
141+
{ borderRadius: 50, backgroundColor: 'tomato', flex: 1 },
142+
animStyle,
143+
]}
144+
/>
145+
</View>
146+
);
147+
};

src/Carousel.tsx

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Animated, {
99
cancelAnimation,
1010
runOnJS,
1111
useAnimatedGestureHandler,
12+
useAnimatedReaction,
1213
useDerivedValue,
1314
useSharedValue,
1415
withSpring,
@@ -81,14 +82,21 @@ export interface ICarouselProps<T extends unknown> {
8182
* @default 0.8
8283
*/
8384
parallaxScrollingScale?: number;
84-
/**
85-
* Callback fired when navigating to an item
86-
*/
87-
onSnapToItem?: (index: number) => void;
8885
/**
8986
* Sping config of translation animated
9087
*/
9188
springConfig?: Animated.WithSpringConfig;
89+
/**
90+
* PanGestureHandler props
91+
*/
92+
panGestureHandlerProps?: Omit<
93+
Partial<PanGestureHandlerProps>,
94+
'onHandlerStateChange'
95+
>;
96+
/**
97+
* Callback fired when navigating to an item
98+
*/
99+
onSnapToItem?: (index: number) => void;
92100
/**
93101
* On scroll begin
94102
*/
@@ -98,12 +106,14 @@ export interface ICarouselProps<T extends unknown> {
98106
*/
99107
onScrollEnd?: (previous: number, current: number) => void;
100108
/**
101-
* PanGestureHandler props
109+
* On progress change
110+
* @param offsetProgress Total of offset distance (0 390 780 ...)
111+
* @param absoluteProgress Convert to index (0 1 2 ...)
102112
*/
103-
panGestureHandlerProps?: Omit<
104-
Partial<PanGestureHandlerProps>,
105-
'onHandlerStateChange'
106-
>;
113+
onProgressChange?: (
114+
offsetProgress: number,
115+
absoluteProgress: number
116+
) => void;
107117
}
108118

109119
export interface ICarouselInstance {
@@ -134,15 +144,16 @@ function Carousel<T extends unknown = any>(
134144
data: _data = [],
135145
loop = true,
136146
mode = 'default',
137-
renderItem,
138147
autoPlay,
139148
autoPlayReverse,
140149
autoPlayInterval = 1000,
141150
parallaxScrollingOffset,
142151
parallaxScrollingScale,
143-
onSnapToItem,
144152
style,
145153
panGestureHandlerProps = {},
154+
renderItem,
155+
onSnapToItem,
156+
onProgressChange,
146157
} = props;
147158

148159
const timingConfig = {
@@ -212,6 +223,19 @@ function Carousel<T extends unknown = any>(
212223
return isNaN(x) ? 0 : x;
213224
}, [computedAnimResult]);
214225

226+
useAnimatedReaction(
227+
() => offsetX.value,
228+
(value) => {
229+
let absoluteProgress = Math.abs(value / width);
230+
if (value > 0) {
231+
absoluteProgress = data.length - absoluteProgress;
232+
}
233+
!!onProgressChange &&
234+
runOnJS(onProgressChange)(value, absoluteProgress);
235+
},
236+
[onProgressChange, data]
237+
);
238+
215239
const next = React.useCallback(() => {
216240
return carouselController.next();
217241
}, [carouselController]);
@@ -305,14 +329,16 @@ function Carousel<T extends unknown = any>(
305329
[loop, data, onScrollBegin, onScrollEnd]
306330
);
307331

308-
React.useImperativeHandle(ref, () => {
309-
return {
332+
React.useImperativeHandle(
333+
ref,
334+
() => ({
310335
next,
311336
prev,
312337
getCurrentIndex,
313338
goToIndex,
314-
};
315-
});
339+
}),
340+
[getCurrentIndex, goToIndex, next, prev]
341+
);
316342

317343
const Layouts = React.useMemo<React.FC<{ index: number }>>(() => {
318344
switch (mode) {

0 commit comments

Comments
 (0)