Skip to content

Commit

Permalink
feat: vertical mode
Browse files Browse the repository at this point in the history
  • Loading branch information
gxxgcn committed Dec 11, 2021
1 parent 5287a09 commit 7645b75
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 199 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import Carousel from 'react-native-reanimated-carousel';
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
| 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 |
| 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 ...) |
| vertical || false | boolean | Layout items vertically instead of horizontally |

## Ref

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

## Ref

Expand Down
124 changes: 59 additions & 65 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,26 @@ export default function App() {
paddingTop: 100,
}}
>
<View style={{ width: PAGE_WIDTH, height: 240 }}>
<Carousel
defaultIndex={0}
ref={r}
width={PAGE_WIDTH}
parallaxScrollingScale={0.8}
data={data}
renderItem={(source, index) => {
return (
<Image
key={index}
source={source}
style={{
width: '100%',
height: '100%',
}}
/>
);
}}
/>
</View>
<Carousel
style={{ height: 240 }}
defaultIndex={0}
ref={r}
width={PAGE_WIDTH}
parallaxScrollingScale={0.8}
data={data}
renderItem={(source, index) => {
return (
<Image
key={index}
source={source}
style={{
width: '100%',
height: '100%',
}}
/>
);
}}
/>
<View
style={{
marginTop: 24,
Expand All @@ -79,55 +78,50 @@ export default function App() {
}}
/>
</View>
<View
style={{
height: 240,
alignItems: 'center',
<Carousel
style={{ height: 240 }}
onProgressChange={(_, absoluteProgress) => {
progressValue.value = absoluteProgress;
}}
>
<Carousel
onProgressChange={(_, absoluteProgress) => {
progressValue.value = absoluteProgress;
mode="parallax"
width={window.width}
parallaxScrollingScale={0.8}
data={data}
renderItem={(source, i) => {
return (
<Image
key={i}
source={source}
style={{
width: '100%',
height: '100%',
}}
/>
);
}}
/>
{!!progressValue && (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
width: 100,
alignSelf: 'center',
marginTop: 8,
}}
mode="parallax"
width={window.width}
parallaxScrollingScale={0.8}
data={data}
renderItem={(source, i) => {
>
{data.map((_, index) => {
return (
<Image
key={i}
source={source}
style={{
width: '100%',
height: '100%',
}}
<PaginationItem
animValue={progressValue}
index={index}
key={index}
length={data.length}
/>
);
}}
/>
{!!progressValue && (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
width: 100,
alignSelf: 'center',
}}
>
{data.map((_, index) => {
return (
<PaginationItem
animValue={progressValue}
index={index}
key={index}
length={data.length}
/>
);
})}
</View>
)}
</View>
})}
</View>
)}
</View>
);
}
Expand Down
117 changes: 70 additions & 47 deletions src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import { usePropsErrorBoundary } from './hooks/usePropsErrorBoundary';
import { ScrollViewGesture } from './ScrollViewGesture';
import { useVisibleRanges } from './hooks/useVisibleRanges';
import type { ICarouselInstance, ICarouselProps } from './types';
import { StyleSheet, View } from 'react-native';

