Skip to content

Commit 7645b75

Browse files
committed
feat: vertical mode
#41
1 parent 5287a09 commit 7645b75

File tree

12 files changed

+256
-199
lines changed

12 files changed

+256
-199
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import Carousel from 'react-native-reanimated-carousel';
102102
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
103103
| 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 |
104104
| 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 ...) |
105+
| vertical || false | boolean | Layout items vertically instead of horizontally |
105106

106107
## Ref
107108

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import Carousel from 'react-native-reanimated-carousel';
102102
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
103103
| windowSize || 0 | number | 能响应平移手势事件的最大 item 数量,0 表示所有元素都会先响应 |
104104
| onProgressChange || | onProgressChange?: (offsetProgress: number,absoluteProgress: number) => void | 当滚动进度发生变化时触发 `offsetProgress`:总的偏移值 (0 390 780 ...); `absoluteProgress`:转化为 index 的进度变化 (0 1 2 ...) |
105+
| vertical || false | boolean | 将元素垂直布局而不是水平 |
105106

106107
## Ref
107108

example/src/App.tsx

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,26 @@ export default function App() {
3838
paddingTop: 100,
3939
}}
4040
>
41-
<View style={{ width: PAGE_WIDTH, height: 240 }}>
42-
<Carousel
43-
defaultIndex={0}
44-
ref={r}
45-
width={PAGE_WIDTH}
46-
parallaxScrollingScale={0.8}
47-
data={data}
48-
renderItem={(source, index) => {
49-
return (
50-
<Image
51-
key={index}
52-
source={source}
53-
style={{
54-
width: '100%',
55-
height: '100%',
56-
}}
57-
/>
58-
);
59-
}}
60-
/>
61-
</View>
41+
<Carousel
42+
style={{ height: 240 }}
43+
defaultIndex={0}
44+
ref={r}
45+
width={PAGE_WIDTH}
46+
parallaxScrollingScale={0.8}
47+
data={data}
48+
renderItem={(source, index) => {
49+
return (
50+
<Image
51+
key={index}
52+
source={source}
53+
style={{
54+
width: '100%',
55+
height: '100%',
56+
}}
57+
/>
58+
);
59+
}}
60+
/>
6261
<View
6362
style={{
6463
marginTop: 24,
@@ -79,55 +78,50 @@ export default function App() {
7978
}}
8079
/>
8180
</View>
82-
<View
83-
style={{
84-
height: 240,
85-
alignItems: 'center',
81+
<Carousel
82+
style={{ height: 240 }}
83+
onProgressChange={(_, absoluteProgress) => {
84+
progressValue.value = absoluteProgress;
8685
}}
87-
>
88-
<Carousel
89-
onProgressChange={(_, absoluteProgress) => {
90-
progressValue.value = absoluteProgress;
86+
mode="parallax"
87+
width={window.width}
88+
parallaxScrollingScale={0.8}
89+
data={data}
90+
renderItem={(source, i) => {
91+
return (
92+
<Image
93+
key={i}
94+
source={source}
95+
style={{
96+
width: '100%',
97+
height: '100%',
98+
}}
99+
/>
100+
);
101+
}}
102+
/>
103+
{!!progressValue && (
104+
<View
105+
style={{
106+
flexDirection: 'row',
107+
justifyContent: 'space-between',
108+
width: 100,
109+
alignSelf: 'center',
110+
marginTop: 8,
91111
}}
92-
mode="parallax"
93-
width={window.width}
94-
parallaxScrollingScale={0.8}
95-
data={data}
96-
renderItem={(source, i) => {
112+
>
113+
{data.map((_, index) => {
97114
return (
98-
<Image
99-
key={i}
100-
source={source}
101-
style={{
102-
width: '100%',
103-
height: '100%',
104-
}}
115+
<PaginationItem
116+
animValue={progressValue}
117+
index={index}
118+
key={index}
119+
length={data.length}
105120
/>
106121
);
107-
}}
108-
/>
109-
{!!progressValue && (
110-
<View
111-
style={{
112-
flexDirection: 'row',
113-
justifyContent: 'space-between',
114-
width: 100,
115-
alignSelf: 'center',
116-
}}
117-
>
118-
{data.map((_, index) => {
119-
return (
120-
<PaginationItem
121-
animValue={progressValue}
122-
index={index}
123-
key={index}
124-
length={data.length}
125-
/>
126-
);
127-
})}
128-
</View>
129-
)}
130-
</View>
122+
})}
123+
</View>
124+
)}
131125
</View>
132126
);
133127
}

src/Carousel.tsx

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import { usePropsErrorBoundary } from './hooks/usePropsErrorBoundary';
1414
import { ScrollViewGesture } from './ScrollViewGesture';
1515
import { useVisibleRanges } from './hooks/useVisibleRanges';
1616
import type { ICarouselInstance, ICarouselProps } from './types';
17+
import { StyleSheet, View } from 'react-native';
1718

1819
function Carousel<T>(
1920
props: PropsWithChildren<ICarouselProps<T>>,
2021
ref: React.Ref<ICarouselInstance>
2122
) {
2223
const {
2324
defaultIndex = 0,
24-
height = '100%',
2525
data: _data = [],
2626
loop = true,
2727
mode = 'default',
@@ -30,35 +30,39 @@ function Carousel<T>(
3030
autoPlayInterval = 1000,
3131
parallaxScrollingOffset,
3232
parallaxScrollingScale,
33-
style,
33+
style = {},
3434
panGestureHandlerProps = {},
3535
renderItem,
3636
onSnapToItem,
3737
onProgressChange,
3838
windowSize,
39+
vertical,
3940
} = props;
4041

4142
usePropsErrorBoundary({
4243
...props,
43-
defaultIndex,
44-
height,
45-
loop,
46-
mode,
47-
autoPlay,
48-
autoPlayReverse,
49-
autoPlayInterval,
50-
parallaxScrollingOffset,
51-
parallaxScrollingScale,
52-
style,
53-
panGestureHandlerProps,
54-
// @ts-ignore
55-
onSnapToItem,
56-
onProgressChange,
5744
viewCount: _data.length,
5845
});
5946

60-
const width = Math.round(props.width);
61-
const defaultHandlerOffsetX = -Math.abs(defaultIndex * width);
47+
const width = React.useMemo(
48+
() => Math.round(props.width || 0),
49+
[props.width]
50+
);
51+
const height = React.useMemo(
52+
() => Math.round(props.height || 0),
53+
[props.height]
54+
);
55+
const size = React.useMemo(
56+
() => (vertical ? height : width),
57+
[width, height, vertical]
58+
);
59+
const layoutStyle = React.useMemo(() => {
60+
return {
61+
width: !vertical ? width : '100%',
62+
height: vertical ? height : '100%',
63+
};
64+
}, [vertical, width, height]);
65+
const defaultHandlerOffsetX = -Math.abs(defaultIndex * size);
6266
const handlerOffsetX = useSharedValue<number>(defaultHandlerOffsetX);
6367
const data = React.useMemo<T[]>(() => {
6468
if (!loop) return _data;
@@ -78,14 +82,14 @@ function Carousel<T>(
7882
originalLength: data.length,
7983
length: data.length,
8084
handlerOffsetX,
81-
width,
85+
size,
8286
loop,
8387
onChange: (i) => onSnapToItem && runOnJS(onSnapToItem)(i),
8488
});
8589

8690
const carouselController = useCarouselController({
8791
loop,
88-
width,
92+
size,
8993
handlerOffsetX,
9094
indexController,
9195
disable: !data.length,
@@ -115,8 +119,8 @@ function Carousel<T>(
115119
}, [sharedPreIndex, sharedIndex, computedIndex, props, run]);
116120

117121
const offsetX = useDerivedValue(() => {
118-
const totalWidth = width * data.length;
119-
const x = handlerOffsetX.value % totalWidth;
122+
const totalSize = size * data.length;
123+
const x = handlerOffsetX.value % totalSize;
120124

121125
if (!loop) {
122126
return handlerOffsetX.value;
@@ -127,7 +131,7 @@ function Carousel<T>(
127131
useAnimatedReaction(
128132
() => offsetX.value,
129133
(value) => {
130-
let absoluteProgress = Math.abs(value / width);
134+
let absoluteProgress = Math.abs(value / size);
131135
if (value > 0) {
132136
absoluteProgress = data.length - absoluteProgress;
133137
}
@@ -169,7 +173,7 @@ function Carousel<T>(
169173

170174
const visibleRanges = useVisibleRanges({
171175
total: data.length,
172-
viewSize: width,
176+
viewSize: size,
173177
translation: handlerOffsetX,
174178
windowSize,
175179
});
@@ -184,11 +188,13 @@ function Carousel<T>(
184188
parallaxScrollingScale={parallaxScrollingScale}
185189
data={data}
186190
width={width}
191+
height={height}
187192
handlerOffsetX={offsetX}
188193
index={i}
189194
key={i}
190195
loop={loop}
191196
visibleRanges={visibleRanges}
197+
vertical={vertical}
192198
>
193199
{renderItem(item, i)}
194200
</ParallaxLayout>
@@ -204,6 +210,7 @@ function Carousel<T>(
204210
key={i}
205211
loop={loop}
206212
visibleRanges={visibleRanges}
213+
vertical={vertical}
207214
>
208215
{renderItem(item, i)}
209216
</CarouselItem>
@@ -214,40 +221,56 @@ function Carousel<T>(
214221
loop,
215222
mode,
216223
data,
217-
height,
218224
offsetX,
219225
parallaxScrollingOffset,
220226
parallaxScrollingScale,
221-
width,
222227
renderItem,
223228
visibleRanges,
229+
vertical,
230+
width,
231+
height,
224232
]
225233
);
226234

227235
return (
228-
<ScrollViewGesture
229-
pagingEnabled
230-
infinite={loop}
231-
translation={handlerOffsetX}
232-
style={style}
233-
totalWidth={data.length * width}
234-
width={width}
235-
count={data.length}
236-
>
237-
<Animated.View
238-
// eslint-disable-next-line react-native/no-inline-styles
239-
style={{
240-
...style,
241-
width,
242-
height,
243-
flexDirection: 'row',
244-
position: 'relative',
245-
}}
236+
<View style={[styles.container, layoutStyle, style]}>
237+
<ScrollViewGesture
238+
pagingEnabled
239+
vertical={vertical}
240+
infinite={loop}
241+
translation={handlerOffsetX}
242+
style={style}
243+
max={data.length * size}
244+
size={size}
245+
panGestureHandlerProps={panGestureHandlerProps}
246246
>
247-
{data.map(renderLayout)}
248-
</Animated.View>
249-
</ScrollViewGesture>
247+
<Animated.View
248+
style={[
249+
styles.container,
250+
layoutStyle,
251+
style,
252+
vertical
253+
? styles.itemsVertical
254+
: styles.itemsHorizontal,
255+
]}
256+
>
257+
{data.map(renderLayout)}
258+
</Animated.View>
259+
</ScrollViewGesture>
260+
</View>
250261
);
251262
}
252263

253264
export default React.forwardRef(Carousel) as typeof Carousel;
265+
266+
const styles = StyleSheet.create({
267+
container: {
268+
overflow: 'hidden',
269+
},
270+
itemsHorizontal: {
271+
flexDirection: 'row',
272+
},
273+
itemsVertical: {
274+
flexDirection: 'column',
275+
},
276+
});

0 commit comments

Comments
 (0)