function Carousel<T>(
props: PropsWithChildren<ICarouselProps<T>>,
ref: React.Ref<ICarouselInstance>
) {
const {
defaultIndex = 0,
height = '100%',
data: _data = [],
loop = true,
mode = 'default',
Expand All @@ -30,35 +30,39 @@ function Carousel<T>(
autoPlayInterval = 1000,
parallaxScrollingOffset,
parallaxScrollingScale,
style,
style = {},
panGestureHandlerProps = {},
renderItem,
onSnapToItem,
onProgressChange,
windowSize,
vertical,
} = props;

usePropsErrorBoundary({
...props,
defaultIndex,
height,
loop,
mode,
autoPlay,
autoPlayReverse,
autoPlayInterval,
parallaxScrollingOffset,
parallaxScrollingScale,
style,
panGestureHandlerProps,
// @ts-ignore
onSnapToItem,
onProgressChange,
viewCount: _data.length,
});

const width = Math.round(props.width);
const defaultHandlerOffsetX = -Math.abs(defaultIndex * width);
const width = React.useMemo(
() => Math.round(props.width || 0),
[props.width]
);
const height = React.useMemo(
() => Math.round(props.height || 0),
[props.height]
);
const size = React.useMemo(
() => (vertical ? height : width),
[width, height, vertical]
);
const layoutStyle = React.useMemo(() => {
return {
width: !vertical ? width : '100%',
height: vertical ? height : '100%',
};
}, [vertical, width, height]);
const defaultHandlerOffsetX = -Math.abs(defaultIndex * size);
const handlerOffsetX = useSharedValue<number>(defaultHandlerOffsetX);
const data = React.useMemo<T[]>(() => {
if (!loop) return _data;
Expand All @@ -78,14 +82,14 @@ function Carousel<T>(
originalLength: data.length,
length: data.length,
handlerOffsetX,
width,
size,
loop,
onChange: (i) => onSnapToItem && runOnJS(onSnapToItem)(i),
});

const carouselController = useCarouselController({
loop,
width,
size,
handlerOffsetX,
indexController,
disable: !data.length,
Expand Down Expand Up @@ -115,8 +119,8 @@ function Carousel<T>(
}, [sharedPreIndex, sharedIndex, computedIndex, props, run]);

const offsetX = useDerivedValue(() => {
const totalWidth = width * data.length;
const x = handlerOffsetX.value % totalWidth;
const totalSize = size * data.length;
const x = handlerOffsetX.value % totalSize;

if (!loop) {
return handlerOffsetX.value;
Expand All @@ -127,7 +131,7 @@ function Carousel<T>(
useAnimatedReaction(
() => offsetX.value,
(value) => {
let absoluteProgress = Math.abs(value / width);
let absoluteProgress = Math.abs(value / size);
if (value > 0) {
absoluteProgress = data.length - absoluteProgress;
}
Expand Down Expand Up @@ -169,7 +173,7 @@ function Carousel<T>(

const visibleRanges = useVisibleRanges({
total: data.length,
viewSize: width,
viewSize: size,
translation: handlerOffsetX,
windowSize,
});
Expand All @@ -184,11 +188,13 @@ function Carousel<T>(
parallaxScrollingScale={parallaxScrollingScale}
data={data}
width={width}
height={height}
handlerOffsetX={offsetX}
index={i}
key={i}
loop={loop}
visibleRanges={visibleRanges}
vertical={vertical}
>
{renderItem(item, i)}
</ParallaxLayout>
Expand All @@ -204,6 +210,7 @@ function Carousel<T>(
key={i}
loop={loop}
visibleRanges={visibleRanges}
vertical={vertical}
>
{renderItem(item, i)}
</CarouselItem>
Expand All @@ -214,40 +221,56 @@ function Carousel<T>(
loop,
mode,
data,
height,
offsetX,
parallaxScrollingOffset,
parallaxScrollingScale,
width,
renderItem,
visibleRanges,
vertical,
width,
height,
]
);

return (
<ScrollViewGesture
pagingEnabled
infinite={loop}
translation={handlerOffsetX}
style={style}
totalWidth={data.length * width}
width={width}
count={data.length}
>
<Animated.View
// eslint-disable-next-line react-native/no-inline-styles
style={{
...style,
width,
height,
flexDirection: 'row',
position: 'relative',
}}
<View style={[styles.container, layoutStyle, style]}>
<ScrollViewGesture
pagingEnabled
vertical={vertical}
infinite={loop}
translation={handlerOffsetX}
style={style}
max={data.length * size}
size={size}
panGestureHandlerProps={panGestureHandlerProps}
>
{data.map(renderLayout)}
</Animated.View>
</ScrollViewGesture>
<Animated.View
style={[
styles.container,
layoutStyle,
style,
vertical
? styles.itemsVertical
: styles.itemsHorizontal,
]}
>
{data.map(renderLayout)}
</Animated.View>
</ScrollViewGesture>
</View>
);
}

export default React.forwardRef(Carousel) as typeof Carousel;

const styles = StyleSheet.create({
container: {
overflow: 'hidden',
},
itemsHorizontal: {
flexDirection: 'row',
},
itemsVertical: {
flexDirection: 'column',
},
});
Loading

0 comments on commit 7645b75

Please sign in to comment